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
相关产品推荐
相关产品推荐

