Dart HTTP服务器分块返回流数据时flush报错如何解决
Dart HTTP 服务实现分块流响应的正确方案
错误原因
你遇到的Bad state: StreamSink is bound to a stream报错由两个核心问题导致:
- 手动设置
Transfer-Encoding: chunked头属于多余操作:Dart 原生HttpServer在响应内容长度未知(传输流数据)时,会自动启用分块传输编码,手动设置该头会和框架内置逻辑冲突。 - 手动监听流、无等待调用
write+flush违反了IOSink的状态规则:HttpResponse的flush()是异步操作,上一次刷新未完成时再次调用写入/刷新方法,就会触发sink被占用的状态错误;同时这种写法没有做背压控制,流生产速度快于客户端接收速度时会撑爆服务器内存。
正确实现(和Go版本逻辑完全对齐)
直接使用addStream方法将音频流绑定到响应即可,框架会自动处理分块拆分、逐块刷新、背压控制、客户端断开时的流取消逻辑,不需要手动调用flush:
final server = await HttpServer.bind(InternetAddress.anyIPv4, 8080); await for (var request in server) { final stream = await _audio.stream(); final response = request.response; // 设置响应头,无需手动配置Transfer-Encoding response.headers ..contentType = ContentType("audio", "mpeg") ..add("Connection", "Keep-Alive") ..add("Access-Control-Allow-Origin", "*") ..add("X-Content-Type-Options", "nosniff"); try { // 直接管道流到响应,自动逐块发送、自动flush await response.addStream(stream); } catch (e) { // 捕获客户端断开、流读取异常等场景 print('传输中断: $e'); } finally { // 传输完成/异常时关闭响应 await response.close(); } }
特殊场景:需要逐块自定义处理的写法
如果需要在发送每个数据块前加自定义逻辑(比如日志、数据加工),不要直接在listen回调里无等待调用flush,需要通过暂停/恢复订阅做背压控制,等上一块发送完成再处理下一块:
final stream = await _audio.stream(); final response = request.response; // 响应头配置和上文一致,省略 StreamSubscription? subscription; subscription = stream.listen( (event) async { // 暂停订阅,防止当前块未发完就收到下一块数据 subscription?.pause(); response.add(event); // 等待数据刷新到客户端 await response.flush(); // 恢复订阅处理下一块 subscription?.resume(); }, onError: (e) async { await response.close(); }, onDone: () async { await response.close(); }, ); // 客户端主动断开时取消流订阅 response.done.then((_) => subscription?.cancel());
注意:非必要不使用这种手动写法,
addStream内部已经实现了完整的背压、异常处理逻辑,稳定性更高。
内容的提问来源于stack exchange,提问作者Gijs Withagen
相关产品推荐
相关产品推荐

