Server-Sent Events(Server-Sent Events)
一、SSE 概述
1.1 什么是 Server-Sent Events
Server-Sent Events 是一种让服务器通过 HTTP 协议向浏览器推送消息的技术。与 WebSocket 的双向通信不同,SSE 是单向的——数据只能从服务器流向客户端。
// 创建 EventSource 连接
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
console.log('Message:', event.data);
};
eventSource.onerror = (error) => {
console.error('Error:', error);
};
1.2 SSE vs WebSocket
| 特性 | Server-Sent Events | WebSocket |
|---|---|---|
| 方向 | 单向(服务器→客户端) | 双向 |
| 协议 | HTTP/2 | 特殊协议(ws://) |
| 自动重连 | 支持 | 需要手动实现 |
| 二进制数据 | 不支持(仅文本) | 支持 |
| 兼容性 | 良好(旧浏览器需 polyfill) | 良好 |
| 简单性 | 简单 | 较复杂 |
| 防火墙 | 容易通过(HTTP) | 可能被阻止 |
二、EventSource API
2.1 基本用法
const eventSource = new EventSource('/api/events');
// 监听消息
eventSource.onmessage = (event) => {
console.log('Message:', event.data);
};
// 监听错误
eventSource.onerror = (error) => {
console.error('Error:', error);
// EventSource 会自动重连
};
2.2 监听特定事件
// 服务器端发送事件类型
// event: update\ndata: {}\n\n
eventSource.addEventListener('update', (event) => {
console.log('Update event:', event.data);
});
eventSource.addEventListener('notification', (event) => {
console.log('Notification:', event.data);
});
2.3 连接管理
const eventSource = new EventSource('/api/events');
// 检查连接状态
console.log(eventSource.readyState);
// EventSource.CONNECTING = 0
// EventSource.OPEN = 1
// EventSource.CLOSED = 2
// 关闭连接
eventSource.close();
// 检查是否关闭
if (eventSource.readyState === EventSource.CLOSED) {
console.log('Connection closed');
}
三、服务器端实现
3.1 SSE 格式
服务器需要发送特定格式的响应:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Access-Control-Allow-Origin: *
event: message
data: {"message": "Hello"}
event: update
data: {"status": "active"}
3.2 数据字段
| 字段 | 说明 |
|---|---|
event |
事件类型 |
data |
事件数据(可以多行) |
id |
事件 ID(用于断点续传) |
retry |
重连时间(毫秒) |
: |
注释 |
3.3 服务器示例(Node.js)
// Express 示例
app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 发送初始消息
res.write('event: connected\n');
res.write('data: {"status": "connected"}\n\n');
// 定时发送更新
const interval = setInterval(() => {
res.write(`event: update\n`);
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
// 清理
req.on('close', () => {
clearInterval(interval);
res.end();
});
});
四、实际应用
4.1 实时数据更新
class RealTimeUpdater {
constructor(url) {
this.url = url;
this.eventSource = null;
this.handlers = new Map();
}
connect() {
this.eventSource = new EventSource(this.url);
this.eventSource.onmessage = (event) => {
this.handleMessage(event);
};
this.eventSource.onerror = (error) => {
console.error('SSE Error:', error);
this.handleError(error);
};
}
on(eventType, handler) {
this.handlers.set(eventType, handler);
}
handleMessage(event) {
const data = JSON.parse(event.data);
console.log('Received:', data);
}
handleError(error) {
// 可以在此实现自定义重连逻辑
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
}
// 使用
const updater = new RealTimeUpdater('/api/events');
updater.on('stock-update', (data) => {
updateStockPrice(data);
});
updater.on('notification', (data) => {
showNotification(data.message);
});
updater.connect();
4.2 进度跟踪
// 服务器端
app.get('/api/progress', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
let progress = 0;
const interval = setInterval(() => {
progress += 10;
res.write(`data: ${JSON.stringify({ progress })}\n\n`);
if (progress >= 100) {
res.write(`event: complete\n`);
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
clearInterval(interval);
res.end();
}
}, 500);
});
// 客户端
const eventSource = new EventSource('/api/progress');
eventSource.onmessage = (event) => {
const { progress } = JSON.parse(event.data);
updateProgressBar(progress);
};
eventSource.addEventListener('complete', () => {
console.log('Process completed');
eventSource.close();
});
五、断点续传与 ID
5.1 使用 Last-Event-ID
// 客户端自动发送 Last-Event-ID
const eventSource = new EventSource('/api/events');
eventSource.addEventListener('message', (event) => {
// event.lastEventId 包含服务器发送的 ID
console.log('Last ID:', event.lastEventId);
});
// 服务器端接收
app.get('/api/events', (req, res) => {
const lastEventId = req.headers['last-event-id'];
console.log('Last Event ID:', lastEventId);
// 从 lastEventId 之后开始发送
});
5.2 重连处理
// 服务器可以指定重连时间
res.write(`retry: 5000\n\n`); // 5秒后重连
// 客户端重连逻辑
eventSource.onerror = (error) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('Connection closed permanently');
// 显示重连按钮
}
};