Retrofit+RxJava多图以及文字同时上传

时间:2021-12-02 17:44:57


学习后写了自己的实现,地址:https://github.com/yanghuiyu39/retrofix_rejava

根据http/1.1 rfc 2616的协议规定,我们的请求方式只有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE等,那为为何我们还会有multipart/form-data请求之说呢?这里简要说明下。

http协议大家都知道是规定了以ASCII码传输,建立在tcp、ip协议之上的应用层规范,规范内容把http请求分为3个部门:请求方法 URI 协议/版本,请求头,请求正文。所有的方法、实现都是围绕如何运用和组织这三部分来完成的。

也就是说http协议原始方法不支持multipart/form-data请求,那这个请求自然就是由这些原始的方法演变而来的,具体如何演变如下:

1、multipart/form-data的基础方法是post,也就是说是由post方法来组合实现的 
2、multipart/form-data与post方法的不同之处:请求头,请求体。 
3、multipart/form-data的请求头必须包含一个特殊的头信息:Content-Type,且其值也必须规定为multipart/form-data,同时还需要规定一个内容分割符用于分割请求体中的多个post的内容,如文件内容和文本内容自然需要分割开来,不然接收方就无法正常解析和还原这个文件了。具体的头信息如下:

<code class="hljs haskell has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Content</span>-<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Type</span>: multipart/form-<span class="hljs-typedef" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">data</span>; boundary=$<span class="hljs-container" style="box-sizing: border-box;">{<span class="hljs-title" style="box-sizing: border-box;">bound</span>}</span>  </span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

//其中${bound} 是一个占位符,代表我们规定的分割符,可以自己任意规定,但为了避免和正常文本重复了,尽量要使用复杂一点的内容。如:——————–56423498738365

4、multipart/form-data的请求体也是一个字符串,不过和post的请求体不同的是它的构造方式,post是简单的name=value值连接,而multipart/form-data则是添加了分隔符等内容的构造体。具体格式如下:

Retrofit+RxJava多图以及文字同时上传

其中${bound}为之前头信息中的分割符,如果头信息中规定为123,那么这里也要为123,;可以很容易看出,这个请求体是多个相同的部分组成的:每一个部分都是以–加分隔符开始的,然后是该部分内容的描述信息,然后一个回车,然后是描述信息的具体内容;如果传送的内容是一个文件的话,那么还会包含文件名信息,以及文件内容的类型。上面的第二个小部分其实是一个文件体的结构,最后会以–分割符–结尾,表示请求体结束。

通过上面分析,可以知道要发送一个multipart/form-data的请求,其实任何支持post请求的工具或语言都可以支持,只是自己要稍微包装一下便可。同样,《自己动手写HTTP框架》里面的HTTP框架也是这么实现的,具体可以看看代码。

下面我们结合具体的接口来分析multipart/form-data的请求。

抓包分析

课程中上传图片相关代码如下图所示:

Retrofit+RxJava多图以及文字同时上传

从上面的代码中可以看出,把图片放在了列表中,图片描述放在了request.content中。

通过对该方法运行时的网络请求抓包分析如下:

Retrofit+RxJava多图以及文字同时上传

返回结果抓包分析如下:

Retrofit+RxJava多图以及文字同时上传

从上图Contents项中可以看到有两个关键字段,分别是data和file0字段。

这两个字段是怎么产生的呢?

通过查看《自己动手写HTTP框架》相关代码,有如下方法:

Retrofit+RxJava多图以及文字同时上传

这个是单张图片上传,紧接着看多张图片上传,代码如下:

Retrofit+RxJava多图以及文字同时上传

通过对上面两段代码的比较,发现主区别在这个地方:

Retrofit+RxJava多图以及文字同时上传

这个地方也是我们使用Retrofit上传的关键点所在,后面我们会再提到。

上面分析了这么多,我们来看看怎么使用retrofit来实现。

Retrofit实现文件和图片一起上传

如果对retrofit不是很了解,参考:初识Retrofit

定义接口

在码小白的博客 Retrofit 2.0 超能实践,轻松实现多文件/图片上传 中有下面内容:

图片和字符串同时上报

Retrofit+RxJava多图以及文字同时上传

这种接口应该也是可以的,具体要怎么实现,都要与服务器接口保持一致,所以不能照搬照抄了。

根据对有心课堂提供的上传图片接口的大量抓包和测试总结,接口定义如下:

Retrofit+RxJava多图以及文字同时上传

这里用到了@Partmap注解,将图片文件信息放入map中。

准备图片

在sdcard根目录存放两张图片,分别为test.png和test.jpg(不要是gif图片啊,服务器不支持)

代码实现

这里就不贴代码了,截图如下(如果看不清,鼠标右键在新窗口打开就可以看到原图了):

Retrofit+RxJava多图以及文字同时上传

关键代码在于:

Retrofit+RxJava多图以及文字同时上传

看到这个是不是想起了上面我们提到的关键代码呢?下面再贴出来我们对比下。

Retrofit+RxJava多图以及文字同时上传

只要将对应的http请求头信息填写正确,就能上传成功。

那么问题又来了,怎么分析和正确拼写这个请求头呢?

在文章开头的时候有个抓包信息:

<code class="hljs lasso has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">Content<span class="hljs-attribute" style="box-sizing: border-box;">-Disposition</span>: form<span class="hljs-attribute" style="box-sizing: border-box;">-data</span>; name<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span><span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"file0"</span>; filename<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span><span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"test.png"</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

实质上上传文件Requestbody对应的请求头就是 name=”file0”; filename=”test.png”,只要拼对了就没有问题了。

注意:

  1. name=”file0”; filename=”test.png”这个请求头是根据有心课堂提供的上传接口写的,不适用其他上传接口,但原理是类似的;
  2. 单张图片上传通用的请求头是:name=”file”; filename=”test.png”
  3. filename=”test.png”这个一般是指(你希望)保存在服务器的文件名字。

举例说明

比如我们这样写请求头信息,如下代码所示:

Retrofit+RxJava多图以及文字同时上传

运行请求抓包请求头信息如下图所示:

Retrofit+RxJava多图以及文字同时上传

出现了name=”name=”file1”这样的字段,拼接错误(不用加name字段),服务器也毫不留情的返回了错误:

Retrofit+RxJava多图以及文字同时上传

这个问题我当初没有发现,后来还是请教了Stay才搞明白了。

好了,不知道我讲的大家明白了没有,最后来个成功运行的请求抓包截图吧:

Retrofit+RxJava多图以及文字同时上传

关于文字类参数上传

写到最后忘了说文字参数了,文字参数相对文件来说容易些。

在接口中,我们有一个文字参数 @Part("data") String des,如果你需要多个,增加就行了。需要注意的是这个参数的名字比如”data”,不是前端自定义,而是后台定义的。


原文:http://blog.csdn.net/jdsjlzx/article/details/52246114