Guide
WebSocket
Nitro 原生支持跨平台 WebSocket API
Nitro 原生支持使用 CrossWS 和 H3 WebSocket 的运行时无关的 WebSocket API。
选择实验性功能
WebSockets 支持目前处于实验阶段。请查看 nitrojs/nitro#2171 了解平台支持状态。
要启用 WebSocket 支持,您需要启用实验性的 websocket
功能标志。
export default defineNitroConfig({
experimental: {
websocket: true
}
})
用法
在 routes/_ws.ts
中创建一个 websocket 处理程序(或者在 Nuxt 中使用 server/routes/_ws.ts
)。
您可以使用任何路由,如
routes/chatroom.ts
,在 /chatroom
上注册升级处理程序。_ws.ts
export default defineWebSocketHandler({
open(peer) {
peer.send({ user: "server", message: `Welcome ${peer}!` });
peer.publish("chat", { user: "server", message: `${peer} joined!` });
peer.subscribe("chat");
},
message(peer, message) {
if (message.text().includes("ping")) {
peer.send({ user: "server", message: "pong" });
} else {
const msg = {
user: peer.toString(),
message: message.toString(),
};
peer.send(msg); // echo
peer.publish("chat", msg);
}
},
close(peer) {
peer.publish("chat", { user: "server", message: `${peer} left!` });
},
});
Nitro 允许您使用相同的事件处理路由定义多个 websocket 处理程序。
使用客户端连接到服务器。例如:(routes/websocket.ts
或 Nuxt 的 server/routes/websocket.ts
)
index.ts
export default defineEventHandler(() => {
return /* html */ `<!doctype html>
<html lang="en" data-theme="dark">
<head>
<title>CrossWS Test Page</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background-color: #1a1a1a;
}
</style>
<script type="module">
// https://github.com/vuejs/petite-vue
import {
createApp,
reactive,
nextTick,
} from "https://esm.sh/petite-vue@0.4.1";
let ws;
const store = reactive({
message: "",
messages: [],
});
const scroll = () => {
nextTick(() => {
const el = document.querySelector("#messages");
el.scrollTop = el.scrollHeight;
el.scrollTo({
top: el.scrollHeight,
behavior: "smooth",
});
});
};
const format = async () => {
for (const message of store.messages) {
if (!message._fmt && message.text.startsWith("{")) {
message._fmt = true;
const { codeToHtml } = await import("https://esm.sh/shiki@1.0.0");
const str = JSON.stringify(JSON.parse(message.text), null, 2);
message.formattedText = await codeToHtml(str, {
lang: "json",
theme: "dark-plus",
});
}
}
};
const log = (user, ...args) => {
console.log("[ws]", user, ...args);
store.messages.push({
text: args.join(" "),
formattedText: "",
user: user,
date: new Date().toLocaleString(),
});
scroll();
format();
};
const connect = async () => {
const isSecure = location.protocol === "https:";
const url = (isSecure ? "wss://" : "ws://") + location.host + "/_ws";
if (ws) {
log("ws", "Closing previous connection before reconnecting...");
ws.close();
clear();
}
log("ws", "Connecting to", url, "...");
ws = new WebSocket(url);
ws.addEventListener("message", async (event) => {
let data = typeof event.data === "string" ? data : await event.data.text();
const { user = "system", message = "" } = data.startsWith("{")
? JSON.parse(data)
: { message: data };
log(
user,
typeof message === "string" ? message : JSON.stringify(message),
);
});
await new Promise((resolve) => ws.addEventListener("open", resolve));
log("ws", "Connected!");
};
const clear = () => {
store.messages.splice(0, store.messages.length);
log("system", "previous messages cleared");
};
const send = () => {
console.log("sending message...");
if (store.message) {
ws.send(store.message);
}
store.message = "";
};
const ping = () => {
log("ws", "Sending ping");
ws.send("ping");
};
createApp({
store,
send,
ping,
clear,
connect,
rand: Math.random(),
}).mount();
await connect();
</script>
</head>
<body class="h-screen flex flex-col justify-between">
<main v-scope="{}">
<!-- Messages -->
<div id="messages" class="flex-grow flex flex-col justify-end px-4 py-8">
<div class="flex items-center mb-4" v-for="message in store.messages">
<div class="flex flex-col">
<p class="text-gray-500 mb-1 text-xs ml-10">{{ message.user }}</p>
<div class="flex items-center">
<img
:src="'https://www.gravatar.com/avatar/' + encodeURIComponent(message.user + rand) + '?s=512&d=monsterid'"
alt="Avatar"
class="w-8 h-8 rounded-full"
/>
<div class="ml-2 bg-gray-800 rounded-lg p-2">
<p
v-if="message.formattedText"
class="overflow-x-scroll"
v-html="message.formattedText"
></p>
<p v-else class="text-white">{{ message.text }}</p>
</div>
</div>
<p class="text-gray-500 mt-1 text-xs ml-10">{{ message.date }}</p>
</div>
</div>
</div>
<!-- Chatbox -->
<div
class="bg-gray-800 px-4 py-2 flex items-center justify-between fixed bottom-0 w-full"
>
<div class="w-full min-w-6">
<input
type="text"
placeholder="Type your message..."
class="w-full rounded-l-lg px-4 py-2 bg-gray-700 text-white focus:outline-none focus:ring focus:border-blue-300"
@keydown.enter="send"
v-model="store.message"
/>
</div>
<div class="flex">
<button
class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4"
@click="send"
>
Send
</button>
<button
class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4"
@click="ping"
>
Ping
</button>
<button
class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4"
@click="connect"
>
Reconnect
</button>
<button
class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-r-lg"
@click="clear"
>
Clear
</button>
</div>
</div>
</main>
</body>
</html>`;
});
现在您可以在 /websocket
路由上进行尝试!
查看我们的 聊天演示,使用 Nitro WebSocket API。
服务器发送事件 (SSE)
作为 WebSockets 的替代方案,您可以使用 服务器发送事件
示例
在 routes/sse.ts
中创建一个 SSE 处理程序(或在 Nuxt 中使用 server/routes/sse.ts
)。
sse.ts
export default defineEventHandler(async (event) => {
const eventStream = createEventStream(event)
const interval = setInterval(async () => {
await eventStream.push(`Message @ ${new Date().toLocaleTimeString()}`)
}, 1000)
eventStream.onClosed(async () => {
clearInterval(interval)
await eventStream.close()
})
return eventStream.send()
})
然后从客户端连接到这个 SSE 端点
const eventSource = new EventSource('http://localhost:3000/sse')
eventSource.onmessage = (event) => {
console.log(event.data)
}