Answers:
磅似乎包含支持协议升级的代码,但我从未使其能够正常工作。论坛和磅邮件列表中也没有各种各样的人。
exratione.com上有一篇非常详细的文章,描述了SSL背后的负载均衡Web套接字的许多选项,包括Pound(作者最终也放弃了)。这篇文章(从2012年初开始)的结论是,没有好的解决方案。
从那篇文章开始,nginx可能已经添加了websocket代理支持,所以值得一看。nginx在配置方面涉及更多,并且IIRC在粘性会话管理方面有一些限制,但是它是支持SSL的可靠,快速的反向代理。
如果您不需要Websocket连接使用SSL,则可以尝试使用简单的TCP负载平衡器。有很多选择-HAProxy在Linux上广受欢迎,但是存在简单,高质量的替代方案,例如Pen,OpenBSD的中继(或其FreeBSD端口)等。
如果您只需要在单个后端服务器之前使用反向代理,并且不需要负载平衡,则可以只使用stunnel来接收前端HTTPS / WSS连接并连接到内部后端。 这是一些示例隧道配置。另外,您也许可以在pen前面使用stunnel ,但是您必须进行实验-我还没有做过,也无法告诉您是否可以使用。(如果尝试,请告诉我们您的结果!)
更新:
HAProxy 1.5.0于2014年6月19日发布。此版本在连接的两面均包含本机SSL支持,这意味着它现在是我的WebSocket代理“首选”解决方案。配置非常简单:
frontend http-in
...
bind 192.0.2.1:80 # if you want
bind 192.0.2.1:443 ssl crt /etc/ssl/yadda.pem
use_backend ws if { hdr(Upgrade) -i WebSocket }
backend ws
server node1 192.168.1.111:8000
server node2 192.168.1.112:8000
或者,您可以使用ACL通过主机名执行此操作:
frontend http-in
...
acl is_ws hdr_end(host) -i ws.example.com
use_backend ws if is_ws
@ghoti的答案效果很好,我可能会按照建议继续使用stunnel,但是这个问题仍然困扰着我,因此我将继续讨论@JanDvorak的评论,他声称已经进行了一些实验而没有进一步详细介绍。
我使用了以下简单的python websocket服务器,该服务器源自https://gist.github.com/jkp/3136208
import struct
import SocketServer
from base64 import b64encode
from hashlib import sha1
from mimetools import Message
from StringIO import StringIO
class WebSocketsHandler(SocketServer.StreamRequestHandler):
magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
def setup(self):
SocketServer.StreamRequestHandler.setup(self)
print "connection established", self.client_address
def handle(self):
data = self.request.recv(1024).strip()
headers = Message(StringIO(data.split('\r\n', 1)[1]))
if headers.get("Upgrade", None) != "websocket":
return
print 'Handshaking...'
key = headers['Sec-WebSocket-Key']
digest = b64encode(sha1(key + self.magic).hexdigest().decode('hex'))
response = 'HTTP/1.1 101 Switching Protocols\r\n'
response += 'Upgrade: websocket\r\n'
response += 'Connection: Upgrade\r\n'
response += 'Sec-WebSocket-Accept: %s\r\n\r\n' % digest
self.request.send(response)
length = ord(self.rfile.read(2)[1]) & 127
if length == 126:
length = struct.unpack(">H", self.rfile.read(2))[0]
elif length == 127:
length = struct.unpack(">Q", self.rfile.read(8))[0]
masks = [ord(byte) for byte in self.rfile.read(4)]
decoded = ""
for char in self.rfile.read(length):
decoded += chr(ord(char) ^ masks[len(decoded) % 4])
print decoded
self.request.send(chr(129))
length = len(decoded)
if length <= 125:
self.request.send(chr(length))
elif length >= 126 and length <= 65535:
self.request.send(126)
self.request.send(struct.pack(">H", length))
else:
self.request.send(127)
self.request.send(struct.pack(">Q", length))
self.request.send(decoded)
self.finish()
if __name__ == "__main__":
server = SocketServer.TCPServer(
("localhost", 9000), WebSocketsHandler)
try:
server.serve_forever()
except KeyboardInterrupt:
print "Got ^C"
server.server_close();
print "bye!"
然后将其与从http://www.websocket.org/echo.html借用的以下html结合在一起
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:9000/";
var output;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt) {
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
websocket.close();
}
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message) {
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id="output"></div>
</html>
这很好用,所以我在下面的配置项中加入了磅:
ListenHTTP
Address 127.0.0.1
Port 9999
Service
BackEnd
Address 127.0.0.1
Port 9000
End
End
End
并将html中的端口从9000更改为9999。更改之后,它停止工作。
通过使用wireshark分析流量,我发现HTTP 101请求切换协议的请求已正确转发。但是随后的第一个websocket数据包永远不会按磅转发。print
python服务器脚本的输出证实了这一点,该脚本从未接收WebSocket rocks
到中间带有英镑的消息。
每当英镑收到WebSocket消息时,它都会丢弃该消息,而是写入e414 headers: request URI too long
syslog。查看磅源代码,这似乎是因为磅试图解析HTTP标头。为此,它首先搜索在WebSocket消息中找不到的EOL,因此将其删除为无效消息。
因此,看来OP问题的答案确实是:英镑做不到WebSocket。
我就此问题写了一封电子邮件到磅列表:http : //www.apsis.ch/pound/pound_list/archive/2014/2014-01/1388844924000