## **断点续传**
什么是断点续传?
答:从那个点点断开继续传。。。
额,看起来也没毛病...
再问一遍,什么是断点续传?
答:
> 断点续传就是从文件上次中断的地方开始重新下载或上传,当下载或上传文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会去重头下载,这样很浪费时间。所以断点续传的功能就应运而生了。要实现断点续传的功能,需要客户端记录下当前的下载或上传进度,并在需要续传的时候通知服务端本次需要下载或上传的内容片段。
>
说起到断点续传,他的关键点是什么?
* 如何告知服务器,从指定的位置下载
* 如何知道客户端想要的指定位置是多少
其实,很简单,并不需要我们去写一些什么东西。HTTP协议本身就支持断点续传了。只要我们通过方式告诉服务器,从制定位置开始下载就可以了。
来来来,http协议搞起来~~~~~~
先来提问一下昂,说到http想到了什么???
(在线投骰子?还是谁能**~~自告奋勇~~**???)
```
/**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**/
```
## **Range & Content-Length & Content-Range & If-Range**
这些都是 HTTP 包中 Header 头部的一些字段信息,其中 Range 和 If-Range 是请求头中的字段,Content-Length 和 Content-Range 是响应头中的字段。
### **Range**
当请求头中出现 Range 字段时,表示告知服务端,客户端下载该文件想要从指定的位置开始下载,至于 Range 字段属性值的格式有以下几种:
| 格式 | 含义 |
| --- | --- |
| Range:bytes=0-500 | 表示下载从0到500字节的文件,即头500个字节 |
| Range:bytes=501-1000 | 表示下载从500到1000这部分的文件,单位字节 |
| Range:bytes=-500 | 表示下载最后的500个字节 |
| Range:bytes=500- | 表示下载从500开始到文件结束这部分的内容 |
当 app 想实现缩短大文件的下载耗时,可以开启多个下载线程,每个线程只负责文件的一部分下载,当所有线程下载结束后,将每个线程下载的文件按顺序拼接成一个完整的文件,这样就可以达到缩短下载大文件的耗时目的了。
那么就可以使用`Range:bytes=501-1000`这种格式了,每个线程在各自的请求头字段中,以这种格式加入相对应的信息即可达到目的了。
*****
如果 app 想实现断点续传,文件下载到一半被迫中断,下次启动还可以继续接着上次进度下载时,那么此时可以使用 `Range:bytes=500-` 这种格式了,只要先获取本地那份文件目前的大小,通过在请求头中加入 Range 字段信息即可。
#### **Content-Length**
Content-Length 字段出现在响应头中,用于告知客户端此次下载的文件大小。
客户端需要实现下载进度实时更新时,需要知道文件的总大小和目前下载的大小。
想要知道文件总大小? Content-Length中的字段就好
如果想要实现多线程同时分段下载大文件时,在下载前会发送一个不需要携带body信息请求,用于先获取响应头中的Content-Length字段
> * 如果这条链接是一次性将整个文件下载下来的,那么 Content-Length 就表示这个文件的总大小。
> * 如果这条链接指定了 Range,表明了只是下载文件的指定部分的内容,那么此时 Content-Length 表示的就只是这一部分的大小。
所以,如果客户端实现了下载进度实时更新功能时,需要注意一下。因为如果文件是断点续传的,那么进度条的分母就不能用每次 HTTP 链接中的 Content-Length。要么下载前先发一条获取用于文件总大小的请求,然后一直维护着这个数据,要么就使用 Content-Range 字段。
*****
#### **Content-Range**
Content-Range 字段也是出现在响应头中,用于告知客户端此链接下载的文件是哪个部分的,以及文件的总大小。
比如,当客户端在请求头中指定了`Range:bayes=501-1000`来下载一个总大小为 2000 字节文件的中间一部分内容时,此时,响应头中的 Content-Range 字段信息如下:
`Content-Range:bytes 501-1000/2000`
斜杠前表示此链接下载的文件是哪一部分,斜杠后表示文件的总大小。
*****
#### **If-Range**
断点续传,说白点也就是分多次下载,既然不是一次性下载,那么就无法保证多次下载的间隔。
也就是说,有可能出现这种场景:
>这次由于某些原因只下载的一部分,而下次重启继续下载,但可能等到过了很多天后才重启去继续下载,如果在这期间,服务端的这份文件更新了怎么办?
只要不是一次性下载的,那么就有可能会出现这种场景,显然,这时候,就不希望断点续传了,而是要让客户端直接**重头开始下载**,毕竟文件都已经发生更新了,不是同一份了,再继续恢复下载也没有什么意义。
**客户端要如何知道服务端的文件是否发生变化,要重头下载呢?**
这时就可以结合 If-Range 字段来实现了,这个也是在请求头中的字段,跟 Range 字段一起使用,它的作用是给 Range 字段生效设置了一些条件,只有满足这些条件,Range 才能生效。
也就是说,只有先满足 If-Range,那么才能通过 Range 来实现断点续传。
那它的条件值可以设置为哪些呢?有两种,Last-Modified 或者 ETag,这两个也都是响应头中的字段。**但不能将两者同时使用。**
(if-Range)参考链接:[https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/If-Range](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/If-Range)
*****
#### **抓包:**
> 1\. https://www.taobao.com/sw.js
> 2. https://www.baidu.com/img/bd\_logo1.png?where=super
> 3.https://mat1.gtimg.com/pingjs/ext2020/qqindex2018/dist/img/qq\_logo\_2x.png
4.https://www.magicdatatech.com/filespath/files/20191104064048.png
鲨鱼软件的抓包示例:
![](https://img.kancloud.cn/1e/04/1e0410999563f7e8b28e30f4bca3b3e9_416x366.png)
首先先发起一个请求,设置了不携带 BODY 信息,这样就可以在下载前先获取到文件的总大小。
![](https://img.kancloud.cn/0f/99/0f990ce339c674d17136aefaf79d950e_506x481.png)
这是下载中断后,重启想要继续下载时发起的请求信息,请求头中指定了 `Range:bytes=12341380-` 表示本地已经下载了这么多,需要从这里开始继续往下下载。
响应头中返回了这部分的内容,并在 Content-Length 和 Content-Range 字段中给出了相关信息。
```
// 创建一个XMLHttpRequest对象
// IE6, IE5
// var xhr = new ActiveXObject("Microsoft.XMLHTTP");
// IE7+, Firefox, Chrome, Opera, Safari
var xhr = new XMLHttpRequest()
// 重置服务器端返回的类型
xhr.overrideMimeType('image/png')
// 初始化一个请求 参数3:表示该请求应该以`异步模式`执行。
xhr.open('GET', '[https://imgcps.jd.com/ling/7641991/5bmz5p2\_55S16KeG5beo5YiS566X/5L2g5YC85b6X5YWl5omL/p-5bd8253082acdd181d02f9d8/e129873b/590x470.jpg](https://imgcps.jd.com/ling/7641991/5bmz5p2_55S16KeG5beo5YiS566X/5L2g5YC85b6X5YWl5omL/p-5bd8253082acdd181d02f9d8/e129873b/590x470.jpg)', true)
// 超时时间,单位是毫秒
xhr.timeout = 2000;
// 设置 HTTP 请求头的值。在`open()`之后、`send()`之前调用`setRequestHeader()`方法。
xhr.setRequestHeader('range', 'bytes=0-10')
// 发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回。
xhr.send()
// 只要 `readyState` 属性发生变化,就会调用相应的处理函数。这个回调函数会被用户线程所调用。当一个`XMLHttpRequest`请求被abort()方法取消时,其对应的 `readystatechange`事件不会被触发。
xhr.onreadystatechange = function(){
if ( xhr.readyState == 4 && xhr.status == 206 ) {
console.info(xhr)
console.info( xhr.response);
} else {
console.info( xhr.statusText );
}
}
```
### **js 中的 Blob对象**
https://www.jianshu.com/p/b322c2d5d778