You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter流式Multipart请求未完成且引发段故障问题排查

问题分析与解决方案

核心问题定位

  1. Content-Length计算错误:StreamedRequest需要手动设置准确的*Content-Length*,如果计算值小于实际发送的字节数,服务器会一直等待剩余数据,导致请求pending;如果大于实际字节数,服务器会认为数据不完整,同样无法完成请求。
  2. 热重启段错误:请求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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:20:38