Клавиша / esc

WebSocket

Устанавливает постоянное соединение с сервером для обмена данными в реальном времени.

Время чтения: 10 мин

Кратко

Скопировано

WebSocket — браузерный API для постоянного соединения с сервером. Клиент и сервер обмениваются данными в реальном времени, клиенту не нужно постоянно отправлять HTTP-запросы.

Открыть демо в новой вкладке

Как пишется

Скопировано
        
          
          const socket = new WebSocket(url, [protocols]?)
          const socket = new WebSocket(url, [protocols]?)

        
        
          
        
      
  • url — строка с адресом сервера. Поддерживаются схемы ws://, wss://, http://, https:// и query-параметры;
  • protocols — строка или массив строк с названиями предпочитаемых подпротоколов WebSocket, необязательный параметр. Какой подпротокол выбрал сервер, можно узнать из свойства protocol.

new WebSocket() возвращает экземпляр и сразу начинает подключение. У WebSocket нет статических методов и свойств, кроме статических констант.

Ошибки

Скопировано

Конструктор выбросит SyntaxError, когда:

  • url невалиден, схема не ws, wss, http или https, или содержит якорь (#);
  • в protocols пустая строка, недопустимый символ или дубликат имени.

Если подключиться не удалось, например при 404, отказе в upgrade или сбое TLS, конструктор ошибку не выбросит. Сначала сработает событие error, затем close, в котором флаг event.wasClean со значением false будет указывать на то, что соединение оборвалось.

Протоколы ws и wss

Скопировано

WebSocket использует два протокола:

  • ws:// — незащищённое соединение (как http://);
  • wss:// — защищённое соединение с шифрованием (как https://).

Браузер преобразует http:// в ws://, а https:// в wss://:

        
          
          const socket = new WebSocket("https://example.com/ws")console.log(socket.url)// wss://example.com/ws
          const socket = new WebSocket("https://example.com/ws")

console.log(socket.url)
// wss://example.com/ws

        
        
          
        
      

Итоговый URL хранится в свойстве url.

Методы

Скопировано

У экземпляра WebSocket всего два публичных метода: send() и close(). Оба вызываются синхронно. Браузер сразу выполняет действие и возвращает управление, но отправка данных и закрытие соединения на уровне сети происходят асинхронно.

send()

Скопировано

Отправляет данные на сервер. Принимает один аргумент — данные, которые нужно отправить:

        
          
          socket.send('Привет, сервер!')
          socket.send('Привет, сервер!')

        
        
          
        
      

Можно отправлять разные типы данных:

        
          
          const socket = new WebSocket('wss://example.com/ws')const buffer = new ArrayBuffer(4)const view = new Uint8Array(buffer)view.set([1, 2, 3, 4])const blob = new Blob([buffer], { type: 'application/octet-stream' })if (socket.readyState === WebSocket.OPEN) {  // Отправляем строку  socket.send('Сообщение')  // Отправляем JSON  socket.send(JSON.stringify({ type: 'message', text: 'Привет' }))  // Отправляем бинарные данные  socket.send(buffer)  // Отправляем Blob  socket.send(blob)}
          const socket = new WebSocket('wss://example.com/ws')

const buffer = new ArrayBuffer(4)
const view = new Uint8Array(buffer)
view.set([1, 2, 3, 4])

const blob = new Blob([buffer], { type: 'application/octet-stream' })

if (socket.readyState === WebSocket.OPEN) {
  // Отправляем строку
  socket.send('Сообщение')

  // Отправляем JSON
  socket.send(JSON.stringify({ type: 'message', text: 'Привет' }))

  // Отправляем бинарные данные
  socket.send(buffer)

  // Отправляем Blob
  socket.send(blob)
}

        
        
          
        
      

Если readyState равен WebSocket.CONNECTING, вызов send() выбросит InvalidStateError, потому что соединение ещё устанавливается. В состояниях WebSocket.CLOSING и WebSocket.CLOSED при вызове send() данные отбрасываются без ошибки.

Ещё у метода есть особенность: он складывает данные во внутреннюю очередь. Если отправляете много сообщений подряд, они уйдут не сразу, но порядок сохранится. Проверить размер очереди можно через свойство bufferedAmount.

close()

Скопировано

Закрывает соединение с сервером. Принимает два необязательных аргумента:

        
          
          socket.close(code?, reason?)
          socket.close(code?, reason?)

        
        
          
        
      
  • code — числовой код закрытия. Допустимы 1000 и значения от 3000 до 4999;
  • reason — строка с причиной закрытия, не длиннее 123 байт в UTF-8.

Метод выбросит:

  • InvalidAccessError, если code недопустим;
  • SyntaxError, если reason длиннее 123 байт в UTF-8.
        
          
          // Простое закрытиеsocket.close()// Закрытие с кодом и причинойsocket.close(1000, 'Работа завершена')// Пользовательский кодsocket.close(4001, 'Неверный формат сообщения')
          // Простое закрытие
socket.close()

// Закрытие с кодом и причиной
socket.close(1000, 'Работа завершена')

// Пользовательский код
socket.close(4001, 'Неверный формат сообщения')

        
        
          
        
      

Как и send(), метод close() вызывается синхронно. Сразу после вызова readyState становится WebSocket.CLOSING. Браузер начинает закрывающее рукопожатие с сервером, но соединение ещё не закрыто полностью.

Когда обмен завершится, состояние сменится на WebSocket.CLOSED и сработает событие close.

Если readyState уже WebSocket.CLOSING или WebSocket.CLOSED, повторный вызов close() ничего не сделает.

Память после закрытия

Скопировано

close() закрывает соединение, но не удаляет объект WebSocket из памяти. Пока на объект есть ссылка, он живёт вместе со всеми свойствами и обработчиками. Обработчики нередко замыкают DOM-элементы и другие объекты.

Если socket объявлен внутри функции и никуда не «утекает» через замыкание, после выхода из функции сборщик мусора удалит объект сам. Если же ссылка хранится долго, например в глобальной переменной, в состоянии приложения или как поле объекта, после close() её лучше обнулить:

        
          
          let socket = new WebSocket('wss://example.com/ws')// ...socket.close()socket = null
          let socket = new WebSocket('wss://example.com/ws')
// ...
socket.close()
socket = null

        
        
          
        
      

Слушатели событий

Скопировано

WebSocket наследует EventTarget, поэтому на события можно подписаться двумя способами:

  • через on-свойства — onopen, onmessage, onerror, onclose;
  • через addEventListener().

У экземпляра есть четыре события:

  • open — соединение установлено, readyState равен WebSocket.OPEN;
  • message — пришли данные от сервера. Текст приходит строкой или бинарными данными в формате, заданном binaryType;
  • error — ошибка соединения. Событие не содержит деталей, код и причину смотрите в close;
  • close — соединение закрыто. event.code и event.reason содержат код и причину. Флаг event.wasClean равен true, если закрытие штатное.

on-свойства

Скопировано

Чтобы подписаться на событие, запишите функцию в соответствующее свойство.

        
          
          const socket = new WebSocket('wss://example.com/ws')socket.onopen = () => {  console.log('Соединение открыто')}socket.onmessage = (event) => {  console.log('Сообщение:', event.data)}socket.onerror = () => {  console.log('Ошибка соединения')}socket.onclose = (event) => {  const { code, reason, wasClean } = event  console.log(`Закрыто: ${code}, ${reason}, wasClean: ${wasClean}`)}
          const socket = new WebSocket('wss://example.com/ws')

socket.onopen = () => {
  console.log('Соединение открыто')
}

socket.onmessage = (event) => {
  console.log('Сообщение:', event.data)
}

socket.onerror = () => {
  console.log('Ошибка соединения')
}

socket.onclose = (event) => {
  const { code, reason, wasClean } = event
  console.log(`Закрыто: ${code}, ${reason}, wasClean: ${wasClean}`)
}

        
        
          
        
      

На каждое событие одна функция. Новое присваивание заменяет предыдущий обработчик. Чтобы снять обработчик, присвойте свойству null, например socket.onmessage = null.

Коды в событии close

Скопировано

В event.code приходит код закрытия. Бывают стандартные (10001015) и пользовательские (30004999, те же, что можно передать в close()).

Из стандартных в close() можно передать только 1000. Остальные коды из диапазона 10001015 попадают в event.code, когда их присылает сервер, а 1005, 1006 и 1015 браузер выставляет сам.

  • 1000 — нормальное закрытие;
  • 1001 — сторона закрывает соединение, потому что уходит (закрыли вкладку, ушли со страницы, сервер упал);
  • 1002 — ошибка протокола;
  • 1003 — получены неподдерживаемые данные;
  • 1004 — зарезервирован, не используется;
  • 1005 — код не был указан (например, close() без аргументов);
  • 1006 — соединение оборвалось до завершения закрытия;
  • 1007 — неверные данные во фрейме;
  • 1008 — нарушение политики;
  • 1009 — сообщение слишком большое;
  • 1010 — клиент ожидал расширение, которое сервер не поддерживает;
  • 1011 — внутренняя ошибка сервера;
  • 1012 — сервер перезапускается;
  • 1013 — временная перегрузка, попробуйте подключиться позже;
  • 1014 — сервер выступал прокси и получил неверный ответ от upstream-сервера (аналог HTTP 502);
  • 1015 — не удалось установить TLS-соединение.

Полный список кодов закрытия смотрите в реестре IANA.

Свойства

Скопировано

У экземпляра WebSocket шесть свойств. Пять из них только для чтения и описывают состояние соединения и параметры, согласованные с сервером при рукопожатии. binaryType можно менять: оно задаёт формат входящих бинарных сообщений.

readyState

Скопировано

Текущее состояние соединения. Возвращает число от 0 до 3. Для сравнения используйте статические константы.

url

Скопировано

Строка с итоговым URL соединения после нормализации браузером. Может отличаться от аргумента конструктора. Подробнее в разделе «Протоколы ws и wss».

protocol

Скопировано

Подпротокол, который выбрал сервер при рукопожатии. Пустая строка, если подпротокол не согласовывали или сервер ничего не выбрал.

Значение доступно после того, как соединение будет открыто:

        
          
          const socket = new WebSocket('wss://example.com/ws', ['soap', 'wamp'])socket.onopen = () => {  console.log(socket.protocol) // 'soap' или 'wamp'}
          const socket = new WebSocket('wss://example.com/ws', ['soap', 'wamp'])

socket.onopen = () => {
  console.log(socket.protocol) // 'soap' или 'wamp'
}

        
        
          
        
      

Список допустимых имён подпротоколов смотрите в реестре IANA.

extensions

Скопировано

Строка с расширениями протокола, которые выбрал сервер (например, сжатие permessage-deflate). Если расширений нет, вернётся пустая строка.

Список зарегистрированных расширений смотрите в реестре IANA.

Значение доступно после того, как соединение будет открыто:

        
          
          socket.onopen = () => {  console.log(socket.extensions) // например, 'permessage-deflate'}
          socket.onopen = () => {
  console.log(socket.extensions) // например, 'permessage-deflate'
}

        
        
          
        
      

binaryType

Скопировано

Определяет, в каком виде бинарные сообщения попадут в event.data у события message. На текстовые сообщения не влияет: они всегда приходят как строки.

Допустимые значения:

  • 'blob' (по умолчанию) — бинарные данные приходят объектом Blob;
  • 'arraybuffer' — бинарные данные приходят ArrayBuffer.

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

        
          
          const socket = new WebSocket('wss://example.com/ws')socket.binaryType = 'arraybuffer'socket.onmessage = (event) => {  if (event.data instanceof ArrayBuffer) {    const view = new Uint8Array(event.data)    console.log(view)  } else {    console.log(event.data) // строка  }}
          const socket = new WebSocket('wss://example.com/ws')

socket.binaryType = 'arraybuffer'

socket.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    const view = new Uint8Array(event.data)
    console.log(view)
  } else {
    console.log(event.data) // строка
  }
}

        
        
          
        
      

'arraybuffer' удобен, когда нужно сразу работать с байтами через TypedArray. 'blob' подходит, если данные удобнее обрабатывать как файл, например создать object URL или прочитать через FileReader.

bufferedAmount

Скопировано

Количество байт данных, поставленных в очередь через send(), но ещё не отправленных по сети.

Если отправлять сообщения быстрее, чем браузер успевает передать их по сети, очередь может переполниться. В этом случае браузер закроет соединение, однако метод send() не вызовет ошибку.

Лимит очереди браузер не сообщает. Имеет смысл следить за bufferedAmount, чтобы очередь не переполнилась.

        
          
          socket.send('Первое сообщение')socket.send('Второе сообщение')console.log(socket.bufferedAmount) // размер очереди в байтах
          socket.send('Первое сообщение')
socket.send('Второе сообщение')

console.log(socket.bufferedAmount) // размер очереди в байтах

        
        
          
        
      

После закрытия соединения bufferedAmount не обнуляется.

В состояниях WebSocket.CLOSING и WebSocket.CLOSED при вызове send() данные отбрасываются, метод не выбросит исключение, но bufferedAmount при каждом таком вызове всё равно увеличивается.

Статические константы

Скопировано

Для сравнения с readyState:

  • WebSocket.CONNECTING (0) — идёт подключение;
  • WebSocket.OPEN (1) — соединение открыто, можно вызывать метод send();
  • WebSocket.CLOSING (2) — идёт закрытие после вызова метода close();
  • WebSocket.CLOSED (3) — соединение закрыто.

Заголовки рукопожатия

Скопировано

Подключение WebSocket начинается как обычный HTTP-запрос. Браузер просит сервер «переключить протокол». Если всё прошло успешно, дальше общение идёт уже по WebSocket, а не по HTTP.

Все заголовки браузер формирует сам при вызове new WebSocket(). Из JavaScript их нельзя задать или изменить, в отличие от fetch().

Запрос клиента

Скопировано

Пример запроса на установку соединения:

        
          
          GET /ws HTTP/1.1Host: example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Sec-WebSocket-Version: 13Sec-WebSocket-Protocol: soap, wampSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
          GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: soap, wamp
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

        
        
          
        
      

Основные заголовки:

  • Upgrade: websocket и Connection: Upgrade — просьба перейти с HTTP на WebSocket;
  • Sec-WebSocket-Key — случайное значение для проверки, что сервер действительно поддерживает WebSocket;
  • Sec-WebSocket-Version: 13 — версия протокола, в браузерах всегда 13;
  • Sec-WebSocket-Protocol — список подпротоколов из второго аргумента конструктора;
  • Sec-WebSocket-Extensions — расширения, которые предлагает браузер (например, сжатие).

Браузер также отправляет служебные заголовки вроде Origin и cookie для домена, как при обычном HTTP-запросе.

Ответ сервера

Скопировано

Если сервер согласен, он отвечает кодом 101 Switching Protocols:

        
          
          HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: soapSec-WebSocket-Extensions: permessage-deflate
          HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: soap
Sec-WebSocket-Extensions: permessage-deflate

        
        
          
        
      
  • Sec-WebSocket-Accept — ответ на Sec-WebSocket-Key, браузер проверяет его автоматически. Если значение неверное, соединение не откроется;
  • Sec-WebSocket-Protocol — один подпротокол из списка клиента, который выбрал сервер;
  • Sec-WebSocket-Extensions — расширения, которые сервер принял.
Поддержка в браузерах:
  • Chrome 18, поддерживается
  • Edge 12, поддерживается
  • Firefox 11, поддерживается
  • Safari 6, поддерживается
О Baseline