Кратко
СкопированоWeb — браузерный API для постоянного соединения с сервером. Клиент и сервер обмениваются данными в реальном времени, клиенту не нужно постоянно отправлять HTTP-запросы.
Как пишется
Скопировано
const socket = new WebSocket(url, [protocols]?)
const socket = new WebSocket(url, [protocols]?)
url— строка с адресом сервера. Поддерживаются схемыws,: / / wss,: / / http,: / / httpsи query-параметры;: / / protocols— строка или массив строк с названиями предпочитаемых подпротоколов WebSocket, необязательный параметр. Какой подпротокол выбрал сервер, можно узнать из свойстваprotocol.
new возвращает экземпляр и сразу начинает подключение. У Web нет статических методов и свойств, кроме статических констант.
Ошибки
СкопированоКонструктор выбросит Syntax, когда:
urlневалиден, схема неws,wss,httpилиhttps, или содержит якорь (#);- в
protocolsпустая строка, недопустимый символ или дубликат имени.
Если подключиться не удалось, например при 404, отказе в upgrade или сбое TLS, конструктор ошибку не выбросит. Сначала сработает событие error, затем close, в котором флаг event со значением 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.
Методы
СкопированоУ экземпляра Web всего два публичных метода: send и close. Оба вызываются синхронно. Браузер сразу выполняет действие и возвращает управление, но отправка данных и закрытие соединения на уровне сети происходят асинхронно.
send()
СкопированоОтправляет данные на сервер. Принимает один аргумент — данные, которые нужно отправить:
socket.send('Привет, сервер!')
socket.send('Привет, сервер!')
Можно отправлять разные типы данных:
- строки (
String); - бинарные данные —
Array,Buffer Typed,Array Data;View 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)}
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)
}
Если ready равен , вызов send выбросит InvalidState, потому что соединение ещё устанавливается. В состояниях и при вызове send данные отбрасываются без ошибки.
Ещё у метода есть особенность: он складывает данные во внутреннюю очередь. Если отправляете много сообщений подряд, они уйдут не сразу, но порядок сохранится. Проверить размер очереди можно через свойство buffered.
close()
СкопированоЗакрывает соединение с сервером. Принимает два необязательных аргумента:
socket.close(code?, reason?)
socket.close(code?, reason?)
code— числовой код закрытия. Допустимы1000и значения от3000до4999;reason— строка с причиной закрытия, не длиннее 123 байт в UTF-8.
Метод выбросит:
InvalidAccess, еслиError codeнедопустим;Syntax, еслиError reasonдлиннее 123 байт в UTF-8.
// Простое закрытиеsocket.close()// Закрытие с кодом и причинойsocket.close(1000, 'Работа завершена')// Пользовательский кодsocket.close(4001, 'Неверный формат сообщения')
// Простое закрытие
socket.close()
// Закрытие с кодом и причиной
socket.close(1000, 'Работа завершена')
// Пользовательский код
socket.close(4001, 'Неверный формат сообщения')
Как и send, метод close вызывается синхронно. Сразу после вызова ready становится . Браузер начинает закрывающее рукопожатие с сервером, но соединение ещё не закрыто полностью.
Когда обмен завершится, состояние сменится на и сработает событие close.
Если ready уже или , повторный вызов close ничего не сделает.
Память после закрытия
Скопированоclose закрывает соединение, но не удаляет объект Web из памяти. Пока на объект есть ссылка, он живёт вместе со всеми свойствами и обработчиками. Обработчики нередко замыкают 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
Слушатели событий
СкопированоWeb наследует Event, поэтому на события можно подписаться двумя способами:
- через
on-свойства —onopen,onmessage,onerror,onclose; - через
add.Event Listener ( )
У экземпляра есть четыре события:
open— соединение установлено,readyравенState ;Web Socket . OPEN message— пришли данные от сервера. Текст приходит строкой или бинарными данными в формате, заданномbinary;Type error— ошибка соединения. Событие не содержит деталей, код и причину смотрите вclose;close— соединение закрыто.eventи. code eventсодержат код и причину. Флаг. reason eventравен. was Clean 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.
Коды в событии close
СкопированоВ event приходит код закрытия. Бывают стандартные (1000–1015) и пользовательские (3000–4999, те же, что можно передать в close).
Из стандартных в close можно передать только 1000. Остальные коды из диапазона 1000–1015 попадают в event, когда их присылает сервер, а 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.
Свойства
СкопированоУ экземпляра Web шесть свойств. Пять из них только для чтения и описывают состояние соединения и параметры, согласованные с сервером при рукопожатии. binary можно менять: оно задаёт формат входящих бинарных сообщений.
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). Если расширений нет, вернётся пустая строка.
Список зарегистрированных расширений смотрите в реестре IANA.
Значение доступно после того, как соединение будет открыто:
socket.onopen = () => { console.log(socket.extensions) // например, 'permessage-deflate'}
socket.onopen = () => {
console.log(socket.extensions) // например, 'permessage-deflate'
}
binaryType
СкопированоОпределяет, в каком виде бинарные сообщения попадут в event у события message. На текстовые сообщения не влияет: они всегда приходят как строки.
Допустимые значения:
'blob'(по умолчанию) — бинарные данные приходят объектомBlob;'arraybuffer'— бинарные данные приходятArray.Buffer
Свойство можно менять в любой момент, в том числе после открытия соединения. Новое значение применится к следующим входящим сообщениям.
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' удобен, когда нужно сразу работать с байтами через Typed. 'blob' подходит, если данные удобнее обрабатывать как файл, например создать object URL или прочитать через File.
bufferedAmount
СкопированоКоличество байт данных, поставленных в очередь через send, но ещё не отправленных по сети.
Если отправлять сообщения быстрее, чем браузер успевает передать их по сети, очередь может переполниться. В этом случае браузер закроет соединение, однако метод send не вызовет ошибку.
Лимит очереди браузер не сообщает. Имеет смысл следить за buffered, чтобы очередь не переполнилась.
socket.send('Первое сообщение')socket.send('Второе сообщение')console.log(socket.bufferedAmount) // размер очереди в байтах
socket.send('Первое сообщение')
socket.send('Второе сообщение')
console.log(socket.bufferedAmount) // размер очереди в байтах
После закрытия соединения buffered не обнуляется.
В состояниях и при вызове send данные отбрасываются, метод не выбросит исключение, но buffered при каждом таком вызове всё равно увеличивается.
Статические константы
СкопированоДля сравнения с ready:
(Web Socket . CONNECTING 0) — идёт подключение;(Web Socket . OPEN 1) — соединение открыто, можно вызывать методsend;( ) (Web Socket . CLOSING 2) — идёт закрытие после вызова методаclose;( ) (Web Socket . CLOSED 3) — соединение закрыто.
Заголовки рукопожатия
СкопированоПодключение WebSocket начинается как обычный HTTP-запрос. Браузер просит сервер «переключить протокол». Если всё прошло успешно, дальше общение идёт уже по WebSocket, а не по HTTP.
Все заголовки браузер формирует сам при вызове new . Из 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— просьба перейти с HTTP на WebSocket;: Upgrade Sec— случайное значение для проверки, что сервер действительно поддерживает WebSocket;- WebSocket - Key Sec— версия протокола, в браузерах всегда- WebSocket - Version : 13 13;Sec— список подпротоколов из второго аргумента конструктора;- WebSocket - Protocol Sec— расширения, которые предлагает браузер (например, сжатие).- WebSocket - Extensions
Браузер также отправляет служебные заголовки вроде Origin и cookie для домена, как при обычном HTTP-запросе.
Ответ сервера
СкопированоЕсли сервер согласен, он отвечает кодом 101 :
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, поддерживается