Pound: configure para manipular WebSocket

3

É possível configurar libra para lidar com solicitações do WebSocket? Se não, quais são as melhores alternativas para um proxy reverso? Eu gostaria de usar libra ou um equivalente reverso equivalente leve.

    
por The Wavelength 26.01.2013 / 20:36

2 respostas

1

O Pound parece incluir um código que suporta a atualização do protocolo, mas nunca consegui fazê-lo funcionar. Nem tem várias pessoas em fóruns e na lista de discussão da libra.

Há um post bem detalhado em exratione.com que descreve uma série de opções para balanceamento de carga websockets por trás de SSL, incluindo Pound (que o autor também acabou desistindo). A conclusão deste post (que data do início de 2012) é que não existe uma solução boa .

Desde essa postagem, o nginx pode ter adicionado suporte a proxy de websocket , então vale a pena olhar. O nginx é um pouco mais complicado em termos de configuração, e o IIRC tem algumas limitações em relação ao gerenciamento de sessões, mas é um proxy reverso confiável e rápido que suporta SSL.

Se você não precisar de SSL para suas conexões de websocket, convém tentar um balanceador de carga TCP simples. Há muitos para escolher - HAProxy é bem amado pelo pessoal do Linux, mas existem alternativas simples e de alta qualidade como Pen , a relayd (ou sua porta do FreeBSD ), etc.

Se você precisar apenas de um proxy reverso na frente de um único servidor back-end e não precisar balancear a carga, provavelmente poderá usar o stunnel para receber conexões HTTPS / WSS front-end e se conectar a uma parte interna -fim. Aqui está uma amostra da configuração do stunnel . Como alternativa, você pode usar o stunnel na frente de pen , mas você teria que experimentar - eu não fiz isso e não posso dizer se ele funcionará. (Se você tentar, por favor, deixe-nos saber seus resultados!)

Atualização:

O HAProxy 1.5.0 foi lançado em 19 de junho de 2014. Esta versão inclui suporte a SSL nativo em ambos os lados da conexão, o que significa que agora é a minha solução "preferencial" para um proxy WebSocket. A configuração é incrivelmente fácil:

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

Ou, alternadamente, você poderia fazer isso por meio do nome do host usando uma ACL:

frontend http-in
    ...
    acl is_ws hdr_end(host) -i ws.example.com
    use_backend ws if is_ws
    
por 14.06.2013 / 22:59
1

A resposta do @ghoti funcionou bem e eu provavelmente vou continuar usando o stunnel como sugerido, mas esta questão me incomodou, então vou expandir o comentário de @JanDvorak, que afirmou ter feito alguns experimentos sem entrar em mais detalhes.

Eu usei o seguinte servidor websocket python, derivado do link

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!"

Combinei com o seguinte html que foi emprestado do link

<!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>

Isso funcionou bem, então coloquei no meio com a seguinte entrada de configuração:

ListenHTTP
    Address 127.0.0.1
    Port    9999
    Service
            BackEnd
                    Address 127.0.0.1
                    Port    9000
            End
    End
End

e mudou a porta no html de 9000 para 9999. Depois dessa mudança, parou de funcionar.

Analisando o tráfego com o wireshark, descobri que o pedido HTTP 101 para mudar o protocolo é encaminhado corretamente. Mas o primeiro pacote websocket subseqüente nunca é encaminhado por libra. Isso é confirmado pela saída print do script do servidor python, que nunca recebe a mensagem WebSocket rocks com a libra no meio.

Sempre que libra recebe uma mensagem do WebSocket, ela solta a mensagem e, em vez disso, grava e414 headers: request URI too long no syslog. Olhando para o código-fonte libra isso parece ser porque libra tenta analisar cabeçalhos HTTP. Para fazer isso, ele primeiro procura por um EOL que não pode ser encontrado na mensagem WebSocket e, portanto, descarta a mensagem como inválida.

Portanto, parece que a resposta para a pergunta do OP é: o pound não pode fazer o WebSocket.

Eu escrevi um e-mail para a lista de mensagens sobre esse problema: link

    
por 04.01.2014 / 14:06

Tags