HTTP断点续传原理(Java)

断点续传,其实也就是要从文件已经下载的地方开始继续下载。在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就支持了。

一般断点下载时才用到 Range 和 Content-Range 实体头。

打个比方,浏览器请求服务器上的一个文件时,所发出的请求如下:

假设服务器域名为 wwww.sjtu.edu.cn,文件名为 down.zip。

GET /down.zip HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-

excel, application/msword, application/vnd.ms-powerpoint, /

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)

Connection: Keep-Alive

服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

200

Content-Length=106786028

Accept-Ranges=bytes

Date=Mon, 30 Apr 2001 12:56:11 GMT

ETag=W/”02ca57e173c11:95b”

Content-Type=application/octet-stream

Server=Microsoft-IIS/5.0

Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

此时当所谓的网络断掉了,或者人为暂停,返回来继续下载时,断点续传,也就是可以从文件已经下载的地方开始继续下载。

所以在客户端浏览器传给服务器的时候要多加一条信息 — 从哪里开始。

下面是用自己编的一个”浏览器”来传递请求信息给 Web 服务器,要求从 2000070 字节开始。

GET /down.zip HTTP/1.0

User-Agent: NetFox

RANGE: bytes=2000070-

Accept: text/html, image/gif, image/jpeg, ; q=.2, /*; q=.2

仔细看一下就会发现多了一行 RANGE: bytes=2000070-

这一行的意思就是告诉服务器 down.zip 这个文件从 2000070 字节开始传,前面的字节不用传了。

服务器收到这个请求以后,返回的信息如下:

206

Content-Length=106786028

Content-Range=bytes 2000070-106786027/106786028

Date=Mon, 30 Apr 2001 12:55:20 GMT

ETag=W/”02ca57e173c11:95b”

Content-Type=application/octet-stream

Server=Microsoft-IIS/5.0

Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

和前面服务器返回的信息比较一下,就会发现增加了一行:

Content-Range=bytes 2000070-106786027/106786028

返回的代码也改为 206 了,而不再是 200 了。

知道了以上原理,就可以进行断点续传的编程了。

Java 实现断点续传的关键几点

  1. (1) 用什么方法实现提交 RANGE: bytes=2000070-。

    当然用最原始的 Socket 是肯定能完成的,不过那样太费事了,其实 Java 的 net 包中提供了这种功能。代码如下:

    URL url = new URL("http://www.sjtu.edu.cn/down.zip");
    
    HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();
    
    // 设置 User-Agent
    
    httpConnection.setRequestProperty("User-Agent","NetFox");
    
    // 设置断点续传的开始位置
    
    httpConnection.setRequestProperty("RANGE","bytes=2000070-");
    
    // 获得输入流
    
    InputStream input = httpConnection.getInputStream();

    从输入流中取出的字节流就是 down.zip 文件从 2000070 开始的字节流。 大家看,其实断点续传用 Java 实现起来还是很简单的吧。 接下来要做的事就是怎么保存获得的流到文件中去了。

       2.保存文件采用的方法,采用的是 IO 包中的 RandAccessFile 类,假设从 2000070 处开始保存文件,代码如下:

RandomAccess oSavedFile = new RandomAccessFile(“down.zip”,”rw”);
long nPos = 2000070;
// 定位文件指针到 nPos 位置
oSavedFile.seek(nPos);
byte[] b = new byte[1024];
int nRead;
// 从输入流中读入字节流,然后写到文件中
while((nRead=input.read(b,0,1024)) > 0){
    oSavedFile.write(b,0,nRead);
}

补充:

Range

用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:

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 0-499/22400

0-499 是指当前发送的数据的范围,而 22400 则是文件的总大小。

而在响应完成后,返回的响应头内容也不同:

HTTP/1.1 200 Ok(不使用断点续传方式) 
HTTP/1.1 206 Partial Content(使用断点续传方式)

增强校验

在实际场景中,会出现一种情况,即在终端发起续传请求时,URL 对应的文件内容在服务器端已经发生变化,此时续传的数据肯定是错误的。如何解决这个问题了?显然此时需要有一个标识文件唯一性的方法。

在 RFC2616 中也有相应的定义,比如实现 Last-Modified 来标识文件的最后修改时间,这样即可判断出续传文件时是否已经发生过改动。同时 FC2616 中还定义有一个 ETag 的头,可以使用 ETag 头来放置文件的唯一标识。

Last-Modified

If-Modified-Since,和 Last-Modified 一样都是用于记录页面最后修改时间的 HTTP 头信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since 则是由客户端往服务器发送的头,可以看到,再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。

Etag

Etag(Entity Tags)主要为了解决 Last-Modified 无法解决的一些问题。

  1. 一些文件也许会周期性的更改,但是内容并不改变(仅改变修改时间),这时候我们并不希望客户端认为这个文件被修改了,而重新 GET。
  2. 某些文件修改非常频繁,例如:在秒以下的时间内进行修改(1s 内修改了 N 次),If-Modified-Since 能检查到的粒度是 s 级的,这种修改无法判断(或者说 UNIX 记录 MTIME 只能精确到秒)。
  3. 某些服务器不能精确的得到文件的最后修改时间。

为此,HTTP/1.1 引入了 Etag。Etag 仅仅是一个和文件相关的标记,可以是一个版本标记,例如:v1.0.0;或者说 “627-4d648041f6b80” 这么一串看起来很神秘的编码。但是 HTTP/1.1 标准并没有规定 Etag 的内容是什么或者说要怎么实现,唯一规定的是 Etag 需要放在 “” 内。

If-Range

用于判断实体是否发生改变,如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。一般格式:

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 条件判断请求来验证资源是否修改。请求一个文件的流程如下:

第一次请求:

  1. 客户端发起 HTTP GET 请求一个文件。
  2. 服务器处理请求,返回文件内容以及相应的 Header,其中包括 Etag(例如:627-4d648041f6b80)(假设服务器支持 Etag 生成并已开启了 Etag)状态码为 200。

第二次请求(断点续传):

  1. 客户端发起 HTTP GET 请求一个文件,同时发送 If-Range(该头的内容就是第一次请求时服务器返回的 Etag:627-4d648041f6b80)。
  2. 服务器判断接收到的 Etag 和计算出来的 Etag 是否匹配,如果匹配,那么响应的状态码为 206;否则,状态码为 200。
  3. 检测服务器是否支持断点续传

    CURL 实现检测:

    [root@localhost ~]# curl -i --range 0-9 http://www.baidu.com/img/bdlogo.gif
    HTTP/1.1 206 Partial Content
    Date: Mon, 21 Nov 2016 05:26:29 GMT
    Server: Apache
    P3P: CP=" OTI DSP COR IVA OUR IND COM "
    Set-Cookie: BAIDUID=0CD0E23B4D4F739954DFEDB92BE6CE03:FG=1; expires=Tue, 21-Nov-17 05:26:29 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
    Last-Modified: Fri, 22 Feb 2013 03:45:02 GMT
    ETag: "627-4d648041f6b80"
    Accept-Ranges: bytes
    Content-Length: 10
    Cache-Control: max-age=315360000
    Expires: Thu, 19 Nov 2026 05:26:29 GMT
    Content-Range: bytes 0-9/1575
    Connection: Keep-Alive
    Content-Type: image/gif
    
    GIF89a[root@localhost ~]#

    能够找到 Content-Range,则表明服务器支持断点续传。有些服务器还会返回 Accept-Ranges,输出结果 Accept-Ranges: bytes ,说明服务器支持按字节下载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值