@blog.justoneplanet.info

日々勉強

Unicode 6.0の絵文字のソースをコード内で扱える形式に変換する

自分用のメモ。元データをJavaやJavaScript内で扱える形式に変換する。以下のようにnodeで実装した。

var fs = require('fs');
var csv = require('csv');
csv()
.from.stream(fs.createReadStream(__dirname+'/EmojiSources.txt'), {"delimiter" : ";"})
.to.path(__dirname+'/sample.txt')
.transform(function(row){
  var elm = row.slice(0, 1); 
  if (elm[0].indexOf('1F') > -1) {
    var code = parseInt("0x" + elm[0], 16);
    code -= 0x10000;
    var hi = code >> 10; 
    var lo = code & 0x3FF;
    hi |= 0xD800;
    lo |= 0xDC00;
    elm[0] = '\\u' + hi.toString(16) + '\\u' + lo.toString(16);
    elm[1] = String.fromCharCode(hi, lo);
  }
  else {
    elm[1] = String.fromCharCode(parseInt(elm[0], 16));
    elm[0] = '\\u' + elm[0] + ''; 
  }
  return elm;
})
.on('record', function(row,index){
  console.log('#' + index + ' ' + JSON.stringify(row));
})
.on('close', function(count){
  console.log('Number of lines: '+count);
})
.on('error', function(error){
  console.log(error.message);
});

緊急地震速報 by Extensionのサーバーを書き直す

そろそろOAuth認証にしておかないとまずいので書きなおすことにした。

■環境構築

必要なパッケージのインストール等をする。

sudo yum -y install openssl-devel gcc-c++ git make
git clone https://github.com/isaacs/nave.git ~/.nave
~/.nave/nave.sh install stable
~/.nave/nave.sh use stable
echo "~/.nave/nave.sh use stable" >> ~/.bash_profile
sudo curl https://npmjs.org/install.sh --insecure | sh

npm -g install forever
npm install twitter
npm install websocket

以下のファイルを編集する。

vim /etc/security/limits.conf

以下の記述を加える。

*               soft    nofile            32768
*               hard    nofile            32768

■実装

以下のようにした。

const PORT = (process.argv[2])? process.argv[2] : 8080;
const FOLLOWING_ID = 16052553;
const CONSUMER_KEY    = "CONSUMER_KEY";
const CONSUMER_SECRET = "CONSUMER_SECRET";
const TOKEN_KEY       = "TOKEN_KEY";
const TOKEN_SECRET    = "TOKEN_SECRET";

var util            = require('util')
  , http            = require('http')
  , twitter         = require('twitter')
  , WebSocketServer = require('websocket').server
;

util.puts('[' + new Date() + ']Listen ' + process.argv[2]);
// build server
var server = http.createServer(function(request, response) {
  util.puts('[' + new Date() + ']Received request for ' + request.url);
  response.writeHead(404);
  response.end();
});
server.listen(PORT, function() {
  util.puts('[' + new Date() + ']Server is listening on port ' + PORT);
});
var wsServer = new WebSocketServer({
  "httpServer"            : server,
  "autoAcceptConnections" : true
});
wsServer.on(
  'connect',
  function(connection){
    util.puts('[' + new Date() + ']Connection accepted.');
    connection.sendUTF('{"status" : "accepted"}');
  }
);
setInterval(function () {wsServer.broadcastUTF("");}, 30 * 1000);

// build twitter connection
var twit = new twitter({
  "consumer_key"        : CONSUMER_KEY,
  "consumer_secret"     : CONSUMER_SECRET,
  "access_token_key"    : TOKEN_KEY,
  "access_token_secret" : TOKEN_SECRET
});
twit.stream('statuses/filter', {"follow" : FOLLOWING_ID}, function(stream) {
  stream.on('data', function(data) {
    if (data['user']['id'] === FOLLOWING_ID) {
      delete data['source'];
      delete data['contributors'];
      delete data['entities'];
      delete data['favorited'];
      delete data['lang'];
      delete data['truncated'];
      delete data['in_reply_to_status_id'];
      delete data['in_reply_to_status_id_str'];
      delete data['in_reply_to_user_id'];
      delete data['in_reply_to_user_id_str'];
      delete data['in_reply_to_screen_name'];
      delete data['user']['name'];
      delete data['user']['screen_name'];
      delete data['user']['url'];
      delete data['user']['description'];
      delete data['user']['protected'];
      delete data['user']['followers_count'];
      delete data['user']['friends_count'];
      delete data['user']['listed_count'];
      delete data['user']['created_at'];
      delete data['user']['favourites_count'];
      delete data['user']['utc_offset'];
      delete data['user']['time_zone'];
      delete data['user']['geo_enabled'];
      delete data['user']['verified'];
      delete data['user']['statuses_count'];
      delete data['user']['lang'];
      delete data['user']['contributors_enabled'];
      delete data['user']['is_translator'];
      delete data['user']['profile_background_color'];
      delete data['user']['profile_background_image_url'];
      delete data['user']['profile_background_image_url_https'];
      delete data['user']['profile_background_tile'];
      delete data['user']['profile_image_url'];
      delete data['user']['profile_image_url_https'];
      delete data['user']['profile_link_color'];
      delete data['user']['profile_sidebar_border_color'];
      delete data['user']['profile_sidebar_fill_color'];
      delete data['user']['profile_text_color'];
      delete data['user']['profile_use_background_image'];
      delete data['user']['default_profile'];
      delete data['user']['default_profile_image'];
      delete data['user']['following'];
      delete data['user']['follow_request_sent'];
      delete data['user']['notifications'];
      util.puts('[' + new Date() + ']EEW ' + data);
      wsServer.broadcastUTF(JSON.stringify(data));
    }   
  }); 
  stream.on('error' , function(err) {
    util.puts('[' + new Date() + ']Error::Stream' + err);
  }); 
});
setTimeout(
  function () {
    throw new Error('[' + new Date() + ']Error::for reboot');
  },  
  1000 * 60 * 60// 1 hour
);

■起動

以前はnohupとか使ってたが、ちゃんとforeverを使うようにする。

ulimit -n 32768
forever start -a --spinSleepTime=10000 -w --watchDirectory=./ -l ~/.forever/eew80.log server.js 80
forever start -a --spinSleepTime=10000 -w --watchDirectory=./ -l ~/.forever/eew443.log server.js 443
forever start -a --spinSleepTime=10000 -w --watchDirectory=./ -l ~/.forever/eew8080.log server.js 8080

■サーバーの再起動

rebootするとforeverが止まるので起動スクリプトを書く。

/etc/rc.d/init.d/eewd

パスは適宜かえるとする。

 /etc/init.d/functions

NAME=eewd
SOURCE_DIR=/var/www/hogehoge.com/eew
SOURCE_FILE=server.js

user=root
pidfile=/root/.forever/pids/$NAME.pid
logfile=/root/.forever/production.log
forever_dir=/root/.nave/installed/0.8.17/bin

node=/root/.nave/installed/0.8.17/bin/node
forever=/root/.nave/installed/0.8.17/bin/forever
sed=sed

export PATH=$PATH:/root/.nave/installed/0.8.17/bin
export NODE_PATH=$NODE_PATH:/root/.nave/installed/0.8.17/lib/node_modules


start() {
  echo "Starting $NAME node instance: "

  if [ "$foreverid" == "" ]; then
    # Create the log and pid files, making sure that
    # the target use has access to them
    touch $logfile
    chown $user $logfile

    touch $pidfile
    chown $user $pidfile

    # Launch the application
    daemon --user=root \
      $forever start -a -d --spinSleepTime=10000 -w --watchDirectory=$SOURCE_DIR -p $forever_dir --pidfile $pidfile -l $logfile $SOURCE_DIR/$SOURCE_FILE
    RETVAL=$?
  else
    echo "Instance already running"
    RETVAL=0
  fi
}

stop() {
  echo -n "Shutting down $NAME node instance : "
  if [ "$foreverid" != "" ]; then
    $node $SOURCE_DIR/prepareForStop.js
    $forever stop -p $forever_dir $id
  else
    echo "Instance is not running";
  fi
  RETVAL=$?
}

if [ -f $pidfile ]; then
  read pid < $pidfile
else
  pid = ""
fi

if [ "$pid" != "" ]; then
  # Gnarly sed usage to obtain the foreverid.
  sed1="/$pid\]/p"
  sed2="s/.*\[\([0-9]\+\)\].*\s$pid\].*/\1/g"
  foreverid=`$forever list -p $forever_dir | $sed -n $sed1 | $sed $sed2`
else
  foreverid=""
fi

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status -p ${pidfile}
    ;;
  *)
    echo "Usage:  {start|stop|status}"
    exit 1
    ;;
esac
exit $RETVAL

これでしばらくテストして上手く行けばリリースする。

node.jsでMySQLに接続する

MySQLサーバーから接続を切られた時は、errorイベントが発火する。一方で接続できなかった時はcallback関数の引き数にerrorオブジェクトが格納される。従って以下のように実装してみた。

var mysql = require('mysql');
var dbh = mysql.createConnection({
  "host"     : CONF['mysql']['host'],
  "user"     : CONF['mysql']['user'],
  "password" : CONF['mysql']['pass'],
  "database" : CONF['mysql']['db']
});
var handleDisconnect = function(dbh){
  dbh.on('error', function(err) {
    if (!err.fatal) {
      return;
    }
    if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
      throw err;
    }
    console.log('Re-connecting lost connection: ' + err.stack);
    util.puts('mysql::connect');
    dbh = mysql.createConnection(dbh.config);
    handleDisconnect(dbh);
    dbh.connect(dbhConnectHandler);
  });
}
var dbhConnectHandler = function(err){
  if (err) {
    util.puts('mysql::failed to connect[' + err.code + ']');
    setTimeout(
      function(){
        util.puts('mysql::connect');
        dbh = mysql.createConnection(dbh.config);
        handleDisconnect(dbh);
        dbh.connect(dbhConnectHandler);
      },
      1000 * 30// 30 seconds
    );
  }
  else {
    util.puts('mysql::connected');
  }
};
util.puts('mysql::connect');
handleDisconnect(dbh);
dbh.connect(dbhConnectHandler);

PHPとかでMySQLに繋ぐのに慣れてると発想の転換が必要になる。

他のファイルのスクリプトと接続を共有する

expressを使っているとapp.jsでの接続をroutes/index.jsでも使いたい事がある。他にも方法はあると思うが以下のようにしてオブジェクトを共有している。

app.js
require('./share').dbh = dbh;
routes/index.js
var dbh = require('./share').dbh;

その場合は以下のようにして再接続後の接続を共有オブジェクトにセットする必要がある。

var mysql = require('mysql');
var dbh = mysql.createConnection({
  "host"     : CONF['mysql']['host'],
  "user"     : CONF['mysql']['user'],
  "password" : CONF['mysql']['pass'],
  "database" : CONF['mysql']['db']
});
var handleDisconnect = function(dbh){
  dbh.on('error', function(err) {
    if (!err.fatal) {
      return;
    }
    if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
      throw err;
    }
    console.log('Re-connecting lost connection: ' + err.stack);
    util.puts('mysql::connect');
    dbh = mysql.createConnection(dbh.config);
    handleDisconnect(dbh);
    dbh.connect(dbhConnectHandler);
    require('./share').dbh = dbh;// ...(a)
  });
}
var dbhConnectHandler = function(err){
  if (err) {
    util.puts('mysql::failed to connect[' + err.code + ']');
    setTimeout(
      function(){
        util.puts('mysql::connect');
        dbh = mysql.createConnection(dbh.config);
        handleDisconnect(dbh);
        dbh.connect(dbhConnectHandler);
        require('./share').dbh = dbh;// ...(b)
      },
      1000 * 30// 30 seconds
    );
  }
  else {
    util.puts('mysql::connected');
  }
};
util.puts('mysql::connect');
handleDisconnect(dbh);
dbh.connect(dbhConnectHandler);
require('./share').dbh = dbh;

仮に(a)と(b)が存在しない場合、以下の様なroutes/index.js内では古い接続に対してクエリを発行する事になる。

dbh.query("something query", null, function (err) {
  if (err) {/*err*/}
  else {/*success*/}
});

この場合、厄介な事にcallbackが実行されず原因究明が困難となる。

hipacheをインストールする

簡単なので別に記事にするほどでもない。

■インストール

npm install hipache -g

■設定

Redisのportや接続に関する設定は以下のファイルを編集する。同階層のファイルを見れば大体分かる。

vim node_modules/hipache/config/config.json

以下のようにしてvhostの設定をする。

redis-cli rpush frontend:example.com hoge.identifer #フロントエンドを作って名前を付ける
redis-cli rpush frontend:example.com http://123.123.123.123:80 #フロントエンドにバックエンドを関連付ける
redis-cli rpush frontend:example.com http://234.234.234.234:80 #フロントエンドにバックエンドを関連付ける

■起動

sudo hipache

参考

殆どgithubに書いてあるものの和訳になってしまった。

nodeでHMAC-MD5を計算する

JavaやObjective-Cと違って1行で記述できる。

var crypto = require('crypto');
crypto.createHmac('md5', 'key').update(str).digest('hex');

md5値を求める場合は以下のようにする。

var crypto = require('crypto');
crypto.createHash('md5').update(str).digest('hex');

参考

foreverでexpressを動作させる

特に変わった事はない。

■インストール

npm install -g forever

■起動

forever start app.js

■停止

forever list
#info:   Forever processes running
#data:       uid  command script forever pid   logfile                                   uptime       
#data:   [0] T5dT node    app.js 24921   24922 /home/user/.forever/T5dT.log 0:0:0:35.653 
forever stop 0

EC2にexpressを入れる

■インストール

yum install openssl-devel gcc-c++ make#前準備
wget http://nodejs.org/dist/v0.6.11/node-v0.6.11.tar.gz
tar xvzf node-v0.6.11.tar.gz
cd node-v0.6.11
./configure
make
make install
curl http://npmjs.org/install.sh | sh
npm install -g express
express sample
cd sample
npm install

./app.js

たまたま80番開いていたので以下のようにして80番で繋がるようにする。

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes');

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

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

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

// Routes

app.get('/', routes.index);

app.listen(80);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

■起動

node app.js

http://ec2-123-123-123-123.ap-northeast-1.compute.amazonaws.com/などで自分のインスタンスにアクセスすれば見れる。

■socket.ioを使う

./package.json

以下のように編集する。

{
    "name": "application-name"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.8"
    , "jade": ">= 0.0.1"
    , "socket.io": "*" 
  }
}

./sampleで以下のコマンドを実行する。

npm install

./app.js

サーバー側のコードをapp.jsの最後尾に付加する。

var io = socketio.listen(app)
   ,count = 0;
io.sockets.on('connection', function(socket){
  count++;
  io.sockets.emit('count change', count);
  socket.on('disconnect', function() {
    count--;
    socket.broadcast.emit('count change', count);
  }
  );  
});

./views/layout.jade

クライアント側の処理を書き加える。

!!!
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(type='text/javascript', src='http://code.jquery.com/jquery.min.js')
    script(type='text/javascript', src='/socket.io/socket.io.js')
    script(type='text/javascript')
      var socket = io.connect();
      socket.on('count change', function(count) {
        $('#count').text(count);
      }); 
  body!= body

./views/index.jade

表示用に要素を追加する。

#page
  #headerArea
    h1= title
    p Welcome to #{title}
    p 現在このページを見ている人は
    span#count
    人います。

■ejsを使う

既存のhtmlコードを使用するときにhamlにするのは手間なのでejsを使う。

./package.json

以下のように編集する。

{
    "name": "application-name"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.8"
    , "jade": ">= 0.0.1"
    , "socket.io": "*" 
    , "ejs": "*" 
  }
}

./app.js

以下のようにして、テンプレートエンジンをjadeからejsに変更する。

/**
 * Module dependencies.
 */

var express  = require('express')
  , socketio = require('socket.io')
  , ejs      = require('ejs')
  , routes   = require('./routes');

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.set('view options', {layout : false});
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

せっかくなんでルーティングも変更してみる。

app.get('/', routes.index);
app.get('/hoge', function(req, res) {
  res.render('hoge.ejs', { title: 'My Site' }); 
});

参考

./views/hoge.ejs

あとは以下のようにテンプレートファイルを用意する。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<title><%= title %></title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect();
socket.on('count change', function(count) {
    $('#count').text(count);
});
</script>
</head>

<body>
<h1><%= title %></h1>
<p>現在このページを見ている人は<span id="count"></span>人います。</p>
</body>
</html>

以下のようなURLでアクセスができるようになる。


http://ec2-123-123-123-123.ap-northeast-1.compute.amazonaws.com/hoge

node.jsでbasic認証を使ってhttps接続する

nodeのバグを踏んだりしてたら4時間かかってしまったのでメモしておく。

■HTTP接続

以下のようにしてHTTPクライアントを生成し、ホストに接続することができる。

var host   = 'www.example.com';
var header = {"Host": host};
var client = http.createClient(
    80,
    host,
    false,
    false
).
client.request(
    "GET",
    "/",
    header
);

createClienetはどうやら廃止される方向らしいので以下のように書きなおす。

var http = require('http');
var host = 'www.example.com';
var req  = http.get(
    {
        "host"  : host,
        "port"  : 80,
        "path"  : "/"
    },
    function(res) {
        console.log("Got response: " + res.statusCode);
    }
);
req.on('error', function(e){
    console.log(e);
})

■Basic認証+HTTP接続

以下のようにしてHTTPクライアントを生成し、ホストに接続することができる。

var username = 'hoge';
var password = 'fuga';
var auth     = 'Basic ' + new Buffer(username + ':' + password).toString('base64');
var host     = 'www.example.com';
var header   = {
    "Host"          : host,
    "Authorization" : auth
};
var client   = http.createClient(
    80,
    host,
    false,
    false
).
client.request(
    "GET",
    "/",
    header
);

createClienetはどうやら廃止される方向らしいので以下のように書きなおす。

var http = require('http');
var host = 'www.example.com';
var req  = http.get(
    {
        "host"  : host,
        "port"  : 80,
        "path"  : "/",
        "auth"  : "username:password"
    },
    function(res) {
        console.log("Got response: " + res.statusCode);
    }
);
req.on('error', function(e){
    console.log(e);
})

■Basic認証+HTTPS接続

失敗例

以下のようにしてHTTPSクライアントを生成しても、ホストに接続することはできない。

var username = 'hoge';
var password = 'fuga';
var auth     = 'Basic ' + new Buffer(username + ':' + password).toString('base64');
var host     = 'www.example.com';
var header   = {
    "Host"          : host,
    "Authorization" : auth
};
var client   = http.createClient(
    443,
    host,
    true,
    false
).
client.request(
    "GET",
    "/",
    header
);

実行してもエラーがでる。

参考

上述のように失敗するのでhttpsモジュールを使う。

■Basic認証+HTTPS接続

以下のようにすることで、HTTPSクライアントでBasic認証を使うことができる。

var https    = require('https');
var host     = 'www.example.com';
var username = 'hoge';
var password = 'fuga';
var auth     = 'Basic ' + new Buffer(username + ':' + password).toString('base64');
var options  = {
    "host"          : host,
    "port"          : 443,
    "path"          : '/',
    "method"        : 'GET',
    "Authorization" : auth
};
var request = https.request(
    options,
    function(response) {
        console.log("statusCode: ", response.statusCode);
        console.log("headers: ", response.headers);
    }
);
request["_headers"]["Authorization"]     = auth;// ...(a)
request["_headerNames"]["Authorization"] = 'Authorization';// ...(b)
request.end();

optionsにAuthorizationをセットするだけではBasic認証が成功しなかった。認証用のヘッダが正しく付加されるように、強引に(a)と(b)を加えた。

参考

■後述

現在のnodeのバージョンでは以下のようにして簡単にhttpsでBasic認証を行うことができた。

var http = require('https');
var host = 'www.example.com';
var req  = http.get(
    {
        "host"  : host,
        "port"  : 443,
        "path"  : "/",
        "auth"  : "username:password"
    },
    function(res) {
        console.log("Got response: " + res.statusCode);
    }
);
req.on('error', function(e){
    console.log(e);
})

参考

Goolge Developer Day 2011のWeb Gameを解いてみる

Extensionとか入れたりするの面倒なので以下のJavaScriptをコンソールに打ち込む。

var ary = [];
for(var i = 0; i < 1024; i++){
    if($('card' + i)){
        $('#card' + i).click();
        ary.push($('#card' + i).css('backgroundColor'));
    }
}
ary.forEach(function(elm, i1, ary){
    for(var i2 = i1 + 1; i2 < ary.length; i2++){
        if(ary[i1] == ary[i2]){
            $('#card' + i1).click();
            $('#card' + i2).click();
        }
    }
});

WebSocket Serverまとめ

Chrome 14からWebSocketプロトコルのバージョンがdraft 10になる。現在、Chrome 13はdraft 76で実装されておりdraft 76とdraft 10は互換性がない。WebSocketを使った既存のサービスは壊れるのでdraft 10で実装されたWebSocketServerを探しときの情報をまとめた。

New WebSocket Protocol: Secure and Extensible

Please note that the new protocol is incompatible with one which Chromium previously supported (draft-ietf-hybi-thewebsocketprotocol-00),
so existing WebSocket-based services may break.
Please upgrade your servers to ones which support HyBi 10.
Existing JavaScript code still works once the protocol version used by the browser and server match.

新しいプロトコルは非互換でなので既存のWebSocketをベースとしたサービスは壊れる。HyBi 10をサポートするようにサーバをアップグレードしてくれ。既存のJavaScriptコードはプロトコルのバージョンが合致すれば動作するだろう。

WebSocket-Node

A WebSocket Draft -08/-09/-10 Implementation for Node.JS

いけちゃうっぽい!broadcastメソッドが存在しないようだが、以下のようにbroadcastメソッドを実装することにした。

var WebSocketServer = require('websocket').server;
WebSocketServer.prototype.broadcastUTF = function(data){
    this.connections.forEach(function(connection, i, ary){
        connection.sendUTF(data);
    });
};

作者の方にも気に入って頂けたようなので近いうちにモジュールに組み込まれるかもしれない。

インストール

npm install websocket

実装

以下のようにしてWebSocketサーバをセットアップした。

/**
 * extend default WebSocketServer for broadcast
 */
var WebSocketServer = require('websocket').server;
WebSocketServer.prototype.broadcastUTF = function(data){
    this.connections.forEach(function(connection, i, ary){
        connection.sendUTF(data);
    });
};


/**
 * websocket server for end users
 */
var server = require('http').createServer(
    function(request, response) {
        console.log((new Date()) + " Received request for " + request.url);
        response.writeHead(404);
        response.end();
    }
);
server.listen(
    eew.env['port'],
    function() {
        console.log((new Date()) + " Server is listening on port 80");
    }
);

wsServer = new WebSocketServer({
    "httpServer"            : server,
    "autoAcceptConnections" : true
});

wsServer.on(
    'connect',
    function(connection){
        console.log((new Date()) + " Connection accepted.");
        connection.sendUTF('{"status" : "accepted"}');
    }
);

broadcastUTFメソッドを自作した以外は殆どサンプルコードと変わらない。

■Support draft-10 (chrome 14-dev+)

node.jsには何種類かWebSocket Serverの実装がある。

This will either be done
via node-websocket-protocol (preferable), or
via some kind of hack to the core of node-websocket-server, which is the route I really don’t want to take.

どうやらプロトコルだけ外に出してモジュール化することで対応する意向らしい。しかし、レスポンスが遅いのが気になる。

Socket IO

hybi10 incompatibility

軽く読んだ限りでは最新の仕様では不具合が発生するようだ。ロングポールなど代替手段が揃っているので有用な選択肢ではある。但し、今回はクライアント側のコードを変更したくないので選択肢から外した。

追記:8/30動作するようになった模様。

pywebsocket

node.js用のモジュールが存在しなければ、他の言語のモジュールで対応するという選択肢も考えた。pywebsocketではdraft-ietf-hybi-thewebsocketprotocol-07まで対応している。

Jetty

Jetty has supported the various WebSocket drafts in the 7.x and 8.x releases.

どうやら最新の実装には対応していないようだ。

■参考

The WebSocket protocol

draft-ietf-hybi-thewebsocketprotocol-10

Comparison table of websocket server implementations.

WebSocketサーバの実装が比較されているが更新されていないっぽい。

■まとめ

バージョンは適宜アップデートされるだろう。node.jsを選んで良かった。

後述