我正在尝试与龙卷风服务器建立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
为什么?
答案 0 :(得分:18)
正如whatwg.org's Websocket documentation中指出的那样(它是标准草案的副本):
WebSocket(url,protocols)构造函数接受一个或两个参数。第一个参数url指定要连接的URL。第二种协议(如果存在)是字符串或字符串数组。如果它是一个字符串,它相当于一个只包含该字符串的数组;如果省略,则相当于空数组。数组中的每个字符串都是子协议名称。 仅当服务器报告已选择其中一个子协议时才会建立连接。子协议名称必须都是符合WebSocket协议规范定义的包含Sec-WebSocket-Protocol字段值的元素要求的字符串。
您的服务器使用空Sec-WebSocket-Protocol
标头回答websocket连接请求,因为它不支持Chat-1
子协议。
由于您正在编写服务器端和客户端(除非您编写要分享的API),否则设置特定的子协议名称并不是非常重要。
您可以通过从javascript连接中删除子协议名称来解决此问题:
var socket = new WebSocket(serviceUrl);
或者通过修改服务器以支持所请求的协议。
我可以给出一个Ruby示例,但由于我没有足够的信息,所以我不能给出Python示例。
由于我在评论中被问到,这是一个Ruby示例。
此示例需要iodine
HTTP / WebSockets服务器,因为它支持rack.upgrade
specification draft(概念详细here)并添加了pub / sub 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!"); };
答案 1 :(得分:1)
对于那些使用cloudformation模板的人来说,AWS有一个很好的例子here。
关键是连接功能中的响应。在上述AWS上显示了如何做到这一点:
library(rvest)
sapply(df$text, function(x) {
read_html(x) %>% html_nodes(css = "h1") %>% html_text %>% toString
}, USE.NAMES = F)
# [1] "my text, Keep it"
# [2] "title"
请注意响应中的标题设置。此响应还必须传递给请求者,因为必须配置此响应集成。
在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;
}
最重要的是最后两行。