-
HTTP流式返回(Stream)是一种服务器向客户端传输数据的方式允许数据分块发送而不是一次性发送完毕。
这样客户端可以在接收到第一部分数据时就开始处理,而不必等待整个响应完成。
-
应用场景:
2.1 业务场景:图表的监听(股票行情);体育比分;ota升级进度条;chartgpt聊天消息返回
2.2 开发场景:视频流,文件的下载
-
分块传输的具体过程
3.1 客户端请求数据:客户端向服务器发送HTTP请求。
3.2 服务器响应头:服务器发送响应头,其中包含Transfer-Encoding: chunked,表明将使用分块传输编码。
3.3 发送数据块:数据分块并依次发送每个块
3.4 结束块:所有数据块发送完后,服务器发送一个大小为0的结束块,表示数据传输完成。
-
客户端处理步骤
4.1 读取响应头:客户端首先读取响应头(Transfer-Encoding: chunked),了解使用了分块传输编码。
4.2 逐块处理数据:客户端依次读取每个块的大小和数据内容,并进行处理。处理完一个块后,继续读取下一个块,直到读取到大小为0的结束块。
-
流式返回工作原理:
5.1 块大小:每个数据块的大小以十六进制数表示,后跟一个回车换行(CRLF,·\r\n')。
5.2 数据块:实际的数据块(以二进制形式传输)。
5.3 终止块:最后一个数据块大小为0('01r\n\z\n'),表示数据传输结束。
ps:HTTP流式返回(也称为分块传输编码,chunked transfer encoding)实际上是通过十六进制表示块的大小,然后传输实际数据块的二进制内容。因此,流式返回的数据块以二进制形式传输,但每个数据块的大小通过十六进制来表示。
-
优点:
6.1 实时性:流式返回允许客户端在接收到部分数据时就开始处理,减少了等待时间。
6.2 减少内存消耗:服务器不需要一次性准备所有数据,适用于大数据量的传输。
6.3 节省带宽:仅在有数据更新时传输,避免不必要的开销。
-
缺点:
7.1 丢包和重传:在网络不稳定的情况下,数据块可能会丢失或损坏,导致数据不完整。虽然可以通过重传机制解决,但这增加了实现的复杂度和系统的负担。
7.2 浏览器兼容性:并非所有浏览器都完美支持流式返回,尤其是一些老旧的浏览器,可能无法正确处理分块传输编码(Chunked Transfer Encoding)或Fetch API中的流处理。
-
常见问题:
8.1 实时聊天应用:如果网络不稳定,可能导致消息丢常见问题,需要复杂的错误处理和重传机制
8.2大文件下载:如果网络中断,文件下载可能不完整,需要支持断点续传功能。
-
常见优化方案:
9.1 错误处理和重传:实现可靠的错误检测和重传机制,确保数据的完整性和正确性。
9.2 优化连接管理:使用高效的连接管理和负载均衡技术,减少长时间连接对服务器资源的占用。
9.3 客户端处理优化:在客户端实现高效的数据块拼接和解析逻辑,确保数据处理的顺序和正确性。
-
前端代码示例:
// app/api/streaming/route.js
const story = [
"李焊玲是一个女汉子。",
"她力大无穷,",
"喜欢挑战各种极限运动。",
"有一天,",
"她决定去攀登一座险峻的高山。",
"在山脚下,",
"她遇到了一群正准备放弃的登山者。",
"李焊玲鼓励他们,",
"说,",
"只要坚持,",
"没有什么是不可能的。",
"于是,",
"她带领这群登山者一起向山顶进发。",
"一路上,",
"她用自己的力量帮助大家克服各种困难,",
"大家都被她的勇气和毅力所感染。",
"最终,",
"在李焊玲的带领下,",
"他们成功登上了山顶。",
"大家欢呼雀跃,",
"李焊玲却笑着说,",
"这只是人生中的一个小挑战,",
"未来还有更多的冒险等着我们。",
"从那以后,",
"李焊玲的故事在登山者中广为流传,",
"她成为了大家心目中的英雄。"
];export async function GET(request) {
const readable = new ReadableStream({
async start(controller) {
// 第一块数据
// controller.enqueue("\n");// 模拟延迟并逐步发送数据块 const encoder = new TextEncoder(); for (let i = 0; i < story.length; i++) { await new Promise((resolve) => setTimeout(resolve, story[i].length*50)); controller.enqueue( encoder.encode( `${story[i]} \n` ) ); } // 关闭流 controller.close(); },
});
return new Response(readable, {
headers: {
"Content-Type": "text/plain",
"Transfer-Encoding": "chunked",
},
});
}
PS:
ReadableStream
和 TransformStream
是 JavaScript 中用于处理流数据的接口。
ReadableStream
ReadableStream
用于从源读取数据,可以异步地获取数据。例如,可以用来读取文件、网络请求等。以下是一个基本的示例:
javascript
const readableStream = new ReadableStream({
start(controller) {
// 初始化数据
controller.enqueue('Hello, ');
controller.enqueue('World!');
controller.close();
},
pull(controller) {
// 当 consumer 请求更多数据时调用
},
cancel(reason) {
// 当 consumer 取消读取时调用
console.log('Stream cancelled, reason:', reason);
}
});
const reader = readableStream.getReader();
reader.read().then(function processText({ done, value }) {
if (done) {
console.log('Stream complete');
return;
}
console.log(value);
return reader.read().then(processText);
});
TransformStream
TransformStream
用于对流数据进行转换。它接受一个输入流,并产生一个输出流。以下是一个简单的示例:
javascript
const transformStream = new TransformStream({
start(controller) {
// 初始化
},
transform(chunk, controller) {
// 对每个数据块进行处理
controller.enqueue(chunk.toUpperCase());
},
flush(controller) {
// 在流结束时处理剩余的数据
console.log('All chunks transformed.');
}
});
const readable = new ReadableStream({
start(controller) {
controller.enqueue('Hello, ');
controller.enqueue('world!');
controller.close();
}
});
const writable = new WritableStream({
write(chunk) {
console.log(chunk);
},
close() {
console.log('All data written.');
}
});
readable.pipeThrough(transformStream).pipeTo(writable);
以上示例展示了如何创建一个 ReadableStream
以及如何使用 TransformStream
对数据进行转换,然后通过 pipeThrough
将转换后的数据传输到一个 WritableStream
。
这两个接口可以非常灵活地处理流数据,适用于许多异步数据处理场景。