event-stream vs octet-stream
TL;DR: text/event-stream
是结构化的事件流格式,适合实时事件推送;application/octet-stream
是原始二进制流,适合大数据传输和自定义格式。
在 HTTP 流式传输中,text/event-stream
和 application/octet-stream
是两种不同的 Content-Type,它们代表了不同的数据组织方式和使用场景。
text/event-stream
text/event-stream
是专门用于服务器推送事件的 MIME 类型,定义了结构化的文本格式来表示事件流。每个事件都有固定的格式要求。
该格式具有以下特点:
- 结构化数据:每个事件包含
data:
、event:
、id:
等字段 - 文本格式:基于 UTF-8 编码的纯文本
- 事件边界:使用双换行符
\n\n
分隔事件 - 浏览器支持:可以用
EventSource
API 或ReadableStream
读取
使用 EventSource
API(传统方式):
const eventSource = new EventSource("/api/events");
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("收到数据:", data);
};
eventSource.onerror = (error) => {
console.error("连接错误:", error);
};
使用 ReadableStream
读取(更灵活):
async function readEventStream() {
const response = await fetch("/api/events");
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const events = chunk.split("\n\n");
events.forEach((event) => {
if (event.startsWith("data: ")) {
const data = event.substring(6);
console.log("收到事件:", data);
}
});
}
}
服务器端需要发送特定格式的响应:
app.get("/api/events", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
// 发送事件
const sendEvent = (data, eventType = null, id = null) => {
if (id) res.write(`id: ${id}\n`);
if (eventType) res.write(`event: ${eventType}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// 定期推送数据
const interval = setInterval(() => {
sendEvent(
{
timestamp: Date.now(),
message: "Hello",
},
"update",
Date.now()
);
}, 1000);
req.on("close", () => clearInterval(interval));
});
application/octet-stream
application/octet-stream
是通用的二进制流 MIME 类型,表示原始的字节流数据。它不预定义任何特定的数据结构,完全由应用程序决定数据格式。
该格式具有以下特点:
- 原始数据:不对数据内容做任何假设或格式化
- 二进制流:可以传输任意类型的数据
- 自定义格式:数据格式完全由应用程序定义
- 高效传输:没有格式开销,传输效率高
客户端使用 ReadableStream
读取:
async function readOctetStream() {
const response = await fetch("/api/stream");
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理原始字节数据
console.log("接收到数据块:", value);
// 根据应用协议解析数据
const chunk = new TextDecoder().decode(value);
console.log("解析后的数据:", chunk);
}
}
服务器端可以发送原始数据流:
app.get("/api/stream", (req, res) => {
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Transfer-Encoding": "chunked",
});
let count = 0;
const interval = setInterval(() => {
if (count >= 10) {
res.end();
clearInterval(interval);
return;
}
// 发送自定义格式的数据
const data = Buffer.from(`数据块 ${count++}\n`);
res.write(data);
}, 100);
});
传输方式与长度处理
两种格式都支持多种传输方式:
分块编码 (Chunked Encoding):服务器将响应分割成多个块,每个块包含大小信息和数据内容。这种方式不需要预先知道数据总长度,适合动态生成的内容。
已知内容长度:服务器预先知道数据总长度,在响应头中声明 Content-Length
。
对于不知道数据长度的场景,有几种处理方式:
- 使用分块编码:这是最推荐的方式,HTTP/1.1 标准支持,无需预先知道长度
- 设置较大的 Content-Length:设置一个足够大的值,传输完成后由服务器主动关闭连接
- 不设置 Content-Length:某些情况下可以省略该头部,依赖连接关闭来标识传输结束
// 方式1:分块编码(推荐)
app.get("/api/stream-chunked", (req, res) => {
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Transfer-Encoding": "chunked",
});
// 动态生成数据,无需预知长度
generateDataStream(
(chunk) => {
res.write(chunk);
},
() => {
res.end(); // 数据生成完毕
}
);
});
// 方式2:设置较大长度
app.get("/api/stream-large", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Content-Length": "999999999", // 设置一个很大的值
});
// 传输数据
streamData(
(chunk) => {
res.write(chunk);
},
() => {
res.end(); // 服务器主动关闭
}
);
});
注意事项:
- 分块编码是处理未知长度数据的标准方式,几乎所有现代 HTTP 客户端都支持
- 设置过大的 Content-Length 可能导致客户端等待超时,建议谨慎使用
- 服务器主动关闭连接是终止流传输的可靠方式,客户端会收到相应的结束信号
格式对比
特性 | text/event-stream | application/octet-stream |
---|---|---|
数据格式 | 结构化事件格式 | 原始字节流 |
数据类型 | 文本(UTF-8) | 二进制 |
格式开销 | 每个事件有固定格式开销 | 无格式开销 |
解析复杂度 | 需要解析事件边界 | 应用程序自定义解析 |
浏览器支持 | EventSource + ReadableStream | ReadableStream |
重连机制 | EventSource 内置自动重连 | 需要应用层实现 |
事件类型 | 支持自定义事件类型 | 无事件概念 |
数据效率 | 相对较低(格式开销) | 高(无额外开销) |
应用场景选择
场景类型 | text/event-stream | application/octet-stream |
---|---|---|
实时通知 | 消息推送、状态更新 | 大量数据推送 |
金融行情 | 股票价格、市场动态 | 历史数据传输 |
协作应用 | 多人编辑、在线会议 | 文件同步、版本控制 |
监控面板 | 系统状态、性能指标 | 日志流、大数据分析 |
媒体传输 | 轻量级事件推送 | 视频下载、音频直播 |
AI 应用 | 简单状态通知 | 模型推理结果流式输出 |
选择建议
选择 text/event-stream
的情况:
- 需要结构化的事件推送
- 数据量较小且频繁
- 希望使用浏览器内置的 EventSource API
- 需要事件类型和 ID 管理
- 主要面向 Web 客户端
选择 application/octet-stream
的情况:
- 需要传输大量数据
- 对传输效率有较高要求
- 需要自定义数据格式
- 需要支持多种客户端类型
- 希望更灵活的数据组织方式
在实际项目中,这两种格式往往根据不同的业务场景来选择,甚至在同一个应用中同时使用两种格式。