it-swarm-ja.com

ポンド:WebSocketを処理するように構成する

WebSocketリクエストを処理するように ポンド を構成することは可能ですか?そうでない場合、リバースプロキシの最良の代替手段は何ですか?ポンドまたは同等の軽量リバースプロキシを使用したいのですが。

3
The Wavelength

Poundにはプロトコルのアップグレードをサポートするコードが含まれているようですが、私はそれを機能させることができませんでした。また、フォーラムやポンドのメーリングリストにさまざまな人がいません。

exratione.com には、SSLの背後にあるWebSocketの負荷分散のためのいくつかのオプションについて説明しているかなり詳細な投稿があります。この投稿(2012年初頭から)の結論は、良い解決策はないということです。

その投稿以来、 nginxはwebsocketプロキシサポートを追加した可能性があります なので、一見の価値があります。 nginxは構成に関してもう少し複雑であり、IIRCにはスティッキーセッション管理に関していくつかの制限がありますが、SSLをサポートする信頼性の高い高速リバースプロキシです。

WebSocket接続にSSLが必要ない場合は、単純なTCPロードバランサーを試してみてください。選択できるものはたくさんあります--- HAProxy で十分ですLinuxの人々に愛されていますが、 Pen 、OpenBSDの relayd (またはその FreeBSD port )などのシンプルで高品質の代替手段が存在します。

単一のバックエンドサーバーの前にリバースプロキシのみが必要で、負荷分散が必要ない場合は、stunnelを使用してフロントエンドHTTPS/WSS接続を受信し、内部バックエンドに接続できます。 ここにいくつかのサンプルのstunnel構成があります 。または、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

@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でトラフィックを分析したところ、プロトコルを切り替えるHTTP101リクエストが正しく転送されることがわかりました。ただし、後続の最初のWebSocketパケットがポンド単位で転送されることはありません。これは、pythonサーバースクリプトのprint出力によって確認されます。サーバースクリプトは、ポンドが中央にある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/13888449240

1
josch