WebSocket 配置详解
本章节详细介绍如何使用 vite-plugin-mock-dev-server 配置和开发 WebSocket Mock 服务。
概述
WebSocket 提供了全双工通信通道,适用于实时聊天、实时数据推送、在线协作等场景。插件通过 ws 选项支持 WebSocket Mock。
基础配置
启用 WebSocket 支持
在插件配置中指定 WebSocket 路径前缀:
ts
import { defineConfig } from 'vite'
import { mockDevServerPlugin } from 'vite-plugin-mock-dev-server'
export default defineConfig({
plugins: [
mockDevServerPlugin({
// HTTP Mock 路径前缀
prefix: ['/api'],
// WebSocket Mock 路径前缀
wsPrefix: ['/ws', '/socket.io']
})
]
})重要
wsPrefix 中的路径不应该同时配置在 vite.config.ts 的 server.proxy 中,这会导致 WebSocket 冲突。
基本 WebSocket Mock
创建 WebSocket Mock 文件:
ts
import { defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/ws/chat',
ws: true,
setup(wss) {
// 监听连接事件
wss.on('connection', (ws, req) => {
console.log('Client connected:', req.url)
// 发送欢迎消息
ws.send(JSON.stringify({
type: 'system',
message: 'Welcome to chat!'
}))
// 监听消息
ws.on('message', (data) => {
const message = JSON.parse(data.toString())
console.log('Received:', message)
// 广播给所有客户端
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify({
type: 'message',
data: message
}))
}
})
})
// 监听关闭
ws.on('close', () => {
console.log('Client disconnected')
})
})
}
})配置选项详解
ws 选项
启用 WebSocket 支持:
ts
export default defineMock({
url: '/ws/endpoint',
ws: true, // 启用 WebSocket
setup(wss, options) {
// WebSocket 服务器逻辑
}
})setup 函数
WebSocket 服务器的核心配置函数:
ts
function setup(wss, options): void参数说明:
wss: WebSocketServer - WebSocket 服务器实例options: 包含onCleanup清理回调注册函数
wss (WebSocketServer)
WebSocket 服务器实例,提供以下功能:
| 属性/方法 | 类型 | 说明 |
|---|---|---|
clients | Set<WebSocket> | 所有连接的客户端 |
on(event, callback) | Function | 监听事件 |
emit(event, data) | Function | 触发事件 |
options.onCleanup
注册清理回调,在热更新时执行:
ts
export default defineMock({
url: '/ws/chat',
ws: true,
setup(wss, { onCleanup }) {
const interval = setInterval(() => {
// 定期广播心跳
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify({ type: 'heartbeat' }))
}
})
}, 30000)
// 注册清理函数
onCleanup(() => {
clearInterval(interval)
console.log('Cleanup WebSocket resources')
})
}
})WebSocket 事件
服务器事件
connection 事件
新客户端连接时触发:
ts
wss.on('connection', (ws, req) => {
// ws: WebSocket 实例
// req: HTTP 请求对象
console.log('New connection from:', req.socket.remoteAddress)
console.log('URL:', req.url)
console.log('Headers:', req.headers)
})客户端事件
message 事件
收到客户端消息时触发:
ts
ws.on('message', (data, isBinary) => {
if (isBinary) {
// 处理二进制数据
console.log('Binary data received:', data)
}
else {
// 处理文本数据
const text = data.toString()
console.log('Text message:', text)
}
})close 事件
客户端断开连接时触发:
ts
ws.on('close', (code, reason) => {
console.log(`Connection closed: ${code} - ${reason}`)
})error 事件
发生错误时触发:
ts
ws.on('error', (error) => {
console.error('WebSocket error:', error)
})实际应用场景
场景 1:实时聊天室
ts
import { defineMock, defineMockData } from 'vite-plugin-mock-dev-server'
interface ChatMessage {
id: string
userId: string
username: string
content: string
timestamp: number
}
// 存储聊天记录
const chatHistory = defineMockData<ChatMessage[]>('chat-history', [])
// 存储在线用户
const onlineUsers = defineMockData<Map<string, { username: string, joinTime: number }>>
('online-users', new Map())
export default defineMock({
url: '/ws/chat-room',
ws: true,
setup(wss, { onCleanup }) {
// 广播消息给所有客户端
const broadcast = (message: any, excludeWs?: WebSocket) => {
const data = JSON.stringify(message)
wss.clients.forEach((client) => {
if (client !== excludeWs && client.readyState === 1) {
client.send(data)
}
})
}
wss.on('connection', (ws, req) => {
let userId: string | null = null
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString())
switch (message.type) {
case 'join':
userId = message.userId
onlineUsers.value.set(userId, {
username: message.username,
joinTime: Date.now()
})
// 发送历史记录
ws.send(JSON.stringify({
type: 'history',
data: chatHistory.value.slice(-50) // 最近50条
}))
// 广播用户加入
broadcast({
type: 'user-joined',
userId,
username: message.username,
onlineCount: onlineUsers.value.size
})
break
case 'message': {
if (!userId)
return
const chatMessage: ChatMessage = {
id: generateId(),
userId,
username: message.username,
content: message.content,
timestamp: Date.now()
}
// 保存到历史
chatHistory.value = [...chatHistory.value, chatMessage]
// 广播消息
broadcast({
type: 'new-message',
data: chatMessage
})
break
}
case 'typing': {
broadcast({
type: 'user-typing',
userId,
username: message.username
}, ws)
break
}
}
}
catch (error) {
ws.send(JSON.stringify({
type: 'error',
message: 'Invalid message format'
}))
}
})
ws.on('close', () => {
if (userId) {
const user = onlineUsers.value.get(userId)
onlineUsers.value.delete(userId)
broadcast({
type: 'user-left',
userId,
username: user?.username,
onlineCount: onlineUsers.value.size
})
}
})
})
// 清理
onCleanup(() => {
onlineUsers.value.clear()
})
}
})
function generateId(): string {
return Math.random().toString(36).substring(2, 15)
}场景 2:实时数据推送
ts
import { defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/ws/realtime',
ws: true,
setup(wss, { onCleanup }) {
// 模拟实时数据
const generateMetrics = () => ({
cpu: Math.random() * 100,
memory: Math.random() * 100,
network: Math.random() * 1000,
timestamp: Date.now()
})
// 定期推送数据
const interval = setInterval(() => {
const data = generateMetrics()
const message = JSON.stringify({
type: 'metrics',
data
})
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(message)
}
})
}, 1000)
wss.on('connection', (ws) => {
// 立即发送当前数据
ws.send(JSON.stringify({
type: 'metrics',
data: generateMetrics()
}))
})
onCleanup(() => {
clearInterval(interval)
})
}
})场景 3:在线协作编辑
ts
import { defineMock, defineMockData } from 'vite-plugin-mock-dev-server'
interface Operation {
id: string
userId: string
type: 'insert' | 'delete'
position: number
content?: string
timestamp: number
}
const documentContent = defineMockData<string>('doc-content', '')
const operations = defineMockData<Operation[]>('operations', [])
export default defineMock({
url: '/ws/collaboration/:docId',
ws: true,
setup(wss) {
wss.on('connection', (ws, req) => {
const docId = req.url?.split('/').pop()
// 发送当前文档内容
ws.send(JSON.stringify({
type: 'init',
content: documentContent.value,
docId
}))
ws.on('message', (data) => {
const message = JSON.parse(data.toString())
if (message.type === 'operation') {
const op: Operation = {
id: generateId(),
userId: message.userId,
type: message.operation.type,
position: message.operation.position,
content: message.operation.content,
timestamp: Date.now()
}
// 应用操作
applyOperation(op)
operations.value = [...operations.value, op]
// 广播给其他客户端
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === 1) {
client.send(JSON.stringify({
type: 'operation',
data: op
}))
}
})
}
})
})
function applyOperation(op: Operation) {
const content = documentContent.value
if (op.type === 'insert' && op.content) {
documentContent.value
= content.slice(0, op.position)
+ op.content
+ content.slice(op.position)
}
else if (op.type === 'delete') {
documentContent.value
= content.slice(0, op.position)
+ content.slice(op.position + (op.content?.length || 1))
}
}
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
}
})客户端使用示例
原生 WebSocket
ts
const ws = new WebSocket('ws://localhost:5173/ws/chat')
// 连接建立
ws.onopen = () => {
console.log('Connected')
// 发送加入消息
ws.send(JSON.stringify({
type: 'join',
userId: 'user-123',
username: 'John'
}))
}
// 接收消息
ws.onmessage = (event) => {
const message = JSON.parse(event.data)
switch (message.type) {
case 'history':
console.log('Chat history:', message.data)
break
case 'new-message':
console.log('New message:', message.data)
break
case 'user-joined':
console.log(`${message.username} joined`)
break
}
}
// 连接关闭
ws.onclose = () => {
console.log('Disconnected')
}
// 发送消息
function sendMessage(content: string) {
ws.send(JSON.stringify({
type: 'message',
username: 'John',
content
}))
}使用 Socket.io
如果使用 Socket.io 客户端:
ts
import { io } from 'socket.io-client'
const socket = io('ws://localhost:5173', {
path: '/ws/chat'
})
socket.on('connect', () => {
console.log('Connected')
})
socket.on('new-message', (data) => {
console.log('New message:', data)
})
socket.emit('message', {
username: 'John',
content: 'Hello!'
})调试技巧
开启调试日志
ts
mockDevServerPlugin({
log: 'debug'
})使用 Chrome DevTools
- 打开 Chrome DevTools
- 切换到 Network 面板
- 选择 WS (WebSocket) 过滤器
- 查看 WebSocket 连接和消息
命令行测试
使用 wscat 工具测试:
bash
# 安装 wscat
npm install -g wscat
# 连接 WebSocket
wscat -c ws://localhost:5173/ws/chat
# 发送消息
> {"type":"join","userId":"1","username":"test"}注意事项
- 连接数限制: 浏览器对同一域名的 WebSocket 连接数有限制(通常 6 个)
- 心跳机制: 建议实现心跳检测,防止连接被中间件断开
- 错误处理: 始终添加错误处理,避免未捕获的异常
- 资源清理: 使用
onCleanup清理定时器和资源 - 消息格式: 建议统一使用 JSON 格式,便于处理
常见问题
Q: WebSocket 连接失败?
检查以下几点:
wsPrefix配置是否正确- 确保没有在
vite.config.ts的proxy中配置相同路径 - 使用
ws://或wss://协议,不是http://
Q: 如何广播消息?
ts
wss.clients.forEach((client) => {
if (client.readyState === 1) { // 确保连接打开
client.send(message)
}
})Q: 如何获取客户端信息?
ts
wss.on('connection', (ws, req) => {
console.log('IP:', req.socket.remoteAddress)
console.log('URL:', req.url)
console.log('Headers:', req.headers)
})