我正在尝试使用此代码使用 Flutter Retrofit 中的 Multipart 上传图像
@POST("apiUrl/upload/files")
@MultiPart()
Future<TSSuccessResponse<UploadFileRemoteResponse>> uploadFile(
@Part() File file);
然而,它不断收到错误 415,表示不支持 application/form-octet MediaType。
E/flutter ( 8367): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioError [bad response]: The request returned an invalid status code of 415.
E/flutter ( 8367): #0 DioMixin.fetch.<anonymous closure> (package:dio/src/dio_mixin.dart:530:7)
E/flutter ( 8367): #1 _RootZone.runBinary (dart:async/zone.dart:1665:54)
E/flutter ( 8367): #2 _FutureListener.handleError (dart:async/future_impl.dart:162:22)
E/flutter ( 8367): #3 Future._propagateToListeners.handleError (dart:async/future_impl.dart:779:47)
E/flutter ( 8367): #4 Future._propagateToListeners (dart:async/future_impl.dart:800:13)
E/flutter ( 8367): #5 Future._completeError (dart:async/future_impl.dart:575:5)
E/flutter ( 8367): #6 _SyncCompleter._completeError (dart:async/future_impl.dart:51:12)
E/flutter ( 8367): #7 _Completer.completeError (dart:async/future_impl.dart:23:5)
E/flutter ( 8367): #8 Future.any.onError (dart:async/future.dart:617:45)
E/flutter ( 8367): #9 _RootZone.runBinary (dart:async/zone.dart:1665:54)
E/flutter ( 8367): #10 _FutureListener.handleError (dart:async/future_impl.dart:162:22)
E/flutter ( 8367): #11 Future._propagateToListeners.handleError (dart:async/future_impl.dart:779:47)
E/flutter ( 8367): #12 Future._propagateToListeners (dart:async/future_impl.dart:800:13)
E/flutter ( 8367): #13 Future._completeError (dart:async/future_impl.dart:575:5)
E/flutter ( 8367): #14 Future._asyncCompleteError.<anonymous closure> (dart:async/future_impl.dart:666:7)
E/flutter ( 8367): #15 _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter ( 8367): #16 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
I/flutter ( 8367): ╔ DioErrorType.badResponse
I/flutter ( 8367): ║ {
I/flutter ( 8367): ║ statusCode: 415,
I/flutter ( 8367): ║ "File type application/octet-stream is not matching: image, pdf, powerpoint, prese
I/flutter ( 8367): ║ ntation, video, video/quicktime"
I/flutter ( 8367): ║ error: "Unsupported Media Type"
I/flutter ( 8367): ║ }
I/flutter ( 8367): ╚══════════════════════════════════════════════════════════════════════════════════════════╝
然后我尝试更改请求以在 @Part() 标记中包含内容类型,正如有人提到的那样,以便它满足从 Retrofit 生成的文件。
@POST("apiUrl/upload/files")
@MultiPart()
Future<TSSuccessResponse<UploadFileRemoteResponse>> uploadFile(
@Part(contentType: "image/png") File file);
但是它抛出了另一个错误,那就是
E/flutter ( 8367): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioError [bad response]: The request returned an invalid status code of 415.
E/flutter ( 8367): #0 DioMixin.fetch.<anonymous closure> (package:dio/src/dio_mixin.dart:530:7)
E/flutter ( 8367): #1 _RootZone.runBinary (dart:async/zone.dart:1665:54)
E/flutter ( 8367): #2 _FutureListener.handleError (dart:async/future_impl.dart:162:22)
E/flutter ( 8367): #3 Future._propagateToListeners.handleError (dart:async/future_impl.dart:779:47)
E/flutter ( 8367): #4 Future._propagateToListeners (dart:async/future_impl.dart:800:13)
E/flutter ( 8367): #5 Future._completeError (dart:async/future_impl.dart:575:5)
E/flutter ( 8367): #6 _SyncCompleter._completeError (dart:async/future_impl.dart:51:12)
E/flutter ( 8367): #7 _Completer.completeError (dart:async/future_impl.dart:23:5)
E/flutter ( 8367): #8 Future.any.onError (dart:async/future.dart:617:45)
E/flutter ( 8367): #9 _RootZone.runBinary (dart:async/zone.dart:1665:54)
E/flutter ( 8367): #10 _FutureListener.handleError (dart:async/future_impl.dart:162:22)
E/flutter ( 8367): #11 Future._propagateToListeners.handleError (dart:async/future_impl.dart:779:47)
E/flutter ( 8367): #12 Future._propagateToListeners (dart:async/future_impl.dart:800:13)
E/flutter ( 8367): #13 Future._completeError (dart:async/future_impl.dart:575:5)
E/flutter ( 8367): #14 Future._asyncCompleteError.<anonymous closure> (dart:async/future_impl.dart:666:7)
E/flutter ( 8367): #15 _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter ( 8367): #16 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
I/flutter ( 8367): ╔ DioErrorType.badResponse
I/flutter ( 8367): ║ {
I/flutter ( 8367): ║ statusCode: 415,
I/flutter ( 8367): ║ message: "File type is not matching",
I/flutter ( 8367): ║ error: "Unsupported Media Type"
I/flutter ( 8367): ║ }
I/flutter ( 8367):
我还尝试将 contentType 更改为“image/*”,但它在 MediaType.parser(来自生成的文件)上抛出错误,指出它是无效类型。
在这个场景中我有什么遗漏的吗?以前有人解决过这个问题吗?我尝试了[1],[2]和许多其他人提到的解决方案,但仍然陷入这个问题。提前谢谢。
需要注意的一点:此请求在具有相同文件的 Postman 中正常工作。
我通过将文件解析为 MultipartFiles 并动态设置其 contentType 解决了这个问题。我使用 mime 库来确定文件的 contentType。
将文件解析为MultipartFiles的方法:
Future<List<MultipartFile>> filesToMultipartFiles(List<File> files) async {
final multipartFiles = <MultipartFile>[];
for (final file in files) {
final filename = file.path.split('/').last;
final fileBytes = await file.readAsBytes();
List<int> header = [];
for (var element in fileBytes) {
if (element == 0) continue;
header.add(element);
}
final mime = lookupMimeType(filename, headerBytes: header) ?? '';
final mimeParts = mime.split('/');
if (mimeParts.length != 2) throw Exception('Invalid file type');
final multipartFile = MultipartFile.fromBytes(
fileBytes,
filename: file.path.split('/').last,
contentType: MediaType(mimeParts[0], mimeParts[1]),
);
multipartFiles.add(multipartFile);
}
return multipartFiles;
}
API类:
@RestApi()
abstract class _FilesAPI {
factory _FilesAPI(Dio dio) = __FilesAPI;
@POST('/files')
@MultiPart()
Future<List<AppFile>> _uploadFile({
@Part(name: 'files[]') required List<MultipartFile> files,
});
}
class FilesAPI extends __FilesAPI {
FilesAPI(Dio dio) : super(dio);
Future<List<AppFile>> uploadFile({
required List<MultipartFile> files,
}) {
return _uploadFile(
files: files,
);
}
}
我找到了问题的答案。有人在 github 中提出了这个问题,显然根本原因问题是 Dio 在我们未指定任何内容时将 MediaType 设置为
application/octet-stream
。
如果我们像这样从改造中指定它(通过@Part的contentType),
@POST("apiUrl/upload/files")
@MultiPart()
Future<TSSuccessResponse<UploadFileRemoteResponse>> uploadFile(
@Part(contentType: "image/jpg") File file);
Retrofit 生成的文件中的以下行仍会抛出错误,因为它与模式不匹配。
MediaType.parse(contentType)
我的解决方案是使用普通的 Dio 请求,然后按照前面的 github 链接的建议,使用 pub.dev 中的 mime 包指定 mime 类型。
import 'package:mime/mime.dart';
FormData data = FormData.fromMap({
"file": await MultipartFile.fromFile(file.path,
filename: fileName,
contentType: MediaType.parse("${lookupMimeType(fileName)}")),
});
final response = await dio.post(
"api/uploadFile",
data: data,
options: Options(headers: {"Content-Type": "multipart/form-data"}),
);
如果您想放弃改造代码生成,则接受的答案是可以的。但如果您仍然想继续使用 Retrofit 和 dio,只需导入您定义 @Multipart 的文件即可。
import 'package:http_parser/http_parser.dart';
您仍然可以将内容类型保留为 contentType: "application/octet-stream" 如下:
@Part(contentType: "application/octet-stream") File file,
这样,改造代码生成引发的错误将消失,因为 MediaType 来自 http_parser 库。