Рукопожатие Websocket зависает с HAProxy

В последнем браузере Chrome я пытаюсь использовать клиент sockjs для связи с внутренним сервером, который находится за haproxy. На моем локальном хосте (без haproxy посередине) это работает нормально — клиент может подключаться, отправлять и получать сообщения, используя протокол websocket. Например:

conn.onopen = function() {
    if (conn.readyState === SockJS.OPEN) {
        conn.send("hello server");
        console.log("msg sent");
    }
};

Как только я развертываю его на сервере с HAProxy, происходит странная вещь, что sockjs думает, что соединение открыто (как в conn.readyState === SockJS.OPEN, и в журнале консоли появляется сообщение «отправлено msg»), однако websocket рукопожатие просто зависает, и msg никогда не принимается сервером . Ниже то, что я вижу в журнале haproxy:

Oct 23 09:08:25 localhost.localdomain haproxy[14121]: 129.xx.xxx.105:55000 [23/Oct/2012:09:08:24.459] public www/content 777/0/0/1/778 200 375 - - ---- 3/3/0/1/0 0/0 "GET /sockjs/info HTTP/1.1"
Oct 23 09:10:54 localhost.localdomain haproxy[14121]: 129.xx.xxx.105:55015 [23/Oct/2012:09:08:25.398] public www/content 0/0/0/1/149017 101 147 - - CD-- 4/4/0/0/0 0/0 "GET /sockjs/478/kyi342s8/websocket HTTP/1.1"

Обратите внимание, что второе сообщение журнала появляется только тогда, когда я отключаю внутренний сервер за haproxy. Перед выключением в журнале нет ошибок, но рукопожатие веб-сокета не завершается, и сервер не получает сообщения.

Используя инструменты разработчика Chrome, на вкладке «Сеть» я вижу следующее:

Request URL:ws://www.mysite.com/sockjs/478/kyi342s8/websocket
Request Method:GET
Status Code:101 Switching Protocols

Request Headers
Connection:Upgrade
Host:www.mysite.com
Origin:http://www.mysite.com
Sec-WebSocket-Extensions:x-webkit-deflate-frame
Sec-WebSocket-Key:TFEIKYhlqWWBZKlXzXAuWQ==
Sec-WebSocket-Version:13
Upgrade:websocket
(Key3):00:00:00:00:00:00:00:00

Response Headers
Connection:Upgrade
Sec-WebSocket-Accept:D+s3va02KH6QTso24ywcdxcfDgM=
Upgrade:websocket
(Challenge Response):00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00

И «Тип», и «Время задержки» объекта websocket показывают «Ожидание» на вкладке «Сеть» инструментов разработчика.

Наконец, это мой конфиг haproxy (версия 1.4.22):

global
        log 127.0.0.1   local1 info
        log 127.0.0.1   local1 notice
        #log loghost    local0 info
        maxconn 4096
        chroot /usr/share/haproxy
        uid 99
        gid 99
        daemon
        #debug
        #quiet

defaults
        log             global
        mode            http
        option          httplog
        option          dontlognull
        retries         3
        option          redispatch
        maxconn         500
        timeout connect 6s

frontend public
        mode    http
        bind    *:80
        timeout client  300s
        option  http-server-close
        #option         http-pretend-keepalive
        # define ACLs
        acl host_static hdr_beg(host) -i static. data.
        acl host_www hdr_beg(host) -i www.
        acl url_static path_end .ico .txt .pdf .png .jpg .css .js .csv
        acl is_stats path_beg /haproxy/stats
        # define rules
        use_backend nginx if host_static or host_www url_static
        use_backend stats if is_stats
        default_backend www

backend nginx
        timeout server 20s
        server nginx 127.0.0.1:8484

backend stats
        stats enable
        stats uri /haproxy/stats

backend www
        timeout server 300s
        option forwardfor
        #no option httpclose
        option http-server-close
        server sockcontent 127.0.0.1:8080

Кто-нибудь знает, почему это происходит? Это связано с какой-то конфигурацией haproxy или даже с некоторыми общими сетевыми настройками сервера (например, iptables)?

P.S. Я пытался включить http-pretend-keepalive в haproxy, но это не работает.


person ChrisG    schedule 23.10.2012    source источник


Ответы (3)


Скорее всего, у вас в сети прозрачный брандмауэр, и он портит соединения через веб-сокеты, идущие на порт 80.

Чтобы убедиться, что это так:

  1. Заставьте haproxy прослушивать другой порт и проверьте, заработало ли оно;
  2. Попробуйте получить доступ к вашему сервису за пределами вашей сети.

Если он начнет работать, значит, что-то не так с вашей сетью.

К сожалению, в этом случае SockJS не будет использовать резервный транспорт, так как клиент считает, что он подключен, но на самом деле соединение не установлено.

Как возможное решение: заставить haproxy прослушивать два порта: порт 80 для вашего веб-трафика и другой порт для трафика SockJS. Это гарантирует, что прозрачные прокси-серверы HTTP не будут мешать вашим соединениям через веб-сокеты.

person Joes    schedule 24.10.2012

Как сказал @Joes, это ошибка неправильного поведения брандмауэров. Некоторые говорят, что, например, FortiGate вызывает это.

Дополнительные обсуждения: https://github.com/sockjs/sockjs-client/issues/94< /а>

Обслуживание SockJS через SSL, кажется, решает проблему.

person Marek    schedule 30.10.2012

Я уже сталкивался с этим случаем, я не помню, какой это был сервер, но он не полностью соответствовал спецификации WebSocket. Действительно, не удалось, потому что он нашел токен «закрыть» в заголовке Connection вместе с токеном «Upgrade», в то время как спецификация говорит, что токен «Upgrade» нужен и (к счастью) не предлагает отклонять другие.

Теоретически, если вы используете «опцию http-pretend-keep-alive», как вы прокомментировали, она должна работать. По крайней мере, это было для меня. Но, возможно, здесь дело в другом.

person Willy Tarreau    schedule 23.10.2012