web-dev-qa-db-ja.com

WebSocket:WebSocketハンドシェイク中にエラーが発生しました:空ではない「Sec-WebSocket-Protocol」ヘッダーが送信されましたが、応答が受信されませんでした

トルネードサーバーとのWS接続を作成しようとしています。サーバーコードはシンプルです:

class WebSocketHandler(tornado.websocket.WebSocketHandler):

    def open(self):
        print("WebSocket opened")

    def on_message(self, message):
        self.write_message(u"You said: " + message)

    def on_close(self):
        print("WebSocket closed")

def main():

    settings = {
        "static_path": os.path.join(os.path.dirname(__file__), "static")
    }


    app = tornado.web.Application([
            (r'/ws', WebSocketHandler),
            (r"/()$", tornado.web.StaticFileHandler, {'path':'static/index.html'}),
        ], **settings)


    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

here からクライアントコードをコピーして貼り付けました:

$(document).ready(function () {
    if ("WebSocket" in window) {

        console.log('WebSocket is supported by your browser.');

        var serviceUrl = 'ws://localhost:8888/ws';
        var protocol = 'Chat-1.0';
        var socket = new WebSocket(serviceUrl, protocol);

        socket.onopen = function () {
            console.log('Connection Established!');
        };

        socket.onclose = function () {
            console.log('Connection Closed!');
        };

        socket.onerror = function (error) {
            console.log('Error Occured: ' + error);
        };

        socket.onmessage = function (e) {
            if (typeof e.data === "string") {
                console.log('String message received: ' + e.data);
            }
            else if (e.data instanceof ArrayBuffer) {
                console.log('ArrayBuffer received: ' + e.data);
            }
            else if (e.data instanceof Blob) {
                console.log('Blob received: ' + e.data);
            }
        };

        socket.send("Hello WebSocket!");
        socket.close();
    }
});

接続しようとすると、ブラウザのコンソールに次の出力が表示されます。

WebSocket connection to 'ws://localhost:8888/ws' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

何故ですか?

14
Midiparse

whatwg.orgのWebsocketドキュメント で指摘されているように(これは標準のドラフトからのコピーです):

WebSocket(url、protocol)コンストラクタは、1つまたは2つの引数を取ります。最初の引数urlは、接続するURLを指定します。 2番目のプロトコルは、存在する場合、文字列または文字列の配列です。文字列の場合は、その文字列だけで構成される配列と同じです。省略すると、空の配列と同じになります。配列内の各文字列はサブプロトコル名です。 接続は、サーバーがこれらのサブプロトコルの1つを選択したことを報告した場合にのみ確立されます。サブプロトコル名はすべて、WebSocketプロトコル仕様で定義されているSec-WebSocket-Protocolフィールドの値を構成する要素の要件に一致する文字列である必要があります。

サーバーはSec-WebSocket-Protocolサブプロトコルをサポートしていないため、空のChat-1ヘッダーでWebSocket接続リクエストに応答します。

サーバー側とクライアント側の両方を記述しているため(そして、共有する予定のAPIを記述していない限り)、特定のサブプロトコル名を設定することはそれほど重要ではありません。

これを修正するには、JavaScript接続からサブプロトコル名を削除します。

var socket = new WebSocket(serviceUrl);

または、要求されたプロトコルをサポートするようにサーバーを変更します。

Rubyの例を示すことはできますが、Pythonの例を示すことはできません。これは、十分な情報がないためです。

編集(Rubyの例)

コメントで質問されたので、ここにRubyの例を示します。

rack.upgrade仕様ドラフト (詳細なコンセプト ここ )をサポートし、pub /を追加するため、この例にはiodine HTTP/WebSocketsサーバーが必要ですサブAPI。

サーバーコードは、ターミナルから、またはconfig.ruファイルのラックアプリケーションとして実行できます(サーバーを起動するには、コマンドラインからiodineを実行します)。

# frozen_string_literal: true

class ChatClient
  def on_open client
    @nickname = client.env['PATH_INFO'].to_s.split('/')[1] || "Guest"
    client.subscribe :chat    
    client.publish :chat , "#{@nickname} joined the chat."
    if client.env['my_websocket.protocol']
      client.write "You're using the #{client.env['my_websocket.protocol']} protocol"
    else
      client.write "You're not using a protocol, but we let it slide"
    end
  end
  def on_close client
    client.publish :chat , "#{@nickname} left the chat."
  end
  def on_message client, message
    client.publish :chat , "#{@nickname}: #{message}"
  end
end

module APP
  # the Rack application
  def self.call env
    return [200, {}, ["Hello World"]] unless env["rack.upgrade?"]
    env["rack.upgrade"] = ChatClient.new
    protocol = select_protocol(env)
    if protocol
      # we will use the same client for all protocols, because it's a toy example
      env['my_websocket.protocol'] = protocol # <= used by the client
      [101, { "Sec-Websocket-Protocol" => protocol }, []]
    else
      # we can either refuse the connection, or allow it without a match
      # here, it is allowed
      [101, {}, []]
    end
  end

  # the allowed protocols
  PROTOCOLS = %w{ chat-1.0 soap raw }

  def select_protocol(env)
    request_protocols = env["HTTP_SEC_WEBSOCKET_PROTOCOL"]
    unless request_protocols.nil?
      request_protocols = request_protocols.split(/,\s?/) if request_protocols.is_a?(String)
      request_protocols.detect { |request_protocol| PROTOCOLS.include? request_protocol }
    end # either `nil` or the result of `request_protocols.detect` are returned
  end

  # make functions available as a singleton module
  extend self
end

# config.ru
if __FILE__.end_with? ".ru"
  run APP 
else
# terminal?
  require 'iodine'
  Iodine.threads = 1
  Iodine.listen2http app: APP, log: true
  Iodine.start
end

コードをテストするには、次のJavaScriptが機能する必要があります。

ws = new WebSocket("ws://localhost:3000/Mitchel", "chat-1.0");
ws.onmessage = function(e) { console.log(e.data); };
ws.onclose = function(e) { console.log("Closed"); };
ws.onopen = function(e) { e.target.send("Yo!"); };
23
Myst