Flutter Retrofit上传多部分数据时出现415不支持的媒体类型错误。

huangapple go评论89阅读模式
英文:

Flutter Retrofit upload multipart throws 415 Unsupported Media Type

问题

我正在尝试使用Flutter Retrofit的Multipart功能上传图像,使用以下代码:

  @POST("apiUrl/upload/files")
  @MultiPart()
  Future<TSSuccessResponse<UploadFileRemoteResponse>> uploadFile(
      @Part() File file);

然而,它一直返回错误415,说application/form-octet不支持MediaType。

然后我尝试更改请求,包括在@Part()标签中的content-type,以满足Retrofit生成的文件,如某人提到的。

  @POST("apiUrl/upload/files")
  @MultiPart()
  Future<TSSuccessResponse<UploadFileRemoteResponse>> uploadFile(
      @Part(contentType: "image/png") File file);

但它抛出另一个错误:

DioError [bad response]: The request returned an invalid status code of 415.

我还尝试将contentType更改为"image/*",但它在生成的文件中的MediaType.parser上抛出错误,说它是无效类型。

在这种情况下,我是否遗漏了什么?是否有人在以前解决过这个问题?感谢您的提前帮助。

需要注意的一点是:这个请求在Postman中正常工作,使用相同的文件。

英文:

I'm trying to upload an image using Multipart from Flutter Retrofit using this code

  @POST(&quot;apiUrl/upload/files&quot;)
  @MultiPart()
  Future&lt;TSSuccessResponse&lt;UploadFileRemoteResponse&gt;&gt; uploadFile(
      @Part() File file);

However it keeps getting error 415 that says application/form-octet is not supported 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.&lt;anonymous closure&gt; (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.&lt;anonymous closure&gt; (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): ║         &quot;File type application/octet-stream is not matching: image, pdf, powerpoint, prese
I/flutter ( 8367): ║         ntation, video, video/quicktime&quot;
I/flutter ( 8367): ║         error: &quot;Unsupported Media Type&quot;
I/flutter ( 8367): ║    }
I/flutter ( 8367): ╚══════════════════════════════════════════════════════════════════════════════════════════╝

Then I tried to change the request to include content-type in the @Part() tag as someone mentioned, so that it satisfies the generated file from Retrofit.

  @POST(&quot;apiUrl/upload/files&quot;)
  @MultiPart()
  Future&lt;TSSuccessResponse&lt;UploadFileRemoteResponse&gt;&gt; uploadFile(
      @Part(contentType: &quot;image/png&quot;) File file);

But it throw another error, that is

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.&lt;anonymous closure&gt; (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.&lt;anonymous closure&gt; (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: &quot;File type is not matching&quot;,
I/flutter ( 8367): ║         error: &quot;Unsupported Media Type&quot;
I/flutter ( 8367): ║    }
I/flutter ( 8367): 

I also tried chaging the contentType to "image/*" but it throws error on the MediaType.parser (from the generated file) saying that it's an invalid type.

Is there anything I'm missing in this scenario? Have anyone solved this problem before? I tried solutions mentioned by [1],[2] and many others but still stuck in this problem. Thx in advance.

One thing to note: This request works normally in Postman with the same file.

答案1

得分: 0

我找到了解决我的问题的答案。有人在 github 中提出了这个问题,显然根本原因是 Dio 在没有指定时将 MediaType 设置为 application/octet-stream

如果我们像这样从 Retrofit 中指定它(通过 @Part 的 contentType),

  @POST("apiUrl/upload/files")
  @MultiPart()
  Future<TSSuccessResponse<UploadFileRemoteResponse>> uploadFile(
      @Part(contentType: "image/jpg") File file);

从 Retrofit 生成的文件中的以下行仍然会引发错误,因为它不匹配该模式。

MediaType.parse(contentType)

我的解决方案是使用普通的 Dio 请求,然后使用 pub.dev 上建议的 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"}),
    );
英文:

I've found the answer to my problem. Someone has raised this issue in github, and apparently the root cause problem is Dio set the MediaType to application/octet-stream when we don't specify any.

And if we specify it from retrofit like this (through contentType of the @Part),

  @POST(&quot;apiUrl/upload/files&quot;)
  @MultiPart()
  Future&lt;TSSuccessResponse&lt;UploadFileRemoteResponse&gt;&gt; uploadFile(
      @Part(contentType: &quot;image/jpg&quot;) File file);

the following line in the generated files from Retrofit will still throw an Error since it doesn't match the pattern.

MediaType.parse(contentType)

My solution is to use the normal Dio request then specify the mime type using mime package from pub.dev as per suggested from the preceeding github links.

    import &#39;package:mime/mime.dart&#39;;

    FormData data = FormData.fromMap({
      &quot;file&quot;: await MultipartFile.fromFile(file.path,
          filename: fileName,
          contentType: MediaType.parse(&quot;${lookupMimeType(fileName)}&quot;)),
    });

    final response = await dio.post(
      &quot;api/uploadFile&quot;,
      data: data,
      options: Options(headers: {&quot;Content-Type&quot;: &quot;multipart/form-data&quot;}),
    );

答案2

得分: 0

接受的答案是,如果你想放弃 Retrofit 代码生成的话,可以使用它。但如果你仍然想继续使用 Retrofit 和 Dio,只需在定义 @Multipart 的文件中导入以下内容:

import 'package:http_parser/http_parser.dart';

然后你仍然可以保持 content type 为 contentType: "application/octet-stream",如下所示:

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

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

英文:

The accepted answer is okay if you want to abandon the retrofit code generation. But if you still want to keep using retrofit and dio, just import in the file that you defined the @Multipart.

import &#39;package:http_parser/http_parser.dart&#39;;

and you still can keep content type as contentType: "application/octet-stream" as below:

@Part(contentType: &quot;application/octet-stream&quot;) File file,

This way, the error thrown by retrofit code generation will be gone as MediaType is from http_parser library.

huangapple
  • 本文由 发表于 2023年2月16日 03:37:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/75464681.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定