«前の日記(2011-11-17 (Thu)) 最新 次の日記(2011-11-22 (Tue))» 編集

雑記帳


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で接続する必要があるようだ。