INTEGRITY Документация

Использование WebSockets API

WebSockets позволяют обмениваться данными в режиме реального времени с бессерверными функциями Cloudflare Workers. В этом руководстве вы изучите основы работы с WebSockets в Cloudflare Workers: как писать серверы WebSocket в функциях Workers, а также как подключаться к таким серверам и работать с ними в качестве клиента.

WebSockets представляют собой открытые соединения, которые поддерживаются между клиентом и исходным сервером. В рамках соединения WebSocket клиент и источник могут обмениваться данными в обе стороны без повторного установления сеанса. Благодаря этому обмен данными в соединении WebSocket происходит быстро. WebSockets часто используются в приложениях реального времени, например в чатах и играх.

Напишите сервер WebSocket

Серверы WebSocket в Cloudflare Workers позволяют получать сообщения от клиента в режиме реального времени. В этом руководстве вы узнаете, как настроить сервер WebSocket в Workers.

Клиент может выполнить запрос WebSocket в браузере, создав новый экземпляр WebSocket, передав URL вашей функции Workers:

// In client-side JavaScript, connect to your Workers function using WebSockets:
const websocket = new WebSocket(
	"wss://example-websocket.signalnerve.workers.dev",
);

Когда входящий WebSocket-запрос достигает функции Workers, он содержит Upgrade заголовок со строковым значением websocket. Перед созданием WebSocket проверьте наличие этого заголовка:

async function handleRequest(request) {
  const upgradeHeader = request.headers.get('Upgrade');
  if (!upgradeHeader || upgradeHeader !== 'websocket') {
    return new Response('Expected Upgrade: websocket', { status: 426 });
  }
}
use worker::*;

#[event(fetch)]
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<worker::Response> {
    let upgrade_header = match req.headers().get("Upgrade") {
        Some(h) => h.to_str().unwrap(),
        None => "",
    };
    if upgrade_header != "websocket" {
        return worker::Response::error("Expected Upgrade: websocket", 426);
    }
}

После того как вы должным образом проверили наличие Upgrade заголовок, вы можете создать новый экземпляр WebSocketPair, который содержит серверный и клиентский WebSocket. Один из них должна обрабатывать функция Workers, а другой нужно вернуть как часть Response на 101 код состояния, указывающий, что запрос переключает протоколы:

async function handleRequest(request) {
  const upgradeHeader = request.headers.get('Upgrade');
  if (!upgradeHeader || upgradeHeader !== 'websocket') {
    return new Response('Expected Upgrade: websocket', { status: 426 });
  }

  const webSocketPair = new WebSocketPair();
  const client = webSocketPair[0],
    server = webSocketPair[1];

  return new Response(null, {
    status: 101,
    webSocket: client,
  });
}
use worker::*;

#[event(fetch)]
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<worker::Response> {
    let upgrade_header = match req.headers().get("Upgrade") {
        Some(h) => h.to_str().unwrap(),
        None => "",
    };
    if upgrade_header != "websocket" {
        return worker::Response::error("Expected Upgrade: websocket", 426);
    }

    let ws = WebSocketPair::new()?;
    let client = ws.client;
    let server = ws.server;
    server.accept()?;

    worker::Response::from_websocket(client)

}

WebSocketPair конструктор возвращает Object, у которого 0 и 1 ключей, каждый из которых содержит WebSocket экземпляр в качестве значения. Обычно оба WebSocket из этой пары получают с помощью Object.values и деструктуризация ES6, как показано в примере ниже.

Чтобы начать взаимодействие с client WebSocket в вашем Worker вызовите accept на server WebSocket. Это укажет среде выполнения Workers, что нужно прослушивать данные WebSocket и удерживать соединение открытым с вашим client WebSocket:

async function handleRequest(request) {
  const upgradeHeader = request.headers.get('Upgrade');
  if (!upgradeHeader || upgradeHeader !== 'websocket') {
    return new Response('Expected Upgrade: websocket', { status: 426 });
  }

  const webSocketPair = new WebSocketPair();
  const [client, server] = Object.values(webSocketPair);

  server.accept();

  return new Response(null, {
    status: 101,
    webSocket: client,
  });
}
use worker::*;

#[event(fetch)]
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<worker::Response> {
    let upgrade_header = match req.headers().get("Upgrade") {
        Some(h) => h.to_str().unwrap(),
        None => "",
    };
    if upgrade_header != "websocket" {
        return worker::Response::error("Expected Upgrade: websocket", 426);
    }

    let ws = WebSocketPair::new()?;
    let client = ws.client;
    let server = ws.server;
    server.accept()?;

    worker::Response::from_websocket(client)

}

WebSockets генерируют ряд События к которой можно подключиться с помощью addEventListener. Пример ниже подключается к message событие и генерирует console.log данными из него:

async function handleRequest(request) {
  const upgradeHeader = request.headers.get('Upgrade');
  if (!upgradeHeader || upgradeHeader !== 'websocket') {
    return new Response('Expected Upgrade: websocket', { status: 426 });
  }

  const webSocketPair = new WebSocketPair();
  const [client, server] = Object.values(webSocketPair);

  server.accept();
  server.addEventListener('message', event => {
    console.log(event.data);
  });

  return new Response(null, {
    status: 101,
    webSocket: client,
  });
}
use futures::StreamExt;
use worker::*;

#[event(fetch)]
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<worker::Response> {
    let upgrade_header = match req.headers().get("Upgrade") {
        Some(h) => h.to_str().unwrap(),
        None => "",
    };
    if upgrade_header != "websocket" {
        return worker::Response::error("Expected Upgrade: websocket", 426);
    }

    let ws = WebSocketPair::new()?;
    let client = ws.client;
    let server = ws.server;
    server.accept()?;

    wasm_bindgen_futures::spawn_local(async move {
        let mut event_stream = server.events().expect("could not open stream");
        while let Some(event) = event_stream.next().await {
            match event.expect("received error in websocket") {
                WebsocketEvent::Message(msg) => server.send(&msg.text()).unwrap(),
                WebsocketEvent::Close(event) => console_log!("{:?}", event),
            }
        }
    });
    worker::Response::from_websocket(client)

}
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'

const app = new Hono()

app.get(
  '*',
  upgradeWebSocket((c) => {
    return {
      onMessage(event, ws) {
        console.log('Received message from client:', event.data)
        ws.send(`Echo: ${event.data}`)
      },
      onClose: () => {
        console.log('WebSocket closed:', event)
      },
      onError: () => {
        console.error('WebSocket error:', event)
      },
    }
  })
)

export default app;

Подключение к серверу WebSocket с клиента

Написание клиентов WebSocket, которые взаимодействуют с вашей функцией Workers, состоит из двух шагов: сначала вы создаёте экземпляр WebSocket, а затем добавляете к нему обработчики событий:

const websocket = new WebSocket(
	"wss://websocket-example.signalnerve.workers.dev",
);
websocket.addEventListener("message", (event) => {
	console.log("Message received from server");
	console.log(event.data);
});

Клиенты WebSocket могут отправлять сообщения обратно на сервер, используя send функция:

websocket.send("MESSAGE");

Когда взаимодействие по WebSocket завершено, клиент может закрыть соединение с помощью close:

websocket.close();

Пример на практике см. в websocket-template чтобы начать работу с WebSockets.

Напишите клиент WebSocket

Cloudflare Workers поддерживает new WebSocket(url) конструктор. Worker может устанавливать WebSocket-соединение с удалённым сервером так же, как это описано выше для клиентской реализации.

Кроме того, Cloudflare поддерживает установку WebSocket-соединений через fetch-запрос к URL с Upgrade заголовок установлен.

async function websocket(url) {
	// Make a fetch request including `Upgrade: websocket` header.
	// The Workers Runtime will automatically handle other requirements
	// of the WebSocket protocol, like the Sec-WebSocket-Key header.
	let resp = await fetch(url, {
		headers: {
			Upgrade: "websocket",
		},
	});

	// If the WebSocket handshake completed successfully, then the
	// response has a `webSocket` property.
	let ws = resp.webSocket;
	if (!ws) {
		throw new Error("server didn't accept WebSocket");
	}

	// Call accept() to indicate that you'll be handling the socket here
	// in JavaScript, as opposed to returning it on to a client.
	// You can pass { allowHalfOpen: true } if you need to coordinate
	// the close handshake manually (for example, when proxying).
	ws.accept();

	// Now you can send and receive messages like before.
	ws.send("hello");
	ws.addEventListener("message", (msg) => {
		console.log(msg.data);
	});
}

Поведение при закрытии WebSocket

С web_socket_auto_reply_to_close флаг совместимости (включён по умолчанию при дате совместимости от 2026-04-07), среда выполнения Workers автоматически отвечает на входящие кадры Close и переводит readyState к CLOSED перед вызовом события close событие. Вам не нужно вызывать close() в вашем close обработчик события, но это безопасно (вызов молча игнорируется).

Если вам нужно поведение half-open (например, для проксирования WebSocket), передайте { allowHalfOpen: true } к accept(). Обратите внимание, что new WebSocket(url) всегда отвечает автоматически после включения этого флага. Чтобы получить поведение half-open для клиентского WebSocket, используйте fetch()-ориентированный шаблон, показанный выше, и вызовите ws.accept({ allowHalfOpen: true }).

Подробнее см. в Поведение при закрытии WebSocket.

Сжатие WebSocket

Cloudflare Workers поддерживает сжатие WebSocket. См. Сжатие WebSocket, где это описано подробнее.