采用Retrofit的PUT方式上传文件到apache

概述

前段时间搭建了FastDFS用作文件存储,既然是文件存储,必然需要有文件才能存储。文件可能是由客户端传递上去,可以是视频、也可以是图片等。现在需要提供一个Android端传递视频文件的功能,一说到这,大家肯定想说,okhttp现实一个post表单就搞定了,但是post表单是需要服务端进行接收流,然后采用文件IO方式输出成视频,但这次我打算使用PUT方式上传一个文件。

Apache搭建和配置

搭建

传统的方式,网上应该有很多,客户端POST文件并用servlet作为服务器接收。这次介绍PUT方式,首先需要一个容器能作为文件存储的地址,选择使用Apache Http Server作为服务器,可以去官网下载http://httpd.apache.org/ ,最好安装在Linux。由于公司分配的虚拟机就包含了这个功能,所以就不给大家演示怎么安装Apache Http Server,应该跟Tomcat差不多。

配置

Apache默认是不支持PUT方式的,现在要配置。

vim /etc/httpd/conf/httpd.conf

添加下面两行,如果有就不必添加,一般都是有的。

LoadModule dav_module modules/mod_dav.so
LoadModule dav_fs_module modules/mod_dav_fs.so

配置一个接收文件的路径,由于apache有一个默认路径/var/www/html,所以我直接在这个路径下加入以下配置。
这里写图片描述
别忘了设置监听端口,默认是80。
这里写图片描述

<Directory "/var/www/html/video">
    Dav On
    AllowOverride None
    Options All
    Order allow,deny
    Allow from all
</Directory>

到该/var/www/html目录下,创建一个video文件夹,并增加可写权限。

mkdir video
chmod 777 video -R

这里写图片描述

启动httpd服务,已经启动了可以重启,并查看运行状态。

service httpd start
service httpd restart
service httpd status

现在可以开始测试

curl --request PUT --data-binary @/root/install/1.mp4 --header "Content-Type: application/octet-stream" http://172.16.0.245:8200/video/mf.mp4

这里写图片描述

可以看到将1.mp4这个视频上传到apache服务器上,并且改名为mf.mp4。打开网页浏览http://172.16.0.245:8200/video/ 就能看到这个视频。
这里写图片描述

到这里,apache http就搭建好了,接下来要讲本文重点,android端使用retrofit2+rxjava+okhttp调用put请求上传视频。

Android上传视频

在这里,记录一下遇到的坑,如果对retrofit+rxjava还不是很熟的可以先去了解一下。
首先看一下接口服务类。

public interface RestService {

    /**
     * 没有使用,只是拿出来作对比
     */
    @Multipart
    @POST()
    Observable<ResponseBody> postFormVideoFile(@Url String url, @Part MultipartBody.Part file);

    @Multipart
    @PUT()
    Observable<ResponseBody> putFormVideoFile(@Url String url, @Part MultipartBody.Part file);


    @PUT()
    Observable<ResponseBody> putBodyVideoFile(@Url String url, @Body RequestBody file);
}

分别说明一下三个方法的作用:

  1. postFormVideoFile(),这应该是经常使用的上传文件方式,Post提交表单,但需要有一个服务器接收文件。
  2. putFormVideoFile(),公司需要,直接采用PUT方式推送到apache上,上面已经搭建好环境了,然后我按照Post表单的形式改成Put表单形式,看上去很美好,其实这是个大坑。
  3. putBodyVideoFile(),为了解决上面那个方式的坑,直接采用PUT请求体的方式(一个最基本却容易被遗忘的方式)。

这里写图片描述

putFormVideoFile

先介绍一下put表单的方式,为了找到原因,抓包分析。
Linux中执行

tcpdump -i any host 172.16.0.245 and port 8200 -w ./putForm.pcap

然后点击第二个按钮进行上传。
这里写图片描述

上传完成可以看到,的确生成了一个putForm.mp4文件,此时可以看到文件大小不对,android上显示的是10652806字节,上传文件的大小10653018字节,源视频大小就是10652806,可是android端显示的是没错,怎么传上去就有问题了。注意刚刚我们通过抓包生成了putForm.pcap,等会会分析抓包内容,和android端监听进度的方式来说明。
这里写图片描述

putBodyVideoFile

同样,先抓包。

tcpdump  -i any host 172.16.0.245 and port 8200 -w ./putBody.pcap

然后点击第一个按钮进行上传。过程跟上面的git图一样,但多了两个文件。这时可以看到新多出两个文件。
这里写图片描述
其中putBody.mp4能正常播放,而putForm.mp4就不能播放,比较两者的抓包数据。

这里写图片描述

1.putBody.pcap中,只有一个消息头,里面直接给出了 Content-Length:10652806,这个就是源视频文件大小。再看一下这个方法,第二个参数以消息体的方式加入到http协议中,apache能直接将这个数据导出生成putBody.mp4。

    @PUT()
    Observable<ResponseBody> putBodyVideoFile(@Url String url, @Body RequestBody file);

2.putForm.pcap中,包含两个部分,一个是http消息头,一个是Part,这是很标准的表单提交消息体。首先生成了一个 boundary 用于分割不同的字段,用作每个Part的分割线,每一个Part可以表示为一个文件数据(从方法中也能看到第二个参数是一个Part)。Part中的Content-Length:10652806才是表示这个数据的正确大小,Http消息头中的Content-Length:10653018表示传输长度,将这个数据导出生成putForm.mp4是一个无效的mp4文件,也就不能播放。

    @Multipart
    @PUT()
    Observable<ResponseBody> putFormVideoFile(@Url String url, @Part MultipartBody.Part file);

至于为什么两个方法调用的时候,demo上显示的上传大小都是10653018。CountingRequestBody是继承RequestBody,表示一个请求体,重写了contentLength(),表示这个文件的长度,而界面上显示的最大值,其实只是这个Body的大小,并不是http传输长度。

private static MediaType MEDIA_TYPE_PLAIN = MediaType.parse("multipart/form-data");
requestBody = RequestBody.create(MEDIA_TYPE_PLAIN, file);
public class CountingRequestBody extends RequestBody {
    //实际的待包装请求体
    protected RequestBody delegate;
    //进度回调接口
    protected Listener listener;

    protected CountingSink countingSink;

    public CountingRequestBody(RequestBody delegate, Listener listener) {
        this.delegate = delegate;
        this.listener = listener;
    }

    /**
     * 重写调用实际的响应体的contentLength
     * @return contentLength
     * @throws IOException 异常
     */
    @Override
    public long contentLength() {
        try {
            return delegate.contentLength();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }

小结

一开始,想着效仿post表单的形式改为put表单,结果一路是坑,后来在网上发现一个用okhttp实现put方式能达到我的目的,对照那个demo和抓包才找到原因。总结一下:
1. 采用表单的形式提交文件,最好是以post请求发送到服务器,由服务器进行接收处理。
2. 采用PUT方式,则直接用RequestBody的方式包装文件,并以二进制流的形式传输。
写次博客主要是为了做个记录,给自己一个做笔记的地方。文中对Http协议以及对上传文件的方式的理解感觉不是很到位,不足之处,希望能给出一些意见。
源码demo

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Retrofit2是一个流行的Android网络框架,可以通过注解和动态代理来简化网络请求的步骤。在使用Retrofit2进行文件上传时,可以使用@Multipart注解和@PartMap注解来实现多文件上传。\[1\] 具体的实现步骤如下: 1. 在Retrofit接口中使用@Multipart注解标记该方法为多部分请求。 2. 使用@POST注解指定上传文件的URL。 3. 使用@PartMap注解将文件参数以Map的形式传递给方法。 4. 在Map中,键是参数的名称,值是RequestBody类型的文件内容。 例如,可以使用以下代码来实现文件上传: ``` @Multipart @POST("upload") Observable<String> uploadFiles(@PartMap Map<String, RequestBody> files); ``` 其中,files是一个Map,键是文件参数的名称,值是RequestBody类型的文件内容。 Retrofit2之所以被广泛使用,是因为它能够根据注解封装网络请求,并通过转化器将原始的HTTP响应内容转化为我们需要的对象。这使得使用Retrofit2非常方便。\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Retrofit2 multpart多文件上传详解](https://blog.csdn.net/jdsjlzx/article/details/51649382)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [retrofit2上传文件总结](https://blog.csdn.net/u011323666/article/details/52288753)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值