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

Flutter Web:Token过期后重发含MultipartFile的MultipartRequest失败解决方案

Flutter Web文件上传Token过期后重发请求失败问题解决

我在Flutter Web中使用http包上传文件,Token有效期间一切正常,但Token在API请求过程中过期时,刷新Token后重新发送相同请求却出现错误,第二次请求并未调用API。以下是我尝试的两种方法及报错信息:

方法1(递归调用)

Future uploadFileWeb(PlatformFile file, int type) async {
try {
  String? curTokVar = await .......;
  AccessToken accessToken = AccessToken.fromJson(jsonDecode(curTokVar!));
  //---Create http package multipart request object
  final request = httpApi.MultipartRequest(
    "POST",
    Uri.parse("...../public/uploadFileWeb"),
  );
  //-----add other fields if needed
  request.fields["type"] = type.toString();
  //-----add selected file with request
  request.files.add(httpApi.MultipartFile("file", file.readStream!, file.size, filename: file.name));
  request.headers.addAll({'Authorization': 'bearer ${accessToken.token!}',},);
  //-------Send request
  var resp = await request.send();
  //------Read response
  String result = await resp.stream.bytesToString();
  if (resp.statusCode == 200) {
    return jsonDecode(result);
  } else if (resp.statusCode == 401) {
    debugPrint(' Token Expired.Refresh and Recall the request');
    //-----updating token
    bool tokenStatus = await updateToken(accessToken);
    if(tokenStatus) {
        //----- recalling the same request with updated token
      return await uploadFileWeb(file,type);  // this throws error when it execute, not working
    }
    throw AccessTokenException(message: "Token invalid or expired");
  }
} catch (error) {
  throw Error();
}
}

方法2(复制请求)

// 原方法中401分支的代码
else if (resp.statusCode == 401) {
    debugPrint(' Token Expired.Refresh and Recall the request');
    //-----updating token
    bool tokenStatus = await updateToken(accessToken);
    if(tokenStatus) {
        //----- recalling the same request with updated token
      curTokVar = await .......;
      accessToken = AccessToken.fromJson(jsonDecode(curTokVar!));
      var copyRequest = _copyMultipartRequest(request);
      copyRequest.headers.addAll({'Authorization': 'bearer ${accessToken.token!}',},);
      resp = await copyRequest.send();  // NOT GETTING CALLED ON SERVER,ie, NOT EXECUTING
      debugPrint('resp.statusCode  :: ${resp.statusCode}');
      result = await resp.stream.bytesToString();
      debugPrint('  ###  result  :: $result');
      if (resp.statusCode == 200) {
        return jsonDecode(result);
      }
      throw Error();
    }
    throw AccessTokenException(message: "Token invalid or expired");
  }

// 复制请求的方法
httpApi.MultipartRequest _copyMultipartRequest(httpApi.MultipartRequest request) {
httpApi.MultipartRequest requestCopy = httpApi.MultipartRequest(request.method, request.url)
  ..fields.addAll(request.fields)
  ..files.addAll(request.files);

requestCopy
  ..persistentConnection = request.persistentConnection
  ..followRedirects = request.followRedirects
  ..maxRedirects = request.maxRedirects
  ..headers.addAll(request.headers);

return requestCopy;
}

报错信息

Uncaught (in promise) Error: Instance of 'Error'
at Object.throw_ [as throw] (errors.dart:294:49)
at my_api.MyApi.new.uploadFileWeb (my_api.dart:494:7)
at uploadFileWeb.throw (<anonymous>)
at async_patch.dart:60:31
at _RootZone.runBinary (zone.dart:1666:54)
at _FutureListener.thenAwait.handleError (future_impl.dart:171:22)
at handleError (future_impl.dart:852:46)
at _Future._propagateToListeners (future_impl.dart:873:13)
at [_completeError] (future_impl.dart:649:5)
at async._AsyncCallbackEntry.new.callback (future_impl.dart:739:7)
at Object._microtaskLoop (schedule_microtask.dart:40:11)
at _startMicrotaskLoop (schedule_microtask.dart:49:5)
at async_patch.dart:181:15

问题根源

两种方法失败的核心原因是Web环境下PlatformFile的readStream是一次性流,第一次发送请求时流已经被消费并关闭,递归或复制请求时复用了已关闭的流,导致第二次请求无法向服务器传输数据,服务器自然收不到请求。

解决方案

方案1:预读取文件字节(适合小文件)

将文件内容预读取为字节数组,每次请求时用字节数组生成新的流,避免复用已关闭的流:

Future uploadFileWeb(PlatformFile file, int type) async {
  try {
    // 预读取文件字节,保存后可重复使用
    final fileBytes = await file.readAsBytes();
    String? curTokVar = await .......;
    AccessToken accessToken = AccessToken.fromJson(jsonDecode(curTokVar!));
    
    final request = httpApi.MultipartRequest(
      "POST",
      Uri.parse("...../public/uploadFileWeb"),
    );
    request.fields["type"] = type.toString();
    // 用字节数组生成新的MultipartFile,每次请求都能生成全新的流
    request.files.add(httpApi.MultipartFile.fromBytes(
      "file",
      fileBytes,
      filename: file.name,
    ));
    request.headers.addAll({'Authorization': 'bearer ${accessToken.token!}'});
    
    var resp = await request.send();
    String result = await resp.stream.bytesToString();
    
    if (resp.statusCode == 200) {
      return jsonDecode(result);
    } else if (resp.statusCode == 401) {
      debugPrint('Token过期,刷新后重新请求');
      bool tokenStatus = await updateToken(accessToken);
      if(tokenStatus) {
        // 递归调用时,字节数组可重复使用,生成新流
        return await uploadFileWeb(file, type);
      }
      throw AccessTokenException(message: "Token无效或已过期");
    }
  } catch (error) {
    // 抛出原错误而非新建Error,便于排查
    throw error;
  }
}

方案2:封装流生成方法(适合大文件)

如果文件过大不适合预读取字节,可封装一个每次调用都返回新流的方法,确保每次请求都能获取未被消费的流:

// 封装获取文件流的方法,每次调用返回全新的流
Stream<List<int>> _getFileStream(PlatformFile file) {
  // 根据Web环境下的文件读取逻辑实现,比如重新读取文件
  // 示例:如果是从input获取的File对象,可重新创建读取流
  return file.readStream!.restart(); // 若流支持restart,否则自行实现重新读取逻辑
}

Future uploadFileWeb(PlatformFile file, int type) async {
  try {
    String? curTokVar = await .......;
    AccessToken accessToken = AccessToken.fromJson(jsonDecode(curTokVar!));
    
    final request = httpApi.MultipartRequest(
      "POST",
      Uri.parse("...../public/uploadFileWeb"),
    );
    request.fields["type"] = type.toString();
    // 每次请求调用方法获取新流
    request.files.add(httpApi.MultipartFile(
      "file",
      _getFileStream(file),
      file.size,
      filename: file.name,
    ));
    request.headers.addAll({'Authorization': 'bearer ${accessToken.token!}'});
    
    var resp = await request.send();
    String result = await resp.stream.bytesToString();
    
    if (resp.statusCode == 200) {
      return jsonDecode(result);
    } else if (resp.statusCode == 401) {
      debugPrint('Token过期,刷新后重新请求');
      bool tokenStatus = await updateToken(accessToken);
      if(tokenStatus) {
        return await uploadFileWeb(file, type);
      }
      throw AccessTokenException(message: "Token无效或已过期");
    }
  } catch (error) {
    throw error;
  }
}

额外注意事项

  • 不要直接抛出Error(),应传递原错误信息,便于排查问题。
  • 复制请求时(方法2的思路),不能直接复用request.files,必须重新生成包含新流的MultipartFile对象。

内容的提问来源于stack exchange,提问作者KJEjava48

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 05:18:11