HTTP响应流异常:JS端接收整段数据而非分片
HTTP流式传输分片接收问题解决
我用Rust的actix-web实现HTTP响应流式传输,想在JavaScript端逐个读取数据块,但实际JS一次性接收了所有数据,没有按预期分片。
后端Rust代码
use std::convert::Infallible; use async_stream::stream; use actix_web::{get, HttpResponse}; use actix_web::web::Bytes; #[get("/stream")] pub async fn stream() -> HttpResponse { HttpResponse::Ok() .streaming(stream! { let mut count = 0; while count < 10 { yield Ok::<_, Infallible>(Bytes::from(count.to_string())); count += 1; } }) }
前端JavaScript代码
async function get_stream() { const response = await fetch('/stream'); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); while (true) { let {value, done} = await reader.read(); if (done) break; console.log('Received', value); } console.log('Response fully received'); }
实际输出
Received 0123456789 stream.js:69 Response fully received
期望输出
Received 0 Received 1 Received 2 Received 3 Received 4 Received 5 Received 6 Received 7 Received 8 Received 9 stream.js:69 Response fully received
问题原因
TCP是面向流的协议,本身不保证数据包的边界。加上Nagle算法会合并小数据包以提升传输效率,再加上actix-web内部的缓冲机制,导致多个小的Bytes块被合并成一个大的HTTP帧发送,最终JS端一次性接收所有数据。
解决方案
方案1:添加延迟让服务器逐个发送块
通过在每个数据块发送后添加短暂延迟,让服务器有足够时间将当前块发送出去,避免被合并:
use std::convert::Infallible; use async_stream::stream; use actix_web::{get, HttpResponse}; use actix_web::web::Bytes; use tokio::time::{sleep, Duration}; // 新增导入 #[get("/stream")] pub async fn stream() -> HttpResponse { HttpResponse::Ok() .streaming(stream! { let mut count = 0; while count < 10 { yield Ok::<_, Infallible>(Bytes::from(count.to_string())); sleep(Duration::from_millis(100)).await; // 添加100ms延迟 count += 1; } }) }
方案2:给数据块添加分隔符,客户端按分隔符解析
在每个数据块末尾添加明确的分隔符(比如换行符),客户端接收后按分隔符分割数据,确保能识别每个独立块:
后端修改:
yield Ok::<_, Infallible>(Bytes::from(format!("{}\n", count))); // 每个块后加换行
前端修改:
async function get_stream() { const response = await fetch('/stream'); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); let buffer = ''; // 用于暂存未分割的剩余数据 while (true) { let {value, done} = await reader.read(); if (done) { // 处理最后剩余的未分割数据 if (buffer.trim()) console.log('Received', buffer.trim()); break; } buffer += value; // 按换行符分割数据 const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 把最后不完整的行放回缓冲 // 输出每个完整的块 for (const line of lines) { if (line) console.log('Received', line); } } console.log('Response fully received'); }
内容的提问来源于stack exchange,提问作者Austin Hamner
相关产品推荐
相关产品推荐

