8月3日現在 Node stable v0.4.10, Socket.IO v0.7.7
表80をnginx->裏3000とかでsocket.io動かしてる場合WebSocketはダメでもxhr-pollingとかなら80に繋ぎにいっても動くはず、と思って試してみてるけど動かない、、どうなってるんだろうコレ
socket.io v0.6系だと動くようなのに0.7で動かない?
nginxを使ってreverse proxyする場合、Socket.IOのWebSocket接続は
- 表側に繋がずに裏側のportを指定して繋ぐ
- nginx_tcp_proxy_moduleを使って通す
- あきらめる
のどれかだと思う。
しかしSocket.IOは様々な接続方法をサポートしているので、WebSocketをあきらめてもxhr-pollingや他の方法で繋げられるはず。
ということで試してみたのだけど上手くいかなかった。
失敗事例
nginxを以下のように設定し
http { include mime.types; default_type application/octet-stream; upstream node { server 127.0.0.1:3000; } server { listen 80; server_name _; location / { proxy_pass http://node; } } }
nodeを3000番ポートで動かす。
var server = require('http').createServer(function (req, res) { res.writeHeader(200, { 'Content-Type': 'text/html' }); res.end( '<!DOCTYPE html><html>' + '<head><script type="text/javascript" src="/socket.io/socket.io.js"></script>' + '<script type="text/javascript">' + 'var s = io.connect(null, { transports: ["xhr-polling"] });' + 's.on("connect", function(){ console.log("ok"); });' + 's.on("connect_failed", function(){ console.log("ng"); });' + '</script>' + '</head><body></body></html>'); }); server.listen(3000); require('socket.io').listen(server);
ここではwebsocketをあきらめているので最初からxhr-polling指定で。
[]http://127.0.0.1:80/[]
をブラウザで叩いてみる。
$ node app.js info - socket.io started debug - served static /socket.io.js debug - client authorized info - handshake authorized 18151724210868660 debug - setting request GET /socket.io/1/xhr-polling/18151724210868660?t1312366753908 debug - setting poll timeout debug - client authorized for debug - clearing poll timeout debug - xhr-polling writing 1:: debug - set close timeout for client 18151724210868660 debug - setting request GET /socket.io/1/jsonp-polling/18151724210868660/?t=1312366763909&i=0 debug - setting poll timeout debug - discarding transport debug - cleared close timeout for client 18151724210868660
xhr-pollingで繋いでみて失敗し、jsonp-pollingを試みるが結局ダメで接続が失敗する。
ちなみに繋ぐ先のポートを裏側に明示指定すると一発で繋がる。
var s = io.connect(null, { transports: ["xhr-polling"], port: 3000 });
↑これならok
で、試しにSocket.IO v0.6.18で似たようなことをしてみるとこれは上手くいく。
原因
何が起こっているのだろう? と調べ始め、試しに最初に繋いでいる
[]http://127.0.0.1:80/socket.io/1/xhr-polling/18151724210868660?t1312366753908[]
というのを叩いてみるとこれがレスポンス返ってこない。
じゃあコイツは何を返そうとしているのか、と裏側を直接叩いてみる。
$ curl -i 'http://127.0.0.1:3000/socket.io/1/xhr-polling/18151724210868660?t1312366753908' HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 7 Connection: Keep-Alive 7:::1+0
どうもこの"Keep-Alive"が原因らしく。@typester先生に教えていただいたところ、nginxのHttpProxyModuleは裏側のkeep-aliveには対応していない。
Module ngx_http_proxy_module
上記のようなレスポンスが来た場合はバッファにレスポンスを溜め込み接続が切れない状態になってしまうため、表側にはレスポンスが返ってこない、ということらしい。
解決策
とりあえず表のnginxでproxy_buffering
をoffに設定しておく、という手がある。
server { listen 80; server_name _; location / { proxy_buffering off; proxy_pass http://node; } }
こうしておくと、バッファに溜めずにすぐにレスポンスを返してくれるようになるので、結果的にSocket.IOのxhr-pollingも接続できるようになる。
もしくはSocket.IO側でレスポンスヘッダからkeep-aliveの項目を削除するようにコード書き変えるか何かする、という方法も…?nginx側でproxy_bufferingをoffにしなくても、socket.io/lib/transports/xhr-polling.js
のdoWriteメソッドでheadersを指定している部分があるので、
var origin = this.req.headers.origin , headers = { 'Content-Type': 'text/plain; charset=UTF-8' , 'Content-Length': data === undefined ? 0 : Buffer.byteLength(data) , 'Connection': 'Keep-Alive' };
このKeep-Aliveの行をコメントアウトするだけでも一応動くようになる。