てきとうなさいと べぇたばん

WebSocketメモ Sec-WebSocket-Protocolについてgevent-websocketから知ったこと

pywsgiのgevent-websocketを使ってて気になったこと

Mozilla Firefoxでは動くのに、Google Chromeでは動かない事象に遭遇した。結論から言ってしまうと、WebSocketにサブプロトコルを設定していたのが原因だった。

var socket = new WebSocket('ws://127.0.0.1:8080/graph', ['soap', 'chat']);

Consoleを見てみると、以下の様なエラーになっていた。

(index):15 WebSocket connection to 'ws://127.0.0.1:8080/graph' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

gevent-websocketではどうするのか

サブプロトコルを指定している場合、pywsgiではapp_protocolメソッドを作成して許可するサブプロトコルを返してあげなければならない。

def app_protocol(self, path_info):
    return 'chat'

コレは、gevent-websocketのソースを見て調べた。app_protocolがあれば"Sec-WebSocket-Protocol"がレスポンスヘッダに返されるということで何かを指定することにしたのだ。

https://github.com/youkidearitai/Arduino_LED_on_Python/blob/21376b0fcaf8181a0fc3c7f2e97b9fdcacfd0cc1/accelerator_sensor.pyでは、最初は'xmpp'にしていたけどどうやら'chat'でも行けるようだったので最終的に'chat'にしておいた。

gevent-websocketでいちいち実装するものかコレ?

ただ、いちいちapp_protocolを記述しなければならないのはおかしいので、コレはもしかするとpywsgiかgevent-websocketのバグではないかと疑った。

もしそうなら、Sec-WebSocket-Protocolを指定した状態で返すべきではないのかということである。というわけで調べてみた。

RFC6455

WebSocketが定義されているRFC6455では、もしもSec-WebSocket-Protocolヘッダの値からこのサーバーが接続に用いるひとつがもしあれば導出されてレスポンスヘッダとして返され、ひとつもなければnullを返す(レスポンスヘッダにSec-WebSocket-Protocolを返してはならない)とある。

if the server does not agree to any of the client's requested subprotocols, the only acceptable value is null. The absence of such a field is equivalent to the null value (meaning that if the server does not wish to agree to one of the suggested subprotocols, it MUST NOT send back a |Sec-WebSocket-Protocol| header field in its response).

WebSocketはもしもサブプロトコルがあるのならば、クライアントがそれが許可されているかどうかをサーバーに確認し、許可されているサブプロトコルのうちのひとつを返す。それがSec-WebSocket-Protocolである。

In this version of the protocol, the main option field is |Sec-WebSocket-Protocol|, which indicates the subprotocol that the server has selected. WebSocket clients verify that the server included one of the values that was specified in the WebSocket client's handshake. A server that speaks multiple subprotocols has to make sure it selects one based on the client's handshake and specifies it in its handshake.

ぼくが間違っていたようだ

ここで要点をまとめると、以下のとおりとなり、gevent-websocketはそのとおりに実装していることがわかる。

  • WebSocketはサブプロトコルを選択することができる
  • サブプロトコルを指定しないこともできる
  • サブプロトコルを指定しないのならば、Sec-WebSocket-Protocolは指定してはいけない

つまりクライアントで以下のようにすればよく、これでサーバー側でapp_protocolメソッドが削除できる。

var socket = new WebSocket('ws://127.0.0.1:8080/graph');

すなわち、gevent-websocketのバグではなく、ぼくが間違っていたということになる。プルリク送ることにならなくてよかった。あぶね。