Error during WebSocket handshake

Сервер не ответил на запрос Upgrade корректным кодом 101. Что именно не так, сказано в конце сообщения в консоли.

Что это значит

WebSocket начинается с HTTP-запроса с заголовком Upgrade: websocket. Сервер должен ответить 101 Switching Protocols и прислать заголовки Upgrade, Connection и Sec-WebSocket-Accept (RFC 6455, пункт 4.2.2). Chrome пишет эту ошибку, когда ответ оказался любым другим.

DNS, порт и TLS в порядке. Если бы отказало что-то из них, в сообщении было бы Error in connection establishment.

JavaScript получает только событие close с кодом 1006 и пустой причиной. Что именно случилось, написано в консоли, в конце сообщения.

Как исправить

Читайте текст после Error during WebSocket handshake:.

Unexpected response code: NNN. Сервер ответил не кодом 101:

  • 200: обратный прокси потерял заголовок Upgrade, и бэкенд отдал обычную страницу. Либо путь неверный, и одностраничное приложение вернуло index.html.
  • 400, 426: обычно тот же потерянный заголовок, только бэкенд отклонил запрос. Socket.IO отвечает 400 ещё и при несовпадении версий клиента и сервера.
  • 403: сервер не принял заголовок Origin или учётные данные. Многие библиотеки по умолчанию пускают только с того же origin, что и сам сервер.
  • 404: неверный путь. /ws и /ws/ могут быть разными маршрутами.
  • 301, 302: редирект, чаще всего с ws:// на wss:// или на путь со слешем в конце. Браузер за ним не идёт. Указывайте конечный адрес.
  • 502, 503, 504: прокси работает, а приложение за ним не отвечает.

nginx по умолчанию не передаёт Upgrade бэкенду. При 200 и 400 сначала проверьте эти строки:

location /ws {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Чтобы прочитать ответ целиком, отправьте тот же запрос через curl:

curl -i -N --http1.1 --max-time 5 \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  https://example.com/ws

Не убирайте --http1.1. Без него curl выбирает HTTP/2 везде, где сервер его поддерживает. В HTTP/2 заголовка Upgrade нет, и сервер отвечает как на обычный запрос. Чтобы воспроизвести 403, добавьте -H "Origin: https://your-site.example": браузер отправляет Origin всегда, curl не отправляет.

Incorrect 'Sec-WebSocket-Accept' header value или header is missing. Значение считается так: SHA-1 от ключа с дописанным фиксированным GUID, затем base64. Библиотеки считают его верно, поэтому дело в самописном сервере. Чаще всего ключ декодируют из base64 перед хешированием, отправляют хеш в hex или опечатываются в GUID. Проверьте значение в калькуляторе Sec-WebSocket-Accept. Для ключа из команды curl правильный ответ равен s3pPLMBiTxaQ9kYGzzhZRbK+xOo=.

Сообщение про Upgrade, Connection или Sec-WebSocket-Protocol. Код 101, но с одним из заголовков что-то не так:

  • 'Upgrade' header is missing, 'Connection' header value must contain 'Upgrade': самописный сервер не прислал эти заголовки, или их убрал прокси без поддержки WebSocket.
  • Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received: вы вызвали new WebSocket(url, ['v1']), а сервер не выбрал подпротокол. Браузеры требуют, чтобы он вернул одно из предложенных имён. Выберите подпротокол на сервере или уберите второй аргумент.
  • Response must not include 'Sec-WebSocket-Protocol' header if not present in request: сервер вернул подпротокол, которого клиент не просил.

net::ERR_…. Соединение открылось, но ответ оборвался или оказался не HTTP. Проверьте порт в адресе и лог сервера.

Как увидеть это у себя

Найдите в списке запросов Upgrade к /ws и посмотрите код и заголовки ответа. Сравните Sec-WebSocket-Key в запросе с Sec-WebSocket-Accept в ответе. Если Upgrade и Connection в запросе есть, а в ответе 200, их потерял прокси или путь неверный. Как Solpuga записывает стрим: Перехват и просмотр трафика.

Посмотреть в Solpuga

Инструмент к этой странице

Калькулятор Sec-WebSocket-AcceptИз Sec-WebSocket-Key посчитать ожидаемый Sec-WebSocket-Accept и проверить, что сервер ответил правильно.

Рядом