SSE的使用
客户端请求头
1. 基本请求头
const eventSource = new EventSource('/api/stream');
浏览器会自动发送以下请求头:
GET /api/stream HTTP/1.1
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
2. 自定义请求头配置
注意:EventSource API 本身不支持自定义请求头,如需自定义头部,可以使用以下方案:
方案一:通过 URL 参数传递认证信息
const token = localStorage.getItem('authToken');
const eventSource = new EventSource(`/api/stream?token=${token}`);
方案二:使用 fetch API 实现类似 SSE 的功能
async function createSSEConnection(url, headers = {}) {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Authorization': `Bearer ${token}`,
...headers
}
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
// 处理数据
handleSSEMessage(data);
}
}
}
}
服务端响应头配置
1. 必需的响应头
// Node.js/Express 示例
app.get('/api/stream', (req, res) => {
// 设置 SSE 必需的响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*', // CORS
'Access-Control-Allow-Headers': 'Cache-Control',
'X-Accel-Buffering': 'no' // Nginx 禁用缓冲
});
});
2. CORS 相关配置
app.get('/api/stream', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
// CORS 配置
'Access-Control-Allow-Origin': 'http://localhost:3000',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
'Access-Control-Allow-Methods': 'GET, OPTIONS'
});
});
// 处理 OPTIONS 预检请求
app.options('/api/stream', (req, res) => {
res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:3000',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
'Access-Control-Allow-Methods': 'GET, OPTIONS'
});
res.end();
});
3. 认证和安全相关头部
app.get('/api/stream', authenticateToken, (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
// 安全相关
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
// CORS
'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGIN,
'Access-Control-Allow-Credentials': 'true'
});
});
// 中间件:验证 token
function authenticateToken(req, res, next) {
const token = req.query.token || req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token required' });
}
// 验证 token 逻辑
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
}
反向代理配置
Nginx 配置
location /api/stream {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE 特有配置
proxy_cache_bypass $http_upgrade;
proxy_buffering off;
proxy_read_timeout 24h;
proxy_send_timeout 24h;
# 禁用缓冲
proxy_set_header X-Accel-Buffering no;
}
Apache 配置
<Location "/api/stream">
ProxyPass http://backend/api/stream
ProxyPassReverse http://backend/api/stream
# 禁用缓冲
ProxyPreserveHost On
SetEnv proxy-nokeepalive 1
SetEnv proxy-sendchunked 1
</Location>
完整的前端实现示例
class SecureSSE {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.eventSource = null;
this.token = options.token || this.getToken();
this.connect();
}
getToken() {
return localStorage.getItem('authToken') ||
sessionStorage.getItem('authToken');
}
connect() {
// 由于 EventSource 不支持自定义头部,通过 URL 传递 token
const urlWithAuth = `${this.url}?token=${encodeURIComponent(this.token)}`;
this.eventSource = new EventSource(urlWithAuth);
this.eventSource.onopen = (event) => {
console.log('SSE 连接建立成功');
if (this.options.onOpen) {
this.options.onOpen(event);
}
};
this.eventSource.onmessage = (event) => {
if (this.options.onMessage) {
this.options.onMessage(event);
}
};
this.eventSource.onerror = (event) => {
console.error('SSE 连接错误:', event);
// 如果是认证错误,可能需要重新获取 token
if (event.target.readyState === EventSource.CLOSED) {
this.handleAuthError();
}
if (this.options.onError) {
this.options.onError(event);
}
};
}
handleAuthError() {
// 尝试刷新 token
this.refreshToken().then(newToken => {
this.token = newToken;
setTimeout(() => this.connect(), 1000);
}).catch(err => {
console.error('Token 刷新失败:', err);
// 重定向到登录页面
window.location.href = '/login';
});
}
async refreshToken() {
const response = await fetch('/api/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
}
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const data = await response.json();
localStorage.setItem('authToken', data.token);
return data.token;
}
close() {
if (this.eventSource) {
this.eventSource.close();
}
}
}
// 使用示例
const sse = new SecureSSE('/api/stream', {
token: localStorage.getItem('authToken'),
onMessage: (event) => {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
},
onError: (event) => {
console.error('连接错误:', event);
}
});
关键要点总结
- 客户端限制:EventSource API 不支持自定义请求头
- 认证方案:通过 URL 参数或使用 fetch API 替代
- CORS 配置:服务端必须正确配置跨域头部
- 缓冲控制:服务端和代理都需要禁用缓冲
- 安全考虑:实现适当的认证和授权机制
与传统http长连接的区别
使用 HTTP 长连接的判断标准:
- 频繁的请求-响应交互
- 短时间内多个API调用
- 需要优化连接开销
- 传统的客户端主动请求模式
// 决策示例:电商购物车
class ShoppingCart {
// ✅ 适合 HTTP 长连接
// 用户在短时间内可能进行多次操作:
// - 添加商品
// - 删除商品
// - 更新数量
// - 应用优惠券
// - 计算总价
async addItem(productId, quantity) {
return fetch('/api/cart/add', {
method: 'POST',
headers: {
'Connection': 'keep-alive', // 保持连接用于后续操作
'Content-Type': 'application/json'
},
body: JSON.stringify({ productId, quantity })
});
}
async updateQuantity(itemId, quantity) {
return fetch('/api/cart/update', {
method: 'PUT',
headers: {
'Connection': 'keep-alive',
'Content-Type': 'application/json'
},
body: JSON.stringify({ itemId, quantity })
});
}
}
使用 SSE 的判断标准:
- 需要服务器主动推送数据
- 实时性要求高
- 单向数据流(服务器→客户端)
- 长时间保持连接监听
// 决策示例:股票交易系统
class StockTradingDashboard {
// ✅ 适合 SSE
// 股价需要实时更新,服务器主动推送
constructor() {
this.priceStream = new EventSource('/api/stock-prices/stream');
this.priceStream.addEventListener('price-update', (event) => {
const priceData = JSON.parse(event.data);
this.updateStockPrice(priceData);
});
this.priceStream.addEventListener('market-alert', (event) => {
const alertData = JSON.parse(event.data);
this.showMarketAlert(alertData);
});
}
updateStockPrice(data) {
// 实时更新股价显示
const element = document.getElementById(`stock-${data.symbol}`);
element.textContent = `$${data.price}`;
}
showMarketAlert(data) {
// 实时更新股价显示
const element = document.getElementById(`market-${data.symbol}`);
element.textContent = `$${data.price}`;
}
}