Flutter Retrofit 上传分段会抛出 415 不支持的媒体类型

问题描述 投票:0回答:3

我正在尝试使用此代码使用 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 中正常工作。

flutter retrofit multipart dio
3个回答
1
投票

我通过将文件解析为 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,
    );
  }
}

0
投票

我找到了问题的答案。有人在 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"}),
    );

0
投票

如果您想放弃改造代码生成,则接受的答案是可以的。但如果您仍然想继续使用 Retrofit 和 dio,只需导入您定义 @Multipart 的文件即可。

import 'package:http_parser/http_parser.dart';

您仍然可以将内容类型保留为 contentType: "application/octet-stream" 如下:

@Part(contentType: "application/octet-stream") File file,

这样,改造代码生成引发的错误将消失,因为 MediaType 来自 http_parser 库。

© www.soinside.com 2019 - 2024. All rights reserved.