Кратко
СкопированоКонструктор Web — это браузерный API для работы с протоколом WebSocket. В отличие от HTTP-запросов, соединение остаётся открытым, и обе стороны могут обмениваться данными в любой момент. Сервер сам отправляет обновления, как только они появляются. Повторные запросы не нужны.
Как пишется
Скопировано
new WebSocket(url, [protocols]?)
new WebSocket(url, [protocols]?)
url: строка с адресом сервера, должна начинаться сwsили: / / wss;: / / protocols— строка или массив строк с названиями предпочитаемых подпротоколов WebSocket, необязательный параметр. Какой подпротокол выбрал сервер, можно узнать из свойстваprotocol.
Протоколы ws и wss
СкопированоWebSocket использует два протокола:
ws: незащищённое соединение (как: / / http);: / / wss: защищённое соединение с шифрованием (как: / / https).: / /
Браузер автоматически преобразует http в ws и https в wss, если передать их вместо WebSocket-протоколов:
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)
}
Ещё у метода есть особенность: он складывает данные во внутреннюю очередь. Если отправляете много сообщений подряд, они не обязательно сразу улетят на сервер в том же порядке. Проверить размер очереди можно через свойство buffered:
close()
СкопированоЗакрывает соединение с сервером. Принимает два необязательных аргумента:
socket.close(code?, reason?)
socket.close(code?, reason?)
code: числовой код закрытия (например,1000для нормального закрытия), по умолчанию1005;reason: строка с причиной закрытия, не длиннее 123 байт в UTF-8.
// Простое закрытиеsocket.close()// Закрытие с кодом и причинойsocket.close(1000, 'Работа завершена')// Пользовательский код (допустимы значения от 3000 до 4999)socket.close(4001, 'Неверный формат сообщения')
// Простое закрытие
socket.close()
// Закрытие с кодом и причиной
socket.close(1000, 'Работа завершена')
// Пользовательский код (допустимы значения от 3000 до 4999)
socket.close(4001, 'Неверный формат сообщения')
Как и send, метод close вызывается синхронно. Сразу после вызова ready становится : браузер начинает закрывающее рукопожатие с сервером. На этом этапе соединение ещё не закрыто полностью, поэтому ready не равен . Когда обмен завершится, состояние сменится на и сработает событие close.
socket.close()console.log(socket.readyState) // WebSocket.CLOSING (2), а не CLOSED (3)
socket.close()
console.log(socket.readyState) // WebSocket.CLOSING (2), а не CLOSED (3)
Коды от 1000 до 1015 зарезервированы спецификацией:
1000: нормальное закрытие;1001: сервер или клиент уходит (например, закрыли вкладку);1002: ошибка протокола;1003: получены неподдерживаемые данные;1004: зарезервирован, не используется;1005: код не был передан, нельзя указывать вclose;( ) 1006: соединение оборвалось без фрейма закрытия, нельзя указывать вclose;( ) 1007: неверные данные во фрейме;1008: нарушение политики;1009: сообщение слишком большое;1010: клиент ожидал расширение, которое сервер не поддерживает;1011: внутренняя ошибка сервера;1012: сервер перезапускается;1013: временная перегрузка, попробуйте подключиться позже;1014: сервер выступал прокси и получил неверный ответ от upstream-сервера (аналог HTTP 502);1015: ошибка TLS-рукопожатия, нельзя указывать вclose.( )
Полный список кодов закрытия смотрите в реестре IANA.
Слушатели событий
СкопированоWeb наследует Event, поэтому на события можно подписаться двумя способами:
- через
on-свойства:onopen,onmessage,onerror,onclose; - через
add.Event Listener ( )
У Web четыре события:
open: соединение установлено,readyравенState ;Web Socket . OPEN message: пришли данные от сервера. Текст приходит строкой или бинарными данными в формате, заданномbinary;Type error: ошибка соединения;close: соединение закрыто, код и причина доступны вeventи. code event.. reason
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) => { console.log(`Закрыто: ${event.code}, ${event.reason}`)}
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) => {
console.log(`Закрыто: ${event.code}, ${event.reason}`)
}
Плюсы:
- короткая запись, удобно для простых случаев;
- легко отписаться:
socket.. onmessage = null
Минусы:
- на каждое событие можно повесить только одну функцию, новое присваивание заменяет предыдущий обработчик;
- нельзя добавить второй обработчик на то же событие;
- нет опций вроде
{ onceили: true } { signal }.
addEventListener()
СкопированоWeb поддерживает подписку через add:
const socket = new WebSocket('wss://example.com/ws')function onMessage(event) { console.log('Сообщение:', event.data)}socket.addEventListener('open', () => { console.log('Соединение открыто')})socket.addEventListener('message', onMessage)socket.addEventListener('close', () => { socket.removeEventListener('message', onMessage)})
const socket = new WebSocket('wss://example.com/ws')
function onMessage(event) {
console.log('Сообщение:', event.data)
}
socket.addEventListener('open', () => {
console.log('Соединение открыто')
})
socket.addEventListener('message', onMessage)
socket.addEventListener('close', () => {
socket.removeEventListener('message', onMessage)
})
Плюсы:
- несколько обработчиков на одно событие;
- можно снять конкретный обработчик через
remove;Event Listener ( ) - поддерживаются опции:
{ once,: true } { signal }изAbort.Controller
Минусы:
- запись длиннее;
- чтобы отписаться, нужно сохранить ту же ссылку на функцию, что передавали в
add.Event Listener ( )
Память после закрытия
Скопированоclose закрывает соединение, но не удаляет объект Web из памяти. Пока на него указывает переменная, объект живёт вместе со всеми свойствами и обработчиками.
Обработчики событий усугубляют проблему: они часто замыкают DOM-элементы и другие объекты. Пока жива ссылка на сокет, эта цепочка остаётся в памяти.
Это особенно важно для глобальных переменных и свойств объектов, которые живут долго. Если socket объявлен внутри функции и никуда не «утекает» через замыкание, после выхода из функции GC удалит объект сам, даже без 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
Обнулять onopen, onmessage и остальные on-свойства по отдельности не нужно: вместе с объектом из памяти исчезнут и его обработчики.
remove понадобится только если объект сокета остаётся в памяти, а конкретный обработчик нужно снять.
Свойства
СкопированоУ Web шесть свойств. Пять из них только для чтения и описывают состояние соединения и параметры, согласованные с сервером при рукопожатии. binary можно менять: оно задаёт формат входящих бинарных сообщений.
readyState
СкопированоТекущее состояние соединения. Возвращает число от 0 до 3. Для сравнения используйте константы на конструкторе Web:
(Web Socket . CONNECTING 0): идёт подключение;(Web Socket . OPEN 1): соединение открыто, можно вызыватьsend;( ) (Web Socket . CLOSING 2): идёт закрытие послеclose;( ) (Web Socket . CLOSED 3): соединение закрыто.
const socket = new WebSocket('wss://example.com/ws')console.log(socket.readyState) // WebSocket.CONNECTINGsocket.onopen = () => { console.log(socket.readyState) // WebSocket.OPEN}
const socket = new WebSocket('wss://example.com/ws')
console.log(socket.readyState) // WebSocket.CONNECTING
socket.onopen = () => {
console.log(socket.readyState) // WebSocket.OPEN
}
url
СкопированоСтрока с итоговым URL соединения после нормализации браузером. Может отличаться от аргумента конструктора. Подробнее в разделе «Протоколы ws и wss».
protocol
СкопированоПодпротокол, который выбрал сервер при рукопожатии. Пустая строка, если подпротокол не согласовывали или сервер ничего не выбрал.
Значение доступно после события open:
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. Значение доступно после события open:
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, но ещё не отправленных по сети. Удобно следить за перегрузкой при частой отправке сообщений.
socket.send('Первое сообщение')socket.send('Второе сообщение')console.log(socket.bufferedAmount) // размер очереди в байтах
socket.send('Первое сообщение')
socket.send('Второе сообщение')
console.log(socket.bufferedAmount) // размер очереди в байтах
После закрытия соединения значение может только расти: новые вызовы send выбросят ошибку, но уже поставленные в очередь данные всё ещё учитываются. См. также close и ready.
Заголовки рукопожатия
СкопированоПодключение 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, поддерживается