Flutter流式Multipart请求未完成且引发段故障问题排查
问题分析与解决方案
核心问题定位
- Content-Length计算错误:
StreamedRequest需要手动设置准确的*Content-Length*,如果计算值小于实际发送的字节数,服务器会一直等待剩余数据,导致请求pending;如果大于实际字节数,服务器会认为数据不完整,同样无法完成请求。 - 热重启段错误:请求pending时,流资源未被正确释放,热重启时Flutter引擎回收资源出现冲突,引发段错误。
解决方案步骤
1. 正确构建Multipart请求体并计算Content-Length
手动拼接multipart各部分时,要完整包含边界、字段头、字段内容、文件内容,以及结尾的闭合边界,每一部分的字节数都要计入总长度。
以下是完整的实现代码:
import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; Future<http.StreamedResponse> sendMultipartRequest( String url, Uint8List pdfBytes, String fileName, String documentTitle, List<int> tags, ) async { // 生成随机边界(避免与内容冲突) final boundary = '----${DateTime.now().microsecondsSinceEpoch}'; final contentType = 'multipart/form-data; boundary=$boundary'; // 构建请求体的各个部分 final parts = <List<int>>[]; // 添加普通字段:文件名 parts.add(_buildMultipartField( boundary, 'filename', fileName, )); // 添加普通字段:文档标题 parts.add(_buildMultipartField( boundary, 'title', documentTitle, )); // 添加重复的tags字段(满足API要求的重复key格式) for (final tag in tags) { parts.add(_buildMultipartField( boundary, 'tags', tag.toString(), )); } // 添加文件字段:document(PDF) parts.add(_buildMultipartFile( boundary, 'document', fileName, 'application/pdf', pdfBytes, )); // 添加结尾边界 parts.add(utf8.encode('\r\n--$boundary--\r\n')); // 计算总Content-Length:所有部分的字节数之和 int contentLength = 0; for (final part in parts) { contentLength += part.length; } // 创建StreamedRequest并设置必要参数 final request = http.StreamedRequest('POST', Uri.parse(url)) ..headers['Content-Type'] = contentType ..contentLength = contentLength; // 将所有部分写入请求流 for (final part in parts) { await request.sink.add(part); } // 必须关闭流,否则服务器会一直等待 await request.sink.close(); // 发送请求并返回响应 return await request.send(); } // 构建普通字段的multipart部分 List<int> _buildMultipartField(String boundary, String name, String value) { return utf8.encode(''' --$boundary Content-Disposition: form-data; name="$name" $value '''); } // 构建文件字段的multipart部分 List<int> _buildMultipartFile( String boundary, String name, String fileName, String mimeType, Uint8List fileBytes, ) { final header = utf8.encode(''' --$boundary Content-Disposition: form-data; name="$name"; filename="$fileName" Content-Type: $mimeType '''); return [...header, ...fileBytes, ...utf8.encode('\r\n')]; }
2. 关键注意事项
- 边界格式:每个字段/文件前的边界是
--{boundary},结尾边界是--{boundary}--,必须严格遵循,否则服务器无法解析请求体。 - 换行符规范:字段头与内容之间、文件头与文件内容之间必须用
\r\n分隔,省略会导致格式错误,同时影响*Content-Length*的准确性。 - 流必须关闭:调用
request.sink.close()是触发请求发送的关键,未关闭流会导致服务器一直等待数据,请求持续pending。
3. 解决热重启段错误
在请求pending状态下,热重启前需确保取消请求并释放流资源。可以通过request.abort()主动终止请求:
// 取消请求的示例 _request.abort();
如果在Widget中使用该请求,可在dispose方法中处理资源释放:
late http.StreamedRequest _request; @override void dispose() { // 取消未完成的请求,释放流资源 _request.abort(); super.dispose(); }
这样热重启时,Flutter会先执行dispose逻辑,避免未关闭的流资源引发段错误。
内容的提问来源于stack exchange,提问作者astubenbord
相关产品推荐
相关产品推荐

