从文件上次中断的地方开始重新下载或上传,当下载或上传文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会去重头下载,这样很浪费时间。所以断点续传的功能就应运而生了。
要实现断点续传的功能,需要客户端记录下当前的下载或上传进度,并在需要续传的时候通知服务端本次需要下载或上传的内容片段。
其实断点续传的原理就是在Http的请求上多定义了断点续传相关的HTTP头 Range和Content-Range字段(Range和Content-Range是 HTTP/1.1中新增的HTTP头字段。它用于从服务器中获取一个大文件的某一部分内容,这极大地提高了HTTP的运行效率,很适合下载软件进行多线程下载和断点续传。)
浏览器请求服务器上的一个文件名为test.zip时,请求内容只展示了一些与本文有关的信息
GET /test.zip HTTP/1.1Accept-Language: zh-cnAccept-Encoding: gzip, deflateConnection: Keep-Alive
2.服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:
200Content-Length=66667777Accept-Ranges=bytesContent-Type=application/octet-stream
为了实现从文件已经下载的地方开始继续下载。所以在客户端传给服务器的时候要多加一条信息–从哪里开始。下面是客户端请求时的请求信息,要求从44445555字节开始。
GET /test.zip HTTP/1.0User-Agent: NetFoxRANGE: bytes=44445555-
这段话的意思就是告诉服务器这个文件从44445555字节开始传,前面的字节不用传了。服务器收到这个请求以后,返回的信息如下:
206Content-Length=66667777Content-Range=bytes 44445555-66667777Content-Type=application/octet-stream
和第一次服务器返回的信息相比,增加了一行:
Content-Range=bytes 44445555-66667777
返回的代码也改为206了,而不再是200了。
但是在实际场景中,会出现一种情况,即在终端发起续传请求时,URL对应的文件内容在服务端已经发生变化,此时续传的数据肯定是错误的。如何解决这个问题了?显然此时我们需要有一个标识文件唯一性的方法。在RFC2616中也有相应的定义,比如实现Last-Modified来标识文件的最后修改时间,这样即可判断出续传文件时是否已经发生过改动。同时RFC2616中还定义有一个ETag的头,可以使用ETag头来放置文件的唯一标识,比如文件的MD5值。
终端在发起续传请求时应该在HTTP头中申明If-Match 或者If-Modified-Since 字段,帮助服务端判别文件变化。
另外RFC2616中同时定义有一个If-Range头,终端如果在续传是使用If-Range。If-Range中的内容可以为最初收到的ETag头或者是Last-Modfied中的最后修改时候。服务端在收到续传请求时,通过If-Range中的内容进行校验,校验一致时返回206的续传回应,不一致时服务端则返回200回应,回应的内容为新的文件的全部数据。
HTTP1.1 协议(RFC2616)开始支持获取文件的部分内容,这为并行下载以及断点续传提供了技术支持。它通过在 Header 里两个参数实现的,客户端发请求时对应的是 Range ,服务器端响应时对应的是 Content-Range。
Range
用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:
Range:(unit=first byte pos)-[last byte pos]
Range 头部的格式有以下几种情况:
Range: bytes=0-499 表示第 0-499 字节范围的内容
Range: bytes=500-999 表示第 500-999 字节范围的内容
Range: bytes=-500 表示最后 500 字节的内容
Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容
Range: bytes=0-0,-1 表示第一个和最后一个字节
Range: bytes=500-600,601-999 同时指定几个范围
Content-Range
用于响应头中,在发出带 Range 的请求后,服务器会在 Content-Range 头部返回当前接受的范围和文件总大小。一般格式:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]
例如:
Content-Range: bytes 0-499/22400
0-499 是指当前发送的数据的范围,而 22400 则是文件的总大小。
而在响应完成后,返回的响应头内容也不同:
HTTP/1.1 200 Ok(不使用断点续传方式)
HTTP/1.1 206 Partial Content(使用断点续传方式)
在实际场景中,会出现一种情况,即在终端发起续传请求时,URL 对应的文件内容在服务器端已经发生变化,此时续传的数据肯定是错误的。如何解决这个问题了?显然此时需要有一个标识文件唯一性的方法。
在 RFC2616 中也有相应的定义,比如实现 Last-Modified 来标识文件的最后修改时间,这样即可判断出续传文件时是否已经发生过改动。同时 FC2616 中还定义有一个 ETag 的头,可以使用 ETag 头来放置文件的唯一标识。
If-Modified-Since,和 Last-Modified 一样都是用于记录页面最后修改时间的 HTTP 头信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since 则是由客户端往服务器发送的头。
再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。
Etag(Entity Tags)主要为了解决 Last-Modified 无法解决的一些问题。
为此,HTTP/1.1 引入了 Etag。Etag 仅仅是一个和文件相关的标记,可以是一个版本标记,例如:v1.0.0;或者说 “627-4d648041f6b80” 这么一串看起来很神秘的编码。但是 HTTP/1.1 标准并没有规定 Etag 的内容是什么或者说要怎么实现,唯一规定的是 Etag 需要放在 “” 内。
用于判断实体是否发生改变,如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。一般格式:
If-Range: Etag | HTTP-Date
也就是说,If-Range 可以使用 Etag 或者 Last-Modified 返回的值。当没有 ETage 却有 Last-modified 时,可以把 Last-modified 作为 If-Range 字段的值。
例如:
If-Range: “627-4d648041f6b80”
If-Range: Fri, 22 Feb 2013 03:45:02 GMT
If-Range 必须与 Range 配套使用。如果请求报文中没有 Range,那么 If-Range 就会被忽略。如果服务器不支持 If-Range,那么 Range 也会被忽略。
如果请求报文中的 Etag 与服务器目标内容的 Etag 相等,即没有发生变化,那么应答报文的状态码为 206。如果服务器目标内容发生了变化,那么应答报文的状态码为 200。
用于校验的其他 HTTP 头信息:If-Match/If-None-Match、If-Modified-Since/If-Unmodified-Since。
Etag 由服务器端生成,客户端通过 If-Range 条件判断请求来验证资源是否修改。请求一个文件的流程如下:
第一次请求:
第二次请求(断点续传):
对含“Range”字段的HTTP数据,其响应码并非通常的200等,而是206和416。206的含义是“Partial Content”,即服务器发回的是请求的那一部分内容。416的含义是“Range Not Satisfiable”,表明客户端请求的数据范围不合法。