【问题标题】:WebSocket: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was receivedWebSocket:WebSocket 握手期间出错:发送非空“Sec-WebSocket-Protocol”标头但未收到响应
【发布时间】:2016-03-15 21:24:54
【问题描述】:

我正在尝试与我的 tornado 服务器创建 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

这是为什么呢?

【问题讨论】:

  • 发布您的客户端连接代码,否则我们会猜测...
  • 我使用了Chat-1 协议。最终我删除了那部分,并在没有指定协议的情况下打开了 WS,并以这种方式工作。我仍然对如何配置服务器端来接受它感兴趣。

标签: python websocket


【解决方案1】:

正如whatwg.org's Websocket documentation 中指出的(这是标准草案的副本):

WebSocket(url,protocols) 构造函数接受一两个参数。第一个参数 url 指定要连接的 URL。第二个,protocols,如果存在的话,要么是一个字符串,要么是一个字符串数组。如果它是一个字符串,则相当于一个仅由该字符串组成的数组;如果省略,则相当于空数组。数组中的每个字符串都是一个子协议名称。 只有当服务器报告它选择了这些子协议之一时,才会建立连接。子协议名称必须都是符合 WebSocket 协议规范定义的 Sec-WebSocket-Protocol 字段值的元素要求的字符串。

您的服务器使用空的 Sec-WebSocket-Protocol 标头响应 websocket 连接请求,因为它不支持 Chat-1 子协议。

由于您同时编写服务器端和客户端(除非您编写打算共享的 API),因此设置特定的子协议名称应该不是很重要。

您可以通过从 javascript 连接中删除子协议名称来解决此问题:

var socket = new WebSocket(serviceUrl);

或者通过修改您的服务器以支持所请求的协议。

我可以给出一个 Ruby 的例子,但我不能给出一个 Python 的例子,因为我没有足够的信息。

编辑(Ruby 示例)

由于我在 cmets 中被问到,这里有一个 Ruby 示例。

此示例需要iodine HTTP/WebSockets 服务器,因为它支持rack.upgrade specification draft(概念详解here)并添加了一个发布/订阅API。

服务器代码既可以通过终端执行,也可以作为config.ru 文件中的Rack 应用程序(从命令行运行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!"); };

【讨论】:

  • 我会对 Ruby 示例非常感兴趣。我正在玩 wamp gem 并且有同样的问题 (github.com/bradylove/wamp-ruby) 但它使用 v1.我正在考虑写一个 v2 WAMP gem ...
  • @awenkhh - 我不确定我是否理解您的问题。这是一个与客户端相关的问题,您提到的 gem 是一个服务器实现。也许您可以打开一个带有完整跟踪的新问题并在此处留下链接?
  • 嘿,如果你还有 Ruby 的例子,我可以给我吗?
  • @alt-ja ,为了您的方便,我编辑了答案以添加一个 Ruby 示例。祝你好运!
  • 对我来说,修复确实是删除构造函数中的子协议名称(第二个参数)。
【解决方案2】:

对于那些使用 cloudformation 模板的人,AWS 有一个很好的例子here

更新

关键是连接函数中的响应。在上述 AWS 上展示了如何做到这一点:

exports.handler = async (event) => {
    if (event.headers != undefined) {
        const headers = toLowerCaseProperties(event.headers);
        
        if (headers['sec-websocket-protocol'] != undefined) {
            const subprotocolHeader = headers['sec-websocket-protocol'];
            const subprotocols = subprotocolHeader.split(',');
            
            if (subprotocols.indexOf('myprotocol') >= 0) {
                const response = {
                    statusCode: 200,
                    headers: {
                        "Sec-WebSocket-Protocol" : "myprotocol"
                    }
                };
                return response;
            }
        }
    }
    
    const response = {
        statusCode: 400
    };
        
    return response;
};

function toLowerCaseProperties(obj) {
    var wrapper = {};
    for (var key in obj) {
        wrapper[key.toLowerCase()] = obj[key];
    }
    return wrapper;
}       

请注意响应中的标头设置。此外,此响应必须传递给请求者,因为必须配置此响应集成。

在 AWS 示例中考虑以下代码:

MyIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
  ApiId: !Ref MyAPI
  IntegrationType: AWS_PROXY
  IntegrationUri: !GetAtt MyLambdaFunction.Arn
  IntegrationMethod: POST
  ConnectionType: INTERNET 

最重要的是最后两行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-05
    • 2015-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-15
    • 2020-11-24
    相关资源
    最近更新 更多