文件下载由原来重定向改为HTTP请求Responses.Body返回文件byte内容
这里解释下为什么我会选择重定向来做文件的获取:
假如我们通过FastFileStorageClient的接口来获取100MB的文件,文件会以比特流的方式储存在服务器内存中,考虑到带宽问题,这个文件在HTTP请求正常请求且客户端没读取超时的情况下会一直占用100MB内存,这也只是一个下载请求。若加上断点续传,这100MB的文件可能会被多次加载在内存里,以BitComet为例:
HTTP请求来获取该文件,分别是0-20MB、20-40MB、40-60MB、60-80MB、80-100MB。Range分别为这些范围的HTTP请求,并且在不同的线程内通过FastFileStorageClient的接口来获取这100MB大小的文件,并加载到内存中,最后通过请求头Range参数来返回指定范围的比特流信息。这时我发现现有的下载代码逻辑在生产上是完全无法使用的,根本承受不住轻微的并发,并且对服务器内存要求是极高的,迫切需要一种以缓冲流方式加载文件的方法来获取返回文件,恰巧FastDFS的fastdfs-nginx-module支持,我就直接通过重定向的方法让fastdfs-nginx-module返回文件比特流,解决了文件下载的内存问题,并且减少了将完整文件加载至内存的过程减少了文件下载的等待时间。
原重定向的方法因FastDFS服务器支持断点续传,现断点续传需要通过代码实现
以Chrome浏览器获取视频为例(只要遵循HTTP1.1协议即可):
浏览器首先发送一个简单GET请求,服务端返回200状态:
General:
xxxxxxxxxx51Request URL: http://localhost:20003/rangeTest2Request Method: GET3Status Code: 200 4Remote Address: [::1]:200035Referrer Policy: no-referrer-when-downgradeRequset Headers:
xxxxxxxxxx81Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b32Accept-Encoding: gzip, deflate, br3Accept-Language: zh-CN,zh;q=0.94Connection: keep-alive5DNT: 16Host: localhost:200037Upgrade-Insecure-Requests: 18User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36Response Headers:
xxxxxxxxxx91 Accept-Ranges: bytes2 Content-Disposition: inline;filename="TestVideo.mp4"3 Content-Length: 235086994 Content-Range: bytes 0-23508698/235086995 Content-Type: video/mp46 Date: Thu, 27 Jun 2019 01:52:35 GMT7 ETag: TestVideo.mp48 Expires: Thu, 04 Jul 2019 01:52:35 GMT9 Last-Modified: Fri, 15 Mar 2019 08:44:44 GMT在这里浏览器判断服务器支不支持范围请求(断点续传)主要是看Response Headers是否有Accept-Ranges信息,且值不为none(Accept-Ranges为禁止修改的HTTP头默认值为:bytes),因此要实现断点续传功能首先服务器要在Response Headers返回Accept-Ranges: bytes告诉客户端服务端支持断点续传。
其中Content-Type、Accept-Ranges、Content-Disposition、Content-Length为文件有效信息:
Content-Type: video/mp4:文件类型为MP4Content-Disposition: inline;filename="TestVideo.mp4":以内联方式处理比特流、且文件名为TestVideo.mp4Content-Length: 23508699:当前请求将返回的文件比特流大小Content-Range: bytes 0-23508698/23508699:当前请求将返回文件完整的比特范围,这里是返回完整文件经测试发现,Chrome浏览器及BitComet下载器在获取文件前都会做一次这样的简单GET请求来判断服务器是否支持范。但是,值得注意的是,客户端并不会完整的接收完本次简单请求的比特流数据,而是在解析请求头中Accept-Ranges是否存在且值是否为bytes后马上主动断开连接,之后再根据客户端的设置进行范围请求,因此服务端无法悉知每一个过来的请求是获取完整文件还是仅仅为了识别服务器是否支持断点续传。
经过一次简单GET请求后,客户端根据Response Headers的Accept-Ranges知道服务端是否支持断点续传后会马上创建一个或多个GET请求,但这些GET请求与第一次GET请求在Requset Headers有不同,并且服务端返回206状态:
General:
xxxxxxxxxx51Request URL: http://localhost:20003/rangeTest2Request Method: GET3Status Code: 206 4Remote Address: [::1]:200035Referrer Policy: no-referrer-when-downgradeRequset Headers:
xxxxxxxxxx51Accept-Encoding: identity;q=1, *;q=02DNT: 13Range: bytes=0-4Referer: http://localhost:20003/rangeTest5User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Response Headers:
xxxxxxxxxx91Accept-Ranges: bytes2Content-Disposition: inline;filename="TestVideo.mp4"3Content-Length: 235086994Content-Range: bytes 0-23508698/235086995Content-Type: video/mp46Date: Thu, 27 Jun 2019 01:52:35 GMT7ETag: TestVideo.mp48Expires: Thu, 04 Jul 2019 01:52:35 GMT9Last-Modified: Fri, 15 Mar 2019 08:44:44 GMT
根据以上请求头,关键点在于Range,该字段告诉服务端此次请求应该返回文件的比特流范围(bytes=0-表示从0开始直至文件比特数组下标结束,也就是完整文件,详见)。
此时服务端应在解析完Range: bytes=0-后获取对应的文件比特流信息返回给客户端,并且响应状态为206,假如所请求的范围不合法,那么服务器应响应状态416。
若请求的范围合法,服务端应获取对应文件片段(也可能是完整文件),设置请求头Content-Range、Content-Length、Content-Type等头信息后返回比特流给客户端。
至此,一个基于HTTP1.1协议的范围请求(断点续传)结束了。
所有文件下载接口进来应先解析请求中的参数,从数据库获取FileInfo(含有文件大小、Hash值及FastDFS路径信息)及UserFileInfo(含有文件名)的信息:
ResponseStatusException异常,并设置HttpStatus.NOT_FOUND!解析Requset Headers中的Accept、Range等信息:
根据Requset Headers中的Range设置Response Headers
根据Requset Headers中的Accept设置Response Headers的Content-Disposition值(详见)
最后设置Content-Length、Content-Range、Content-Type并返回客户端比特流,在这里有一点需要注意下:
java.io.IOException: 你的主机中的软件中止了一个已建立的连接。异常信息,在这里我们统一捕获不处理,仅输出warn级别日志信息