下面就来看看HTTP协议里有哪些手段能解决这个问题
最基本的解决方案,就是数据压缩,把大象变成小猪佩奇,再放进冰箱
Accept+Encoding
头字段,里面是浏览器支持的压缩格式列表,比如gzip、deflate、br 等,这样服务器就可以从中选择一种压缩算法,放进Content+Encoding
响应头里,再把原数据压缩后发给浏览器gzip 的压缩率通常能够超过 60%,而 br 算法是专门会 HTML 设计的,压缩效率和性能比 gzip 还要好,能够再提高 20% 的压缩密度。
在数据压缩之外,还有什么办法来解决大文件的问题呢?
这种“化整为零”的思路在HTTP协议中就是chunked
分块传输编码,在响应报文里用字段Transfer-Encoding:chunked
来表示,意思是报文里的body部分不是一次性发过来的,而是分成了很多的块(chunk
)逐个发送
分块传输页可以用于“流式数据”,比如由数据库动态生成的表单页面,这种情况下body数据的长度是未知的,无法再头字段Content-Length
里给出确切的长度,所以也只能用chunked
分块的方式发送
Transfer-Encoding: chunked
和“Content-Length
这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)
为什么分块就意味着长度未知
下面我们来看一下分块传输的编码规则,其实也很简单,同样采用了明文的方式,很类似响应头。
实验环境里的 URI“/16-1”简单地模拟了分块传输,可以用 Chrome 访问这个地址看一下效果:
不过浏览器在收到分块传输的数据后会自动按照规则去掉分块编码,重新组装出内容,所以想要看到服务器发出的原始报文形态就得用 Telnet 手工发送请求(或者用 Wireshark抓包):
因为 Telnet 只是收到响应报文就完事了,不会解析分块数据,所以可以很清楚地看到响应报文里的 chunked 数据格式:先是一行 16 进制长度,然后是数据,然后再是 16 进制长度和数据,如此重复,最后是 0 长度分块结束
有了分块传输编码,服务器就可以轻松的收发大文件了,但对于上G的超大文件,还有一些问题需要考虑。
比如,你在看当下正热播的某穿越剧,想跳过片头,直接看正片,或者有段剧情很无聊,想拖动进度条快进几分钟,这实际上是像获取一个大文件其中的片段数据,而分块传输并没有这个能力。
Accept-Ranges:bytes
明确告知客户端:“我是支持范围请求的”Accept-Ranges:none
,或者干脆不发送Accept-Ranges
字段,这可以客户端就会认为服务器没有实现范围请求的功能,只能收发整块文件了请求头Range
是HTTP范围请求的专用字段,格式是bytes=x-y
,其中的x和y是以字节为单位的数据范围。
Range
的格式也很灵活,起点x和终点y都可以省略,能够很方面的表示正数或者倒数的范围。假设文件是100个字节,那么:
服务器收到Range字段后,需要做四件事:
416
,意思是“你的范围请求有误,我无法处理,请再检查一下”Range
头计算偏移量,读取文件的片段了,返回状态码206 Partial Content
,和200的意思差不多,但表示body只是原数据的一部分Content-Range
,告诉片段的实际偏移量和资源的总大小,格式是bytes x-y/length
,与 Range 头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是bytes 0-10/100
。例如下面的这个请求使用 Range 字段获取了文件的前 32 个字节:
GET /16-2 HTTP/1.1
Host: www.chrono.com
Range: bytes=0-31
有了范围请求之后,HTTP 处理大文件就更加轻松了,看视频时可以根据时间点计算出文件的 Range,不用下载整个文件,直接精确获取片段所在的数据内容。
不仅看视频的拖拽进度需要范围请求,常用的下载工具里的多段下载、断点续传也是基于它实现的,要点是:
刚才说的范围请求一次只获取一个片段,其实它还支持在Range 头里使用多个“x-y”,一次性获取多个片段数据。
这种情况需要使用一种特殊的 MIME 类型:multipart/byteranges
,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数boundary=xxx
来区分不同的片段
- -boundary
开始(前面加两个“-”),Content-Type
和ContentRange
标记这段数据的类型和所在范围- -boundary- -
(前后各有两个-
)表示所有的分段结束。GET /16-2 HTTP/1.1
Host: www.chrono.com
Range: bytes=0-9, 20-29
Transfer-Encoding: chunked
来表示,分块的格式是 16 进制长度头 + 数据块;Range
和响应头字段Content-Range
,响应状态码必须是 206;要注意这四种方法不是互斥的,而是可以混合起来使用,例如压缩后再分块传输,或者分段后再分块
分块传输数据的时候,如果数据里含有回车换行(\r\n)是否会影响分块的处理呢?
不影响,因为分块前有数据长度说明
如果对一个被 gzip 的文件执行范围请求,比如“Range:bytes=10-19”,那么这个范围是应用于原文件还是压缩后的文件呢?
不用想,肯定是原文件。
http交给TCP进行传输的时候本来就会分块,那么http分块还有什么意义呢?
在http层是看不到tcp的,它不知道下层协议是否会分块,下层是否分块对它来说没有意义,不关心
在http里一个报文必须是完整交付,在处理大文件的时候就很不方便,所以就要分块,在http层面方便处理
chunked主要是在http的层次来解决问题
文件上传是根据HTTP协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件。
在上传⼀个⽂件时,需要把 form 标签的enctype
设置为multipart/form-data
,同时method
必须为post
⽅法。那么multipart/form-data表示什么呢?
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN
表示本次请求要上传文件,其中boundary
表示分隔符,如果要上传多个表单项,就要使用boundary
分割,每个表单项由———XXX 开始,以———XXX 结尾。Content-Type
和Content-Disposition
组成。Content-Disposition: form-data
为固定值,表示⼀个表单元素,name 表示表单元素的 名称,回⻋换⾏后⾯就是name的值,如果是上传⽂件就是⽂件的⼆进制内容。Content-Type
:表示当前的内容的 MIME 类型,是图⽚还是⽂本还是⼆进制数据。客户端发送请求到服务器后,服务器会收到请求的消息体,然后对消息体进⾏解析,解析出哪是普通表单哪些是附件。
什么是秒传
通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,
逻辑
什么是分⽚上传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分割成多个数据块(part)来进行上传、上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件
分片上传的场景
⼤⽂件上传⼀般采⽤分⽚上传的⽅式,这样可以提⾼⽂件上传的速度,前端拿到⽂件流后进⾏分⽚,然后与后端进⾏通讯传输,⼀般还会结合断点继传,这时后端⼀般提供三个接⼝:
什么是断点续传
断点续传是在下载或者上传的时候,将下载或上传任务(⼀个⽂件或⼀个压缩包)⼈为的划分为⼏个部分,每⼀个部分采⽤⼀个线程进⾏上传或下载,如果碰到⽹络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,⽽没有必要从头开始上传或者下载。
应⽤场景
断点续传可以看成是分⽚上传的⼀个衍⽣,因此可以使⽤分⽚上传的场景,都可以使⽤断点续传。
实现断点续传的核⼼逻辑
在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上传上传中断的地方进行继续上传。
为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使得客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传
实现流程步骤
(1)方案一,常规步骤
(2)⽅案⼆、实现的步骤
⽂件下载⽐⽂件上传容易的多。
对于HTTP协议,向服务器请求某个⽂件时,只要发送类似如下的请求即可:
GET /Path/FileName HTTP/1.0
Host: www.baidu.com:80
Accept: */*
User-Agent: GeneralDownloadApplication
Connection: close
如果服务器成功收到该请求,并且没有出现任何错误,则会返回类似下⾯的数据:
HTTP/1.0 200 OK
Content-Length: 13057672
Content-Type: application/octet-stream
Last-Modified: Wed, 10 Oct 2005 00:56:34 GMT
Accept-Ranges: bytes
ETag: "2f38a6cac7cec51:160c"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Date: Wed, 16 Nov 2005 01:57:54 GMT
Connection: close
以上就是通过HTTP协议实现⽂件下载的全过程。但还不能实现断点续传,⽽实际上断点续传的实现⾮常简单,只要在请求中加⼀个Range字段就可以了。
假如⼀个⽂件有1000个字节,那么其范围就是0-999,则:
如果HTTP请求中包含Range字段,那么服务器会返回206(Partial Content),同时HTTP头中也会有⼀个相应的Content-Range字段,类似下⾯的格式:
Content-Range: bytes 500-999/1000