Next WS
在Next.js应用目录中添加WebSocket支持npm install next-ws ws
🤔 关于
Next WS (next-ws
) 是一个高级的Next.js插件,可以将WebSocket服务器功能无缝集成到应用目录中的路由中。使用Next WS,你不再需要为WebSocket功能设置单独的服务器。
[!重要]
Next WS 专为基于服务器的环境设计。它不适用于不支持WebSocket服务器的无服务器平台,如Vercel。此外,该插件是为应用目录构建的,不支持较旧的页面目录。
该模块的灵感来自于现已过时的 next-plugin-websocket
,如果你使用的是较旧版本的Next.js,那个模块可能适合你。
🏓 目录
📦 安装
使用Next WS设置WebSocket服务器涉及修补本地Next.js安装。Next WS通过CLI命令简化了这个过程,自动检测并修补你的Next.js版本,确保兼容性。请注意,需要Next.js 13.1.1或更高版本。
npx next-ws-cli@latest patch
[!注意]
如果你的本地Next.js安装发生变更或更新,你需要重新运行修补命令。
成功修补Next.js后,将Next WS包及其对等依赖ws安装到你的项目中:
npm install next-ws ws
🚀 使用方法
使用Next WS在Next.js应用中实现WebSocket功能非常简单,无需额外配置。只需从任何路由文件中导出一个SOCKET
函数即可。当客户端与该特定路由建立WebSocket连接时,将调用此函数。
SOCKET
函数接收三个参数:WebSocket客户端实例、传入的HTTP请求(你可以用它获取URL路径、查询参数和头部信息)以及WebSocket服务器实例。
export function SOCKET(
client: import('ws').WebSocket,
request: import('http').IncomingMessage,
server: import('ws').WebSocketServer,
) {
// ...
}
使用自定义服务器
在生产环境中,Next.js为路由使用工作进程,这可能使得从SOCKET
处理程序外部访问WebSocket服务器变得困难,尤其是当WebSocket服务器存在于主进程时。对于需要克服这一挑战或偏好自定义服务器设置的用户,Next WS提供了解决方案。
next-ws/server
模块提供了设置HTTP和WebSocket服务器的函数。你可以使用这些函数告诉Next WS使用你的服务器实例,而不是创建自己的实例。这允许你从应用的任何地方访问你自己创建的实例。请参考下面的示例。
🌀 示例
有关更详细的示例,请参考examples
目录。
创建Socket
只需在应用目录中的任何位置创建API路由并从中导出SOCKET
函数即可。以下是一个简单的回声服务器示例,它会将收到的任何消息发送回去。
// app/api/ws/route.ts(可以是应用目录中的任何路由文件)
export function SOCKET(
client: import('ws').WebSocket,
request: import('http').IncomingMessage,
server: import('ws').WebSocketServer,
) {
console.log('客户端已连接');
client.on('message', (message) => {
console.log('收到消息:', message);
client.send(message);
});
client.on('close', () => {
console.log('客户端已断开连接');
});
}
使用自定义服务器
要使用自定义服务器,你只需要告诉Next WS使用你的服务器而不是创建自己的服务器。这可以通过调用next-ws/server
中的setHttpServer
和setWebSocketServer
函数并传递你的服务器实例来实现。
// server.js
const { setHttpServer, setWebSocketServer } = require('next-ws/server');
const { Server } = require('node:http');
const { parse } = require('node:url');
const next = require('next');
const { WebSocketServer } = require('ws');
const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = 3000;
const httpServer = new Server();
const webSocketServer = new WebSocketServer({ noServer: true });
// 在启动自定义服务器之前告诉Next WS关于HTTP和WebSocket服务器的信息
setHttpServer(httpServer);
setWebSocketServer(webSocketServer);
const app = next({ dev, hostname, port, customServer: httpServer });
const handle = app.getRequestHandler();
app.prepare().then(() => {
httpServer
.on('request', async (req, res) => {
const parsedUrl = parse(req.url, true);
await handle(req, res, parsedUrl);
})
.listen(port, () => {
console.log(` ▲ 准备就绪,访问 http://${hostname}:${port}`);
});
});
访问WebSocket服务器
除了设置器外,Next WS还为HTTP和WebSocket服务器提供了获取器。这些函数可以在你的应用程序的任何地方用来访问服务器。
[!重要]
为了使用getWebSocketServer
和getHttpServer
函数,你必须使用自定义服务器,这是由于Next.js的限制。请参考使用自定义服务器。
// app/api/stats/route.ts
import { getWebSocketServer } from 'next-ws/server';
// 还有一个`getHttpServer`函数可用
export function GET() {
const wsServer = getWebSocketServer();
// 响应已连接客户端的数量
return Response.json({ count: wsServer.clients.size });
}
客户端工具
为了更容易地连接到你的新WebSocket服务器,Next WS还提供了一些客户端工具。这些是完全可选的,你可以使用自己的实现如果你愿意。
// layout.tsx
'use client';
import { WebSocketProvider } from 'next-ws/client';
export default function Layout({ children }: React.PropsWithChildren) {
return <html>
<body>
<WebSocketProvider
url="ws://localhost:3000/api/ws"
>
{children}
</WebSocketProvider>
</body>
</html>;
}
以下是WebSocketProvider
组件的属性接口,包含所有可用选项。
interface WebSocketProviderProps {
children: React.ReactNode;
/** WebSocket连接的URL。 */
url: string;
/** 使用的子协议。 */
protocols?: string[] | string;
/** 使用的二进制类型。 */
binaryType?: BinaryType;
}
现在你可以使用useWebSocket
钩子来获取WebSocket实例,并发送和接收消息。
// page.tsx
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useWebSocket } from 'next-ws/client';
export default function Page() {
const ws = useWebSocket();
// ^? 客户端上是WebSocket,服务器上是null
const inputRef = useRef<HTMLInputElement>(null);
const [message, setMessage] = useState<string | null>(null);
useEffect(() => {
async function onMessage(event: MessageEvent) {
const payload =
typeof event.data === 'string' ? event.data : await event.data.text();
const message = JSON.parse(payload) as Message;
setMessages((p) => [...p, message]);
}
ws?.addEventListener('message', onMessage);
return () => ws?.removeEventListener('message', onMessage);
}, [ws]);
return <>
<input
ref={inputRef}
type="text"
/>
<button
onClick={() => ws?.send(inputRef.current?.value ?? '')}
>
向服务器发送消息
</button>
<p>
{message === null
? '等待接收消息...'
: `收到消息: ${message}`}
</p>
</>;
}