Rev. 2.73

supermario.gif

Jacob Seidelin씨의 또 다른 작업이다. 그는 Canvas 엘리먼트와 자바스크립트로 횡스크롤 게임인 슈퍼 마리오를 구현했다. 600여개의 Sapn과 Canvas 엘리먼트로 구성된 이 게임은 놀랍게도 HTML과 스타일시트 그리고 자바스크립트 외에 별다른 외부파일을 전혀 사용하지 않는 특징을 가진다. 척보기에도 다양한 애니메이션과 배경에 사용된 많은 이미지를 포함하고 있을 것으로 보이지만 전혀 그렇지 않다. 셀 하나하나가 모두 Canvas 엘리먼트에서 실시간으로 생성된 그래픽이다. 심지어 배경으로 흘러나오는 배경음 역시 base64 data URI를 이용하여 MIDI음악을 재생한다. 놀랍게도 Canvas를 지원하지 않는 IE에서는 4만5천 여개의 Span태그로 그래픽을 그려내기 때문에 IE에서 즐기는데에도 문제가 없다. 한 일본인이 테이블 노가다로 구현한 슈퍼마리오와는 차원이 다르구나, 그저 놀라울 뿐이다.

Comments

firejune.png

Jacob Seidelin씨는 Canvas 엘리먼트에 그려진 정보를 이미지로 저장해주는 라이브러리인 Canvas2Image를 만들었다. 서버의 리소스를 차지하지 않고 자바스크립트만으로 base64 Data URI를 즉석해서 생성하여 PNG, BMP, JPEG 포멧으로 디스크에 저장할 수 있게 한다. 단순히 벡터(vector) 그래픽의 표현 수단에 불과했던 Canvas 엘리먼트를 저장 수단으로 활용한 멋진 역발상 사례다. 이 라이브러리를 응용하면 정말로 Canvas 기반은 웹 드로잉 애플리케이션을 만들 수도 있겠다. 비록 IE계열 브라우저에서 Canvas 엘리먼트를 철저하게 지원하지 않는 덕분에 현설성이 매우 떨어지기는 하지만 말이다.

var strDataURI = oCanvas.toDataURL();   
// returns "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACt..."  

Comments

웹사이트의 외부링크에 미리보기 이미지를 제공하는 서비스인 Snap.com은 광고모델과 접목시키면서부터 더이상 공짜로 프리뷰 이미지를 제공하지 않게 되었다. Snap.com은 프리뷰 이미지를 제공하는 서비스들 중에도 메이저 급에 해당하는 서비스로서 대표적으로는 Alexa Site Thumbnail 등이 있다. 이전의 서비스 방침은 회원 가입 후 사용자 키를 인증받아 요청하는 방식으로 비교적 간단하게 사용할 수 있었지만, 최근 시간 값을 가진 토큰(Token)을 추가하여 사실상 영원히 지속되는 프리뷰 이미지로의 접근을 막아 버린 것이다. 아래는 Snap.com의 주소 요청 스팩의 변경 내역이다.

/* 최초 이미지 요청 주소 */
shots.snap.com/preview/?url=http%3A%2F%2Ffirejune.com%2F&key=23fd8eb03b5aaa509e763e972aa05f&size=small

/* 토큰이 적용된 이미지 주소 */
shots.snap.com/preview/?url=http%3A%2F%2Ffirejune.com%2F&key=23fd8eb03b5aaa509e763e972aa05f&tok=000347faa37c97925f187cfaeedc5b7e&size=small

/* 토큰을 암호화한 이미지 주소 */
shots.snap.com/preview/?url=http%3A%2F%2Ffirejune.com%2F&key=23fd8eb03b5aaa509e763e972aa05f&src=&cp=&sb=1&v=3.24&size=small&lang=ko-kr&search_type=spasense&origin=shots_bubble&act=both_link&po=0&rp=null&tok=000347faa37c97925f187cfaeedc5b7e016644f19c&has_img=0&ol=0&ex=0&ad=unknown&ip=000.110.000.202&ua=Mozilla%2F5.0+%28Windows%3B+U%3B+Windows+NT+6.0%3B+ko%3B+rv%3A1.8.1.13%29+Gecko%2F20080311+Firefox%2F2.0.0.13&vid=b6fd6f4617146baf532db247af9176c5&nl=0&referrer=http%3A%2F%2Ffirejune.com%2F%23pl%3D1309&svc=&view_id=b6fd6f4617146baf532db247af9176c5&goto=%25URL(%3C)%20t%C3%99&direct=1&sc=0

토큰은 Snap.com에서 제공하는 외부 자바스크립트 파일에 변수로 그때 그때 생성되어 접근하도록 되어있다. 이 토큰은 약 하루정도의 시효를 가지고 있으며, 시효가 지난 토큰 값을 가진 이미지는 더미 이미지로 리다이렉트 된다. 이 블로그 역시 Snap.com의 프리뷰 이미지 서비스를 사용하고 있는데, Snap.com이 자체적으로 지원하는 프리뷰 위젯인 Snap Preview Anywher는 워낙에 덩치가 크고 불편하여 자체적으로 만들어 사용하고 있는 실정이다.

일단, 토큰 값을 알아내기 위해 서버단에서 Snap.com의 자바스크립트 파일을 분석하여 값을 추출하는 방식으로 다시금 이미지를 출력하는데 성공하였으나, 요 며칠전 Snap.com은 또 다시 스팩을 변경하여 프리뷰 이미지가 비정상적으로 출력되는 것을 확인하고 뚜껑(?)이 살짝 열린 상태에서 다시금 분석해 보았다. 어설프게도 자바스크립트를 이용하여 토큰을 해시(암호)코드로 다시 만들어 넘기는 방식이다. 그래서 아래와 같은 Prototype기반 클래스를 만들어 버렸다.

/* Snap Preview Anywhere Hack */
var SnapPreviewHack = Class.create({
  initialize: function() {
    this.key = '23fd8eb03b5aaa509e763e972aa05f'; /* user key */
    this.cfg = Cookie.get('SnapCfg') || null;

    if (!this.cfg) {
      this.getSnapCfg();
    } else {
      var arr = this.cfg.split('#-#');
      if (!arr[1] || arr[1] < new Date().getTime() - 86400000) {
        this.getSnapCfg();
      } else {
        try {
          this.cfg = arr[0].evalJSON();
        } catch(e) {
          this.getSnapCfg();
        }
      }
    }
  },
  getSnapCfg: function() {
    new Ajax.Request('/ajax/snapcfg.php', {
      onSuccess: function(req) {
        Cookie.set('SnapCfg', req.responseText + '#-#' + new Date().getTime());
        this.cfg = req.responseJSON;
      }.bind(this)
    });
  },
  getSnapSrc: function(event, href) {
    var vid = this.rand_hash(this.cfg.rnd + (event.clientX || 0) + (event.clientY || 0)),
    ua = navigator.userAgent.split(' ').join('+'),
    encode = function(uri) { return encodeURIComponent(uri); };
    /* return fake src */
    return 'http://shots.snap.com/preview/?url=' + encode(href) +
           '&key=' + this.key + '&src=' + encode((this.cfg.source || '')) + '&cp=' + (this.cfg.campaign || '') + 
           '&sb=' + (this.cfg.has_marea || '1') + '&v=' + this.cfg.version + '&size=' + this.cfg.size + '&lang=' + this.cfg.lang + 
           '&search_type=' + this.cfg.search_type + '&origin=shots_bubble&act=both_link&po=' + (this.cfg.preview_only ? "1": "0") + 
           '&rp=' + encode(this.cfg.redirect_param) + '&tok=' + this.tok(href) + '&has_img=0&ol=0&ex=0&ad=unknown' +
           '&ip=' + this.cfg.client_ip + '&ua=' + encode(ua) + '&vid=' + vid + '&nl=0&referrer=' + encode(document.location.href) +




           '&svc=&view_id=' + vid + '&goto=' + encode(this.cfg.GoToURL) + '&direct=1&sc=0';
  },
  /* piece of snap preview core  */
  hash32: function(D) {
    var C = 5003;
    D = D.toString();
    for (var B = 0; B < D.length; ++B) {
      C += D.charCodeAt(B);
      C += (C << 10);
      C ^= (C >> 6);
    }
    C += (C << 3);
    C ^= (C >> 11);
    C += (C << 15);
    C = Math.abs(C).toString(16);
    while (C.length < 8) {
      C = "0" + C;
    }
    return C;
  },
  rand_hash: function(B) {
    var C, D = ["", "", "", ""];
    B = B.toString() + Math.random() + document.location.href + new Date().getTime() + window.navigator.userAgent;
    for (C = 0; C < B.length; C++) {
      D[C % 4] += B.charAt(C);
    }
    return this.hash32(D[0]) + this.hash32(D[1]) + this.hash32(D[2]) + this.hash32(D[3]);
  },
  tok: function(B) {
    return this.cfg.tkn + "01" + this.hash32(this.cfg.tkn + "" + B);
  }
});

이미지 주소에 붙는 파라메터 값이 엄청나게 많은데 모두가 필수요소는 아니다. key, tok, v, 그리고 vid 정도만 넘겨줘도 이미지를 호출 할 수 있었다. 토큰은 하루정도의 시효가 있기 때문에 매번 서버로부터 호출하는 것이 불필요해서 쿠키에 JSON을 저장하고 비교하는 로직이 포함되어 있다. 위 클래스는 다음처럼 사용된다.

  initialize: function() {
    this.sph = new SnapPreviewHack();
    ...
  },
  ...
  /* event = mouse event, href = domain */
  createShot: function(event, href) {
    return new Element('img', {
      src = this.sph.getSnapSrc(event, href);
    }); 
  },
  ...

서버로부터 분석한 결과를 아래와 같이 JSON으로 수신 받아 처리한다.(변수는 실제 값과 다름)

{
  version: '3.24',
  size: 'small',
  lang: 'ko-kr',
  search_type: 'spasense',
  GoToURL: '%URL(으)로 이동',
  preview_only: false ,
  redirect_param: null,
  tkn: '000347fad03d5d5ba14bee5a546b9b1f',
  rnd: 'f9bb00debbf6b92e865ec64cbe807085',
  client_ip: '000.110.000.202'
}

그러고 보니, 시덥잖은 일로 소중한 주말 밤을 하얗게 불태웠다.

추가 2008-05-19: tok의 알고리듬이 아래와 같이 변경되었다.

//변경전:
tok: function(B) {
  return this.cfg.tkn + "01" + this.hash32(this.cfg.tkn + "" + B);
}
//변경후:
tok: function(B) {
  return this.cfg.tkn + "02" + this.hash32(this.cfg.tkn + "15c1c0ae" + B); 
}

Comments