hakobera's blog

技術メモ。たまに雑談

Clound Foundry の Node で 動く Data URL Proxy Server を書いた

以前、GAE/J で同様のものを書いて利用していたのですが、大した処理じゃないのにスピンアップの時間が1〜2秒かかって初回レスポンスが遅いので、Cloud Foundry で動かせるように Node 版に書き換えてみました。Express 使うほどの処理ではないのですが、趣味と慣れの問題で使ってます。

こういう小物の Proxy や API サーバを置くには、Cloud Foundry (もしくは dotcloud) の Node がコードも短いし、インフラ整備もいらないので良いんじゃないかと思います。

ソース

base64 encoding images using express in NodeJS — Gist

/*
 * node-dataurl-proxy
 * 
 * This code can run in Clound Foundry (http://cloudfoundry.com)
 * You can see example: http://dataurlproxy.cloudfoundry.com/?url=[url]
 */

require.paths.unshift('./node_modules');

var express = require('express'),
              URL = require('url'),
              http = require('http');

var app = module.exports = express.createServer(),
    port = process.env.VMC_APP_PORT || 3000;

// Configuration

app.configure(function(){
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 
});

app.configure('production', function(){
  app.use(express.errorHandler()); 
});

// Routes

app.get('/', function(req, res){
  var url, client, proxyRequest;
  if (req.param('url')) {
    url = URL.parse(req.param('url'));
    
    client = http.createClient(80, url.hostname);
    client.on('error', function(err) {
      res.statusCode = 404;
      res.send(err.message);
    });

    proxyRequest = client.request('GET', url.pathname, { "host": url.hostname });
    proxyRequest.end();

    proxyRequest.on('response', function(proxyResponse) {
      var type = proxyResponse.header('content-type'),
          prefix = 'data:' + type + ';base64,',
          body = '',
          success = true;

      proxyResponse.setEncoding('binary');
      
      proxyResponse.on('data', function(chunk) {
        if (proxyResponse.statusCode === 200) {
	  body += chunk;
        } else {
          res.statusCode = proxyResponse.statusCode;
	  body += chunk;
	  success = false;
        }
      });

      proxyResponse.on('end', function() {
        if (success) {
	  var base64 = new Buffer(body, 'binary').toString('base64'),
	      data = prefix + base64,
	      obj = {
                "src": url.href,
                "data": data
              };

          res.contentType('application/json');
          res.header("Access-Control-Allow-Origin", "*");
          res.header("Access-Control-Allow-Headers", "X-Requested-With");
          res.header("Access-Control-Allow-Methods", "GET,POST");

          res.send(JSON.stringify(obj));
        } else {
          res.send(body);
        }
      });

    });
  }
});

// Only listen on $ node app.js

if (!module.parent) {
  app.listen(port);
  console.log("Express server listening on port %d", app.address().port);
}

クライアント側のコード

jQuery を使うと以下のような感じです。

HTML

<canvas id="canvas"></canvas>

JavaScript

var proxy = 'http://yourappid.cloudfoundry.com/',
      imageUrl = "画像のURL",
      canvas = document.getElementById('canvas'),
      ctx = canvas.getContext('2d');

$.ajax({
  url: proxy,
  type: 'GET',
  data: { url: imageUrl },
  dataType: 'json',
  success: function(result) {
    var img = new Image();
    img.onload = function() {
      ctx.drawImage(img, 0, 0, img.width, img.height);
    };
    img.src = result.data;
  }
});

Data URL Proxy ってどういう場面で必要なの?

HTML5Canvas は drawImage メソッドを利用することで、画像を表示することができます。 ただし、外部ドメインの画像を描画した場合、same-origin-policy 制約により、 描画コンテキストのピクセルデータにアクセスできません。(getImageData メソッドを呼び出すと、セキュリティエラー例外が発生します。)

DATA URL Proxy を利用することで、この制約を回避することができます。

ちなみにGAE/J 版はこれ

比較するとすごく簡単に書けているのがわかりますね。ファイル1つで済むし。

hakobera/data-url-proxy-for-google-appengine · GitHub