如何使用 Retrofit 发送多部分/表单数据?
Posted
技术标签:
【中文标题】如何使用 Retrofit 发送多部分/表单数据?【英文标题】:How to send multipart/form-data with Retrofit? 【发布时间】:2015-06-23 04:20:14 【问题描述】:我想从 android 客户端向 REST 服务器发送 Article。这是来自服务器的 Python 模型:
class Article(models.Model):
author = models.CharField(max_length=256, blank=False)
photo = models.ImageField()
下面的接口描述了之前的实现:
@POST("/api/v1/articles/")
public Observable<CreateArticleResponse> createArticle(
@Body Article article
);
现在我想发送带有 Article 数据的图像。 photo
不是 Android 客户端上的 Article 模型的一部分。
@Multipart
@POST("/api/v1/articles/")
public Observable<CreateArticleResponse> createArticle(
@Part("article") Article article,
@Part("photo") TypedFile photo
);
API 已准备好并已使用 cURL 成功测试。
$ curl -vX POST http://localhost:8000/api/v1/articles/ \
-H "Content-Type: multipart/form-data" \
-H "Accept:application/json" \
-F "author=cURL" \
-F "photo=@/home/user/Desktop/article-photo.png"
当我从 Android 客户端通过createArticle()
发送数据时,我收到HTTP 400
状态,指出字段是必需的/缺少的。
D <--- HTTP 400 http://192.168.1.1/articles/ (2670ms)
D Date: Mon, 20 Apr 2015 12:00:00 GMT
D Server: WSGIServer/0.1 Python/2.7.8
D Vary: Accept, Cookie
D X-Frame-Options: SAMEORIGIN
D Content-Type: application/json
D Allow: GET, POST, HEAD, OPTIONS
D OkHttp-Selected-Protocol: http/1.0
D OkHttp-Sent-Millis: 1429545450469
D OkHttp-Received-Millis: 1429545453120
D "author":["This field is required."],"photo":["No file was submitted."]
D <--- END HTTP (166-byte body)
E 400 BAD REQUEST
这是在服务器端收到的request.data
:
ipdb> print request.data
<QueryDict: u'article': [u'"author":"me"'], \
u'photo': [<TemporaryUploadedFile: IMG_1759215522.jpg \
(multipart/form-data)>]>
如何将 Article 对象转换为多部分一致性数据类型?我读到Retrofit 可能允许为此使用Converters。据我对documentation 的理解,它应该是实现retrofit.mime.TypedOutput
的东西。
Multipart 部分使用
RestAdapter
的转换器,或者他们可以实现TypedOutput
来处理自己的序列化。
相关
html 4.01 Specification - Form submission - multipart/form-data Retrofit Annotation Type Part documentation Upload multipart image data in JSON with Retrofit? REST - HTTP Post Multipart with JSON Retrofit Multipart Upload Image failed Retrofit issue #178: Create manual for sending files with retrofit Retrofit issue #531: Problem uploading file via POST/Multipart Retrofit issue #658: Not able to send string parameters with image when using Multipart Retrofit issue #662: Retrofit Form Encoded and Multipart in single request【问题讨论】:
不就是可以使用的TypedFile类吗? 您的方法似乎没问题。为什么不在RestAdapter
上启用日志记录并准确检查正在发送的数据。
@user2511882 你的意思是我应该对 JSON 数据 (article
) 和图像都使用 TypedFile
吗?请指出如何转换数据。 / @corsair992 我更新了我的帖子。
您确定需要多部分请求吗?我已经看到一些服务器期望 JSON 对象作为 POST 主体,图像二进制数据作为字符串发布。像这样:"author":"authorNameHere","photo":"base64ImageBytesHere"]
。如果是这种情况,我可以提供有关如何使用 Okio 和 Retrofit 执行此操作的代码。
我刚刚看到你有工作的 curl 请求,请你发布它吗?
【参考方案1】:
根据您的 curl 请求,您正尝试像这样创建 smth:
POST http://localhost:8000/api/v1/articles/ HTTP/1.1
User-Agent: curl/7.30.0
Host: localhost
Connection: Keep-Alive
Accept: application/json
Content-Length: 183431
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------23473c7acabb
------------------------------23473c7acabb
Content-Disposition: form-data; name="author"
cURL
------------------------------23473c7acabb
Content-Disposition: form-data; name="photo"; filename="article-photo.png"
Content-Type: application/octet-stream
‰PNG
<!RAW BYTES HERE!>
M\UUÕ+4qUUU¯°WUUU¿×ß¿þ Naa…k¿ IEND®B`‚
------------------------------23473c7acabb--
使用改造适配器可以通过以下方式创建此请求:
@Multipart
@POST("/api/v1/articles/")
Observable<Response> uploadFile(@Part("author") TypedString authorString,
@Part("photo") TypedFile photoFile);
用法:
TypedString author = new TypedString("cURL");
File photoFile = new File("/home/user/Desktop/article-photo.png");
TypedFile photoTypedFile = new TypedFile("image/*", photoFile);
retrofitAdapter.uploadFile(author, photoTypedFile)
.subscribe(<...>);
这会产生类似的输出:
POST http://localhost:8000/api/v1/articles/ HTTP/1.1
Content-Type: multipart/form-data; boundary=32230279-83af-4480-abfc-88a880b21b19
Content-Length: 709
Host: localhost
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.3.0
--32230279-83af-4480-abfc-88a880b21b19
Content-Disposition: form-data; name="author"
Content-Type: text/plain; charset=UTF-8
Content-Length: 4
Content-Transfer-Encoding: binary
cUrl
--32230279-83af-4480-abfc-88a880b21b19
Content-Disposition: form-data; name="photo"; filename="article-photo.png"
Content-Type: image/*
Content-Length: 254
Content-Transfer-Encoding: binary
<!RAW BYTES HERE!>
--32230279-83af-4480-abfc-88a880b21b19--
这里的关键区别在于您使用 POJO Article article
作为多部分参数,默认情况下由 Converter
转换为 json。而您的服务器则需要纯字符串。使用 curl,您发送的是 cURL
,而不是 "author":"cURL"
。
【讨论】:
是否可以一次将整个 Article 模型转换为Typed..
并将其传递给 API,而不是拉出每个成员?也许转换器可以做到这一点?
是的,当然。我还没有见过这样的转换器,您可能想要编写它并根据您的需要进行定制。样本方向:gist.github.com/plastiv/08538a095d2d35acab05.
供参考:我提出了MultipartConverter as a feature for Retrofit。【参考方案2】:
服务器需要一个“作者”字符串,但您试图向它传递一个“文章”对象。传递“字符串作者”而不是“文章文章”。
另外,我认为“未提交文件”错误是一个红鲱鱼,因为该文件显然存在于您的“request.data”中。
【讨论】:
以上是关于如何使用 Retrofit 发送多部分/表单数据?的主要内容,如果未能解决你的问题,请参考以下文章
Retrofit - @Body 参数不能与表单或多部分编码一起使用
如何在 Jetty HttpClient 中进行多部分/表单数据发布
如何在邮递员的同一请求中发送多部分/表单数据和嵌套 json?
如何将多部分/表单数据从 android 发送到 Web 服务器?