磅:配置为处理WebSocket


3

是否可以将配置为处理WebSocket请求?如果没有,反向代理的最佳替代方案是什么?我想使用英镑或等效的轻量反向代理。


它已经不支持websockets吗?Websocket模拟正常的HTTP通信,以便现有代理可以处理它们。
John Dvorak

@JanDvorak-磅可能有支持,但似乎不起作用。在我的实验中(由代理两侧的tcpdump捕获组成),Pound似乎允许协议升级,将有效载荷的前几个字节发送回客户端,然后停止传输。除了确定它不起作用之外,我没有做过任何尝试。我需要一个解决方案,而不是笨拙地遍历Pound的源代码。:-P
ghoti

Answers:


1

磅似乎包含支持协议升级的代码,但我从未使其能够正常工作。论坛和磅邮件列表中也没有各种各样的人。

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

1

@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数据包永远不会按磅转发。printpython服务器脚本的输出证实了这一点,该脚本从未接收WebSocket rocks到中间带有英镑的消息。

每当英镑收到WebSocket消息时,它都会丢弃该消息,而是写入e414 headers: request URI too longsyslog。查看磅源代码,这似乎是因为磅试图解析HTTP标头。为此,它首先搜索在WebSocket消息中找不到的EOL,因此将其删除为无效消息。

因此,看来OP问题的答案确实是:英镑做不到WebSocket。

我就此问题写了一封电子邮件到磅列表:http : //www.apsis.ch/pound/pound_list/archive/2014/2014-01/1388844924000

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.