雑記帳
2011-11-18 (Fri) [長年日記]
■ [AWS] ELBでHTTPリスナーだとWebSocketは使えない
ELBを使っていて、WebSocketを使おうとしたときに、最初のハンドシェイクが上手くいかないという問題に遭遇した。
テストするために、node.jpのドキュメントから、http.ClientReqestにあるサンプルを使ってみた。ここのEvent: 'Upgrade'のところにあるサンプルコードを若干改造して下記のようなコードを用意した。
var http = require('http'); var net = require('net'); // Create an HTTP server var srv = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('okay'); }); srv.on('upgrade', function(req, socket, upgradeHead) { socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + 'Upgrade: WebSocket\r\n' + 'Connection: Upgrade\r\n' + '\r\n\r\n'); socket.ondata = function(data, start, end) { socket.write(data.toString('utf8', start, end), 'utf8'); // echo back }; }); // now that server is running srv.listen(3001, '0.0.0.0', function() {});
これを、test.jsとして、node.jsを起動すると、ポート80で待ち受ける。
これに対して、例えば、次のようなリクエストを送る。
GET http://websocket-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com/ HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: websocket-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
WebSocket的には、次のようなリクエストを返してくるはず。
HTTP/1.1 101 Web Socket Protocol Handshake Upgrade: WebSocket Connection: Upgrade
しかし、実際には、単純な200 OKが返ってきてしまう。
HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 4 Connection: keep-alive okay
ec2に直接リクエストをすれば、期待した通りの結果が帰ってくるのだが、ELBを介すると、正しく動作しない。EC2上でtcpdumpを実行して確認してみたところ、下記のように、ELBがリクエストヘッダから、UpgradeやConnectionを削除してしまっていることが分かった。
GET / HTTP/1.1 host:websocket-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com X-Forwarded-For: xxx.xxx.xxx.xxx X-Forwarded-Port: 80 X-Forwarded-Proto: http Connection: keep-alive
リスナーがHTTPではなければ問題ないだろうと思ったので、リスナーをHTTPからTCPに変更してみたところ、ヘッダが削除されることはなく、期待したとおりのレスポンスが返ってきた。
ということで、WebSocketを使うサービスをELBで負荷分散する場合は、リスナーをTCPにする必要がある。
追記: 問題の原因は、WebSocketはHTTPに似ているがHTTPではないため、ということのようだ。WebSocketというものをよく理解していなかった。
また、ELBは、httpリスナーの場合だと60秒でセッションが切れるので、WebSocketを使うときにはやはりTCPで接続する必要があるようだ。