3

Можно ли настроить фунт для обработки запросов WebSocket? Если нет, каковы лучшие альтернативы для обратного прокси? Я хотел бы использовать фунт или эквивалентный легкий обратный прокси.

2 ответа2

1

Кажется, что в Pound входит код, поддерживающий обновление протокола, но я так и не смог заставить его работать. Также нет разных людей на форумах и в списке рассылки фунта.

На exratione.com есть довольно подробный пост, в котором описан ряд опций для балансировки нагрузки веб-сокетов за SSL, включая Pound (от которого автор также в конце концов отказался). Вывод этого поста (который датируется началом 2012 года) состоит в том, что нет хорошего решения.

Начиная с этого поста, nginx, возможно, добавил поддержку прокси websocket, так что на это стоит обратить внимание. nginx немного больше вовлечен в настройку, и IIRC имеет некоторые ограничения в отношении управления сессионными сеансами, но это надежный и быстрый обратный прокси-сервер, поддерживающий SSL.

Если вам не требуется SSL для ваших подключений через веб-сокет, вы можете попробовать простой балансировщик нагрузки TCP. Есть из чего выбирать - HAProxy хорошо любят пользователи Linux, но существуют простые, высококачественные альтернативы, такие как Pen, релеид OpenBSD (или его порт FreeBSD) и т.д.

Если вам нужен только обратный прокси-сервер перед одним внутренним сервером и вам не требуется балансировка нагрузки, вы, вероятно, можете просто использовать stunnel для получения внешних HTTPS/WSS-соединений и подключения к внутреннему внутреннему серверу. Вот пример конфигурации stunnel. С другой стороны, вы можете использовать stunnel перед ручкой, но вам придется экспериментировать - я этого не делал и не могу сказать вам, будет ли это работать. (Если вы попытаетесь, пожалуйста, дайте нам знать ваши результаты!)

Обновить:

HAProxy 1.5.0 была выпущена 19 июня 2014 года. Эта версия включает в себя встроенную поддержку 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, полученный из 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!"

И я соединил это со следующим html, который был заимствован из http://www.websocket.org/echo.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 для системного журнала. Глядя на исходный код фунта, кажется, это потому, что фунт пытается разобрать заголовки HTTP. Чтобы сделать это, он сначала ищет EOL, который не может найти в сообщении WebSocket, и, таким образом, отбрасывает сообщение как недействительное.

Таким образом, кажется, что ответ на вопрос OP действительно таков: фунт не может сделать WebSocket.

Я написал электронное письмо в список фунтов об этой проблеме: http://www.apsis.ch/pound/pound_list/archive/2014/2014-01/1388844924000

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .