WebSockets

H3 内置跨平台 WebSocket 和服务器发送事件的实用工具。

您可以使用 🔌 CrossWS 为 H3 服务器添加跨平台 WebSocket 支持。

用法

WebSocket 处理程序可以使用 defineWebSocketHandler() 工具定义,并像事件处理程序一样注册到任意路由。

你需要在 serve 函数中将 CrossWS 注册为服务器插件。该插件会自动从你匹配到的路由中解析出正确的 hooks。

import { H3, serve, defineWebSocketHandler } from "h3";

import { plugin as ws } from "crossws/server";

const app = new H3();

app.get("/_ws", defineWebSocketHandler({ message: console.log }));

serve(app, {
  plugins: [ws()],
});
只有在你需要自己解析 hooks(例如不调用应用)时,才需要向 ws() 传入自定义 resolve。默认情况下,CrossWS 会调用应用的 fetch 处理程序,并读取由 defineWebSocketHandler() 附加的 hooks。

完整示例:

websocket.mjs
import { H3, serve, html, defineWebSocketHandler } from "h3";
import { plugin as ws } from "crossws/server";

export const app = new H3();

// 一个用于普通 HTTP 请求的最小自包含 WebSocket 演示页。
const playground = html`<!doctype html>
  <title>H3 WebSocket Playground</title>
  <h1>H3 WebSocket Playground</h1>
  <form id="form">
    <input id="input" placeholder="输入消息..." autocomplete="off" autofocus />
    <button type="submit">发送</button>
  </form>
  <div id="log"></div>
  <script type="module">
    const log = (msg) => {
      const line = document.createElement("div");
      line.textContent = msg;
      document.getElementById("log").append(line);
    };
    const url = location.href.replace(/^http/, "ws");
    const ws = new WebSocket(url);
    ws.addEventListener("open", () => log("[open] 已连接到 " + url));
    ws.addEventListener("message", (e) => log("[message] " + e.data));
    ws.addEventListener("close", () => log("[close] 已断开连接"));
    document.getElementById("form").addEventListener("submit", (e) => {
      e.preventDefault();
      const input = document.getElementById("input");
      ws.send(input.value);
      input.value = "";
    });
  </script>`;

// 单一路由同时提供演示页(普通 HTTP)和
// WebSocket 端点(升级请求)。页面会连接回自身。
app.get(
  "/",
  defineWebSocketHandler(
    {
      open(peer) {
        console.log("[open]", peer);

        // 向新客户端发送欢迎信息
        peer.send("欢迎来到服务器!");

        // 将新客户端加入 "chat" 频道
        peer.subscribe("chat");

        // 通知其他所有已连接客户端
        peer.publish("chat", `[system] ${peer} joined!`);
      },

      message(peer, message) {
        console.log("[message]", peer);

        if (message.text() === "ping") {
          // 向客户端回复一个 ping 响应
          peer.send("pong");
          return;
        }

        // 服务器会将收到的消息重新广播给所有人
        peer.publish("chat", `[${peer}] ${message}`);

        // 将消息回显给发送者
        peer.send(message);
      },

      close(peer) {
        console.log("[close]", peer);
        peer.publish("chat", `[system] ${peer} has left the chat!`);
        peer.unsubscribe("chat");
      },
    },
    // 非升级请求将返回演示页。
    () => playground,
  ),
);

serve(app, {
  plugins: [ws()],
});

处理 HTTP 请求

默认情况下,WebSocket 路由会对任何非 WebSocket 升级请求返回 426 Upgrade Required

你可以向 defineWebSocketHandler() 传入一个可选的 HTTP 处理器作为第二个参数,以在同一路由上处理普通(非升级)请求。WebSocket 升级请求仍然会进入这些 hooks。

app.get(
  "/_ws",
  defineWebSocketHandler({ message: (peer, message) => peer.send(message.text()) }, () => "发送 WebSocket 升级请求以连接。"),
);

服务器发送事件(SSE)

作为 WebSockets 的替代方案,您可以使用服务器发送事件

H3 提供内置 API,可使用 createEventStream(event) 工具创建服务器发送事件。

示例

server-sent-events.mjs
import { H3, serve, createEventStream } from "h3";

export const app = new H3();

app.get("/", (event) => {
  const eventStream = createEventStream(event);

  // 每秒发送一条消息
  const interval = setInterval(async () => {
    await eventStream.push("Hello world");
  }, 1000);

  // 当连接关闭或写入器关闭时清理定时器
  eventStream.onClosed(() => {
    console.log("连接已关闭");
    clearInterval(interval);
  });

  return eventStream.send();
});

serve(app);