Skip to content

Instantly share code, notes, and snippets.

@Wxh16144
Created January 7, 2026 07:04
Show Gist options
  • Select an option

  • Save Wxh16144/b60bcd8b62ef29a1e75fab3177120cec to your computer and use it in GitHub Desktop.

Select an option

Save Wxh16144/b60bcd8b62ef29a1e75fab3177120cec to your computer and use it in GitHub Desktop.
SSE Demo Server
const http = require('http');
let lastPostData = '';
const clients = new Set();
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url.startsWith('/')) {
// 解析 query 参数 interval
const url = require('url');
const parsedUrl = url.parse(req.url, true);
const interval = Math.max(10, parseInt(parsedUrl.query.interval, 10) || 100);
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
lastPostData = body;
// 通知所有客户端上一次 SSE 结束
for (const client of clients) {
client.write(`data: ${JSON.stringify({ type: 'end', message: '中断,新的POST到来' })}\n\n`);
}
// 按行切割
const lines = body.split(/\r?\n/);
let idx = 0;
function sendNextLine() {
if (idx < lines.length) {
const line = lines[idx];
for (const client of clients) {
client.write(`data: ${JSON.stringify({ type: 'data', content: line })}\n\n`);
}
idx++;
setTimeout(sendNextLine, interval);
}
}
sendNextLine();
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('OK');
});
} else if (req.method === 'GET' && req.url === '/sse') {
// SSE 连接
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
res.write('\n');
clients.add(res);
// 立即推送最近一次数据
if (lastPostData) {
// 兼容首次连接时推送历史内容
const lines = lastPostData.split(/\r?\n/);
for (const line of lines) {
res.write(`data: ${JSON.stringify({ type: 'data', content: line })}\n\n`);
}
}
// 定期发送心跳,防止连接被关闭
const heartbeat = setInterval(() => {
res.write(': keep-alive\n\n');
}, 15000);
req.on('close', () => {
clearInterval(heartbeat);
clients.delete(res);
});
} else if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
// 返回 index.html 文件内容
const fs = require('fs');
fs.readFile('index.html', (err, data) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error loading index.html');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
}
});
} else {
res.writeHead(404);
res.end();
}
});
const port = process.env.PORT || 3007;
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSE Demo</title>
</head>
<body>
<h2>最新POST数据:</h2>
<div id="status" style="color:red;margin-bottom:8px;"></div>
<style>
body { font-family: Arial, sans-serif; background: #f7f7f7; }
#sseTable { border-collapse: collapse; width: 90%; margin: 0 auto 20px auto; background: #fff; box-shadow: 0 2px 8px #0001; }
#sseTable th, #sseTable td { border: 1px solid #ccc; padding: 6px 12px; text-align: left; }
#sseTable th { background: #f0f0f0; }
#sseTable tbody tr:nth-child(even) { background: #fafafa; }
#status { text-align: center; }
</style>
<table id="sseTable">
<thead><tr><th>时间</th><th>类型</th><th>内容</th></tr></thead>
<tbody></tbody>
</table>
<script>
const statusDiv = document.getElementById('status');
const tableBody = document.querySelector('#sseTable tbody');
const es = new EventSource('/sse');
function nowStr() {
const d = new Date();
return d.toLocaleTimeString('zh-CN', { hour12: false }) + '.' + d.getMilliseconds().toString().padStart(3, '0');
}
es.onmessage = e => {
let row;
const time = nowStr();
try {
const data = JSON.parse(e.data);
if (data.type === 'end') {
statusDiv.textContent = 'SSE 被中断: ' + (data.message || '');
row = document.createElement('tr');
row.innerHTML = `<td>${time}</td><td style=\"color:red;\">${data.type}</td><td style=\"color:red;\">${data.message || ''}</td>`;
tableBody.insertBefore(row, tableBody.firstChild);
} else if (data.type === 'data') {
statusDiv.textContent = '';
row = document.createElement('tr');
row.innerHTML = `<td>${time}</td><td>${data.type}</td><td>${data.content}</td>`;
tableBody.insertBefore(row, tableBody.firstChild);
}
} catch {
row = document.createElement('tr');
row.innerHTML = `<td>${time}</td><td>raw</td><td>${e.data}</td>`;
tableBody.insertBefore(row, tableBody.firstChild);
}
};
es.onerror = () => {
statusDiv.textContent = 'SSE 连接断开';
};
</script>
</body>
</html>
@Wxh16144
Copy link
Author

Wxh16144 commented Jan 7, 2026

SSE Demo Server

简介

这是一个基于 Node.js 的 Server-Sent Events (SSE) 演示服务。支持通过 POST 发送多行文本,服务端会逐行推送到前端页面,支持断流提示和消息记录。

启动服务

  1. 安装 Node.js(建议 v14+)
  2. 在项目目录下运行:
node app.js

默认监听端口:3007

访问页面

浏览器访问:http://localhost:3007 即可实时查看 SSE 推送效果。

测试 POST 数据

1. macOS 用户:将剪贴板内容发送到服务

pbpaste | curl -X POST -H "Content-Type: text/plain" --data-binary @- 127.0.0.1:3007

2. 发送一组数字(1 到 19)

echo -e "$(seq 1 19 | xargs)" | tr ' ' '\\n' | curl -X POST -H "Content-Type: text/plain" --data-binary @- 127.0.0.1:3007

或直接:

for i in {1..19}; do echo $i; done | curl -X POST -H "Content-Type: text/plain" --data-binary @- 127.0.0.1:3007

3. 自定义 SSE 推送间隔

你可以通过 URL query 参数 interval(单位:毫秒)自定义 SSE 推送每行的间隔。例如:

for i in {1..5}; do echo $i; done | curl -X POST -H "Content-Type: text/plain" --data-binary @- "127.0.0.1:3007/?interval=500"

上述命令会每 500ms 推送一行内容到前端。

说明

  • 每次 POST 新内容,前端会收到断流提示,并逐行显示新内容。
  • SSE 心跳包用于保持连接活跃。
  • 支持多客户端实时推送。
  • interval 参数可自定义推送速度,默认 100ms,最小 10ms。

如有问题可随时反馈。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment