• http实现文件分片下载



    HTTP分片异步下载是一种下载文件的技术,它允许将一个大文件分成多个小块(分片),然后分别下载这些分片,从而实现更快速、稳定的下载过程。这种技术常用于大文件的下载,例如视频、游戏、软件等。或者与文件下载的断点续传功能搭配使用时非常有用。

    比如当你正在看大片时,网络断了,你需要继续看的时候,文件服务器不支持断点的话,则你需要重新等待下载这个大片,才能继续观看。而支持HTTP Range的话,客户端就会记录了之前已经看过的视频文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个视频文件发送回客户端,以此节省网络带宽,带来更流畅的用户体验。

    检测是否支持

    检测服务器端是否支持分片请求。

    假如在响应头中存在 Accept-Ranges(并且它的值不为none),那么表示该服务器支持分片请求。例如,你可以使用 cURL 发送一个 HEAD 请求来进行检测。

    curl -I https://xxx.jpg
    
    HTTP/1.1 200 OK
    ...
    Accept-Ranges: bytes
    Content-Length: 146515
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在上面的响应中, Accept-Ranges: bytes 表示界定范围的单位是 bytes。这里 Content-Length 也是有效信息,因为它提供了要检索的图片的完整大小。

    如果站点未响应 Accept-Ranges,那么它们有可能不支持分片请求。一些站点会明确将其值设置为 none,以此来表明不支持。在这种情况下,某些应用的下载管理器会将暂停按钮禁用。

    curl -I https://xxx.movie
    
    HTTP/1.1 200 OK
    ...
    Accept-Ranges: none
    
    • 1
    • 2
    • 3
    • 4
    • 5

    HTTP Range 语法

    Range是一个HTTP请求头,告知服务器要返回文件的哪一部分,即:哪个区间范围(字节)的数据。

    Range: <unit>=<range-start>-
    Range: <unit>=<range-start>-<range-end>
    Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
    Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>
    
    • 1
    • 2
    • 3
    • 4
    • :范围所采用的单位,通常是字节(bytes)

    • :一个整数,表示在特定单位下,范围的起始值。(下标从0开始)

    • :一个整数,表示在特定单位下,范围的结束值。这个值是可选的,如果不存在,表示此范围一直延伸到文档结束。

    Range: bytes=200-1000 就是下载 200-1000 字节的内容(两边都是闭区间),服务端返回 206 的状态码,并带上这部分内容。

    可以省略右边部分,代表一直到结束:Range: bytes=200-

    也可以省略左边部分,代表从头开始:Range: bytes=-1000

    而且可以请求多段 range,服务端会返回多段内容:Range: bytes=200-1000, 2000-6576, 19000-

    Range请求cURL示例

    从服务器端请求特定的范围。

    单一范围

    我们可以请求资源的某一部分。这次我们依然用 cURL 来进行测试。-H 选项可以在请求中追加一个首部行,在这个例子中,是用 Range 首部来请求图片文件的前 1024 个字节。

    curl http://xxx.jpg -i -H "Range: bytes=0-1023"
    
    • 1

    这样生成的请求如下:

    GET /xxx.jpg HTTP/1.1
    Host: i.imgur.com
    Range: bytes=0-1023
    
    • 1
    • 2
    • 3

    服务器端会返回状态码为 206 Partial Content 的响应:

    HTTP/1.1 206 Partial Content
    Content-Range: bytes 0-1023/146515
    Content-Length: 1024
    ...
    (binary content)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里,Content-Length 首部现在用来表示先前请求范围的大小(而不是整张图片的大小)。Content-Range 响应首部则表示这一部分内容在整个资源中所处的位置。

    多重范围

    Range 头部也支持一次请求文档的多个部分。请求范围用一个逗号分隔开。

    curl https://xxx.jpg -i -H "Range: bytes=0-50, 100-150"
    
    • 1

    服务器返回 206 Partial Content 状态码,Content-Type:multipart/byteranges,boundary=3d6b6a416f9b5 头部。

    • Content-Type:multipart/byteranges 表示这个响应有多个 byterange。
    • 每一部分 byterange 都有他自己的 Content-type 头部和 Content-Range,并且使用 boundary 参数对 body 进行划分。
    HTTP/1.1 206 Partial Content
    Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5
    Content-Length: 282
    
    --3d6b6a416f9b5
    Content-Type: text/html
    Content-Range: bytes 0-50/1270
    
    
    
    
        Example Do
    --3d6b6a416f9b5
    Content-Type: text/html
    Content-Range: bytes 100-150/1270
    
    eta http-equiv="Content-type" content="text/html; c
    --3d6b6a416f9b5--
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li></ul></pre> 
    <h4><a name="t6"></a><a id="_123"></a>条件式分片请求</h4> 
    <p>当(中断之后)重新开始请求更多资源片段的时候,必须确保自从上一个片段被接收之后该资源没有进行过修改。</p> 
    <p><code>The If-Range</code> 请求首部可以用来生成条件式分片请求:</p> 
    <ul><li>假如条件满足的话,条件请求就会生效,服务器会返回状态码为 206 Partial 的响应,以及相应的消息主体。</li><li>假如条件未能得到满足,那么就会返回状态码为 200 OK 的响应,同时返回整个资源。</li></ul> 
    <p>该首部可以与 <code>Last-Modified</code> 验证器或者 <code>ETag</code> 一起使用,但是二者不能同时使用。</p> 
    <pre data-index="8" class="set-code-show prettyprint"><code class="prism language-cmd has-numbering" onclick="mdcp.signin(event)" style="position: unset;">If-Range: Wed, 21 Oct 2015 07:28:00 GMT
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <h3><a name="t7"></a><a id="Range_137"></a>Range分片请求的响应</h3> 
    <p>与分片请求相关的有三种状态:</p> 
    <ul><li>在请求成功的情况下,服务器会返回 <code>206 Partial Content</code> 状态码。请求多个部分,服务器会以 <code>multipart</code> 文件的形式将其返回。</li><li>在请求的范围越界的情况下(范围值超过了资源的大小),服务器会返回 <code>416 Requested Range Not Satisfiable</code> (请求的范围无法满足)状态码。</li><li>在不支持分片请求的情况下,服务器会返回 <code>200 OK</code> 状态码。</li></ul> 
    <h3><a name="t8"></a><a id="_145"></a>文件整体下载</h3> 
    <p>以下实例采用node作为后端。</p> 
    <p>下载文件是一个常见的需求,只要服务端设置 <code>Content-Disposition</code> 为 <code>attachment</code> 就可以。</p> 
    <p>比如这样:</p> 
    <pre data-index="9" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/download'</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Disposition'</span><span class="token punctuation">,</span><span class="token string">'attachment; filename="test.txt"'</span><span class="token punctuation">)</span>
        res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'donwloadfileContent'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    
    app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">server is running at port 3000</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li></ul></pre> 
    <p>设置 <code>Cotent-Disposition</code> 为 <code>attachment</code>,指定 <code>filename</code>。</p> 
    <p>然后 html 里加一个 a 标签:</p> 
    <pre data-index="10" class="set-code-show prettyprint"><code class="prism language-html has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">html</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://localhost:3000/download<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>download<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li></ul></pre> 
    <ol><li>下载<code>http-server</code> npm包,在当前文件目录运行<code>http-server</code>命令跑起静态服务器。</li><li>点击链接就可以下载。若需要在服务器端js文件修改后动态生效,则可以安装<code>nodemon</code> npm包,进而执行<code>nodemon index.js</code>命令。</li></ol> 
    <p>当文件过大,则需要对文件进行分片来下载,下面使用案例进行讲解。</p> 
    <h3><a name="t9"></a><a id="_185"></a>文件分片下载</h3> 
    <h4><a name="t10"></a><a id="_187"></a>文本下载</h4> 
    <p>添加这样一个路由:</p> 
    <pre data-index="11" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;">app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/downloadRange'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Origin'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
        res<span class="token punctuation">.</span><span class="token function">download</span><span class="token punctuation">(</span><span class="token string">'downloadRange.txt'</span><span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
            <span class="token literal-property property">acceptRanges</span><span class="token operator">:</span> <span class="token boolean">true</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li></ul></pre> 
    <p>downloadRange.txt与index.js在同级目录下。</p> 
    <p>设置允许跨域请求,因为前端起的静态服务为http://localhost:8080,而node服务为http://localhost:3000,非同域。</p> 
    <p><code>res.download</code> 是读取文件内容返回,<code>acceptRanges</code> 选项为 true 就是会处理 range 请求(其实默认就是 true)。</p> 
    <p>文件 downloadRange.txt 的内容是这样的:</p> 
    <pre data-index="12" class="set-code-show prettyprint"><code class="prism language-txt has-numbering" onclick="mdcp.signin(event)" style="position: unset;">0123456789
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>然后在 html 里访问一下这个接口:</p> 
    <pre data-index="13" class="set-code-hide prettyprint"><code class="prism language-html has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">html</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
            <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'http://localhost:3000/downloadRange'</span><span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
                <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token string">'bytes=0-4'</span><span class="token punctuation">,</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
                    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token comment">// 输出01234</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
                    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span>
        </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li></ul></pre> 
    <p>访问页面,可以看到返回的是 206 的状态码!</p> 
    <p><img src="https://1000bd.com/contentImg/2024/04/10/91187f8c8d49f3f1.png" alt=""></p> 
    <p>这时候 <code>Content-Length</code> 就代表返回的内容的长度。</p> 
    <p>还有个 <code>Content-Range</code> 代表当前 range 的长度以及总长度。</p> 
    <p>此时响应内容为01234</p> 
    <p><img src="https://1000bd.com/contentImg/2024/04/10/b6e9c99174bfe982.png" alt=""></p> 
    <p>当然,你也可以访问 5 以后的内容</p> 
    <pre data-index="14" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token string">'bytes=5-'</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>响应头内容是这样的:</p> 
    <pre data-index="15" class="set-code-show prettyprint"><code class="prism language-cmd has-numbering" onclick="mdcp.signin(event)" style="position: unset;">Content-Length: 5
    Content-Range: bytes 5-9/10
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li></ul></pre> 
    <p>返回的内容是这样的:</p> 
    <pre data-index="16" class="set-code-show prettyprint"><code class="prism language-cmd has-numbering" onclick="mdcp.signin(event)" style="position: unset;">56789
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>这俩连接起来就是整个文件的内容。这样就实现了简易版的<a href="https://so.csdn.net/so/search?q=%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0&spm=1001.2101.3001.7020" target="_blank" class="hl hl-1" data-report-view="{"spm":"1001.2101.3001.7020","dest":"https://so.csdn.net/so/search?q=%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0&spm=1001.2101.3001.7020","extra":"{\"searchword\":\"断点续传\"}"}" data-report-click="{"spm":"1001.2101.3001.7020","dest":"https://so.csdn.net/so/search?q=%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0&spm=1001.2101.3001.7020","extra":"{\"searchword\":\"断点续传\"}"}" data-tit="断点续传" data-pretit="断点续传">断点续传</a>。</p> 
    <p>我们再来试试如果超出 range 会怎么样:</p> 
    <pre data-index="17" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token string">'bytes=50-60'</span><span class="token punctuation">,</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>请求 50-60 字节的内容,这时候响应头是这样的:</p> 
    <pre data-index="18" class="set-code-show prettyprint"><code class="prism language-cmd has-numbering" onclick="mdcp.signin(event)" style="position: unset;">Status Code: 416 Range Not Satisfiable
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>返回的是 416 状态码,代表 range 不合法。</p> 
    <p>Range 不是还可以设置多段么?多段内容是怎么返回的呢?</p> 
    <p>我们来试一下:</p> 
    <pre data-index="19" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token string">'bytes=0-1, 3-4, 6-'</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>重新访问一下,这时候报了一个跨域的错误,说是发送预检请求失败。</p> 
    <pre data-index="20" class="set-code-show prettyprint"><code class="prism language-text has-numbering" onclick="mdcp.signin(event)" style="position: unset;">Access to fetch at 'http://localhost:3000/downloadRange' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>浏览器会在三种情况下发送预检(preflight)请求:</p> 
    <ul><li>用到了非 GET、POST 的请求方法,比如 PUT、DELETE 等,会发预检请求看看服务端是否支持</li><li>用到了一些非常规请求头,比如用到了 Content-Type,会发预检请求看看服务端是否支持</li><li>用到了自定义 header,会发预检请求</li></ul> 
    <p>为啥 Range 头单个 range 不会触发预检请求,而多个 range 就触发了呢?</p> 
    <p>因为多个 range 的时候返回的 Content-Type 是不一样的,是 <code>multipart/byteranges</code> 类型,比较特殊。</p> 
    <p>预检请求是 <code>options</code> 请求,那我们就支持一下:</p> 
    <pre data-index="21" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;">app<span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token string">'/downloadRange'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Origin'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Headers'</span><span class="token punctuation">,</span> <span class="token string">'Range'</span><span class="token punctuation">)</span>
        res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li></ul></pre> 
    <p>然后重新访问,这时候你会发现虽然状态码为200,且返回的是整个内容!</p> 
    <p>这是因为 express 只做了单 range 的支持,多段 range 可能它觉得没必要支持吧。</p> 
    <p>毕竟你发多个单 range 请求就能达到一样的效果。</p> 
    <h4><a name="t11"></a><a id="_325"></a>图片下载</h4> 
    <p>下面我们就用 range 来实现下文件的分片下载,最终合并成一个文件的功能。</p> 
    <p>我们来下载一个图片吧,分成两块下载,然后下载完合并起来。</p> 
    <p>就用这个图片好了:</p> 
    <p><img src="https://1000bd.com/contentImg/2024/04/10/49ba212242bb60ff.png" alt=""></p> 
    <p>node代码修改如下</p> 
    <pre data-index="22" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;">app<span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token string">'/downloadPicRange'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Origin'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Headers'</span><span class="token punctuation">,</span> <span class="token string">'Range'</span><span class="token punctuation">)</span>
        res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/downloadPicRange'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Origin'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        res<span class="token punctuation">.</span><span class="token function">download</span><span class="token punctuation">(</span><span class="token string">'1681859964629.png'</span><span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
            <span class="token literal-property property">acceptRanges</span><span class="token operator">:</span> <span class="token boolean">true</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li></ul></pre> 
    <p>我们写下分片下载的代码,就分两段:这个图片是 56585 字节,也就是大概55.2k,那我们就分成 0-20000 和 20001- 两段:</p> 
    <pre data-index="23" class="set-code-hide prettyprint"><code class="prism language-html has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">html</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
            <span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
                <span class="token keyword">const</span> p1 <span class="token operator">=</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'http://localhost:3000/downloadPicRange'</span><span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{<!-- --></span>
                        <span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token string">'bytes=0-20000'</span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span>
                <span class="token keyword">const</span> p2 <span class="token operator">=</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'http://localhost:3000/downloadPicRange'</span><span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{<!-- --></span>
                        <span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token string">'bytes=20001-'</span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span>  
    
                <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>p1<span class="token punctuation">,</span> p2<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">func</span> <span class="token operator">=></span> func<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">blob</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token keyword">const</span> completeBlob <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span> <span class="token literal-property property">type</span><span class="token operator">:</span> result<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>type <span class="token punctuation">}</span><span class="token punctuation">)</span>
                console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span>completeBlob<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li></ul></pre> 
    <p>两个响应头Content-Range分别是这样的:</p> 
    <pre data-index="24" class="set-code-show prettyprint"><code class="prism language-text has-numbering" onclick="mdcp.signin(event)" style="position: unset;">Content-Range: bytes 0-20000/56585
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <pre data-index="25" class="set-code-show prettyprint"><code class="prism language-text has-numbering" onclick="mdcp.signin(event)" style="position: unset;">Content-Range: bytes 20001-56584/56585
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li></ul></pre> 
    <p>第一个响应还能看到图片的预览,只能看到上部分:</p> 
    <p><img src="https://1000bd.com/contentImg/2024/04/10/545bc8f0a71eb9d6.png" alt=""></p> 
    <p>然后我们要把两段给拼起来,怎么拼呢?</p> 
    <ol><li>这里由于使用fetch进行请求,我们可以直接获取响应内容的文件对象为<code>blob</code>类型。</li><li>然后将两段blob类型文件对象,合并到新的blob文件对象中。</li><li>通过<code>URL.createObjectURL</code>获取文件对象的资源在本地的链接,将其粘贴到浏览器中,可以看到组合的图片正常显示。</li></ol> 
    <h4><a name="t12"></a><a id="_402"></a>封装下载方法</h4> 
    <p>当然,一般不会这么写死来用,我们可以封装一个通用的文件分片下载工具。</p> 
    <p>但分片之前需要拿到文件的大小,所以要增加一个接口,调用这个接口返回文件大小:</p> 
    <pre data-index="26" class="set-code-show prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/length'</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{<!-- --></span>
        res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Origin'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">''</span> <span class="token operator">+</span> fs<span class="token punctuation">.</span><span class="token function">statSync</span><span class="token punctuation">(</span><span class="token string">'./1681859964629.png'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li></ul></pre> 
    <p>然后我们来做分片:</p> 
    <pre data-index="27" class="set-code-hide prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">fileDownloadRange</span><span class="token punctuation">(</span><span class="token parameter">path<span class="token punctuation">,</span> size<span class="token punctuation">,</span> chunkSize</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">let</span> chunkNum <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>size <span class="token operator">/</span> chunkSize<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
        <span class="token keyword">const</span> downloadTask <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator"><=</span> chunkNum<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token keyword">const</span> rangeStart <span class="token operator">=</span> chunkSize <span class="token operator">*</span> <span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">const</span> rangeEnd <span class="token operator">=</span> chunkSize <span class="token operator">*</span> i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>
    
            downloadTask<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>
                <span class="token function">fetch</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{<!-- --></span>
                        <span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">bytes=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>rangeStart<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>rangeEnd<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span><span class="token punctuation">,</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>downloadTask<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">task</span> <span class="token operator">=></span> task<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">blob</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li></ul></pre> 
    <p>这部分代码不难理解:</p> 
    <p>首先根据 chunk 大小来计算一共几个 chunk,通过 <code>Math.ceil</code> 向上取整。</p> 
    <p>然后计算每个 chunk 的 range,构造下载任务的 promise。</p> 
    <p><code>Promise.all</code> 等待所有下载任务完成,并获取下载内容为<code>blob</code>文件类型</p> 
    <p>我们来验证下:</p> 
    <pre data-index="28" class="set-code-hide prettyprint"><code class="prism language-javascript has-numbering" onclick="mdcp.signin(event)" style="position: unset;"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">fileDownloadRange</span><span class="token punctuation">(</span><span class="token parameter">path<span class="token punctuation">,</span> size<span class="token punctuation">,</span> chunkSize</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">let</span> chunkNum <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>size <span class="token operator">/</span> chunkSize<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
        <span class="token keyword">const</span> downloadTask <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator"><=</span> chunkNum<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token keyword">const</span> rangeStart <span class="token operator">=</span> chunkSize <span class="token operator">*</span> <span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">const</span> rangeEnd <span class="token operator">=</span> chunkSize <span class="token operator">*</span> i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>
    
            downloadTask<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>
                <span class="token function">fetch</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{<!-- --></span>
                        <span class="token literal-property property">Range</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">bytes=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>rangeStart<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>rangeEnd<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span><span class="token punctuation">,</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>downloadTask<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">task</span> <span class="token operator">=></span> task<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">blob</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    
    <span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">const</span> fileLength <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'http://localhost:3000/length'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">const</span> blobArr <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fileDownloadRange</span><span class="token punctuation">(</span><span class="token string">'http://localhost:3000/downloadPicRange'</span><span class="token punctuation">,</span> fileLength<span class="token punctuation">,</span> <span class="token number">15000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
        <span class="token keyword">const</span> fileCompleted <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span>blobArr<span class="token punctuation">,</span> <span class="token punctuation">{<!-- --></span> <span class="token literal-property property">type</span><span class="token operator">:</span> blobArr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>type <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span>fileCompleted<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li><li style="color: rgb(153, 153, 153);">24</li><li style="color: rgb(153, 153, 153);">25</li><li style="color: rgb(153, 153, 153);">26</li></ul></pre>
                    </div>
                        </div>
                    </li>
    
                    <li class="list-group-item ul-li">
    
                        <b>相关阅读:</b><br>
                        <nobr>
    <a href="/Article/Index/1709777">博客园众包平台:游戏开发者找人长期合作建设自己的网站</a>                            <br />
    <a href="/Article/Index/990932">多线程概述(线程创建,方法(等待,通知,加入,睡眠,礼让,中断),上下文切换,死锁,守护线程与用户线程)</a>                            <br />
    <a href="/Article/Index/1838332">【ARM】ARM Cortex 处理器详细讲解</a>                            <br />
    <a href="/Article/Index/1061742">5G+AI数字化智能工厂建设解决方案</a>                            <br />
    <a href="/Article/Index/804630">Adobe Auditon 如何将音频文件切分成多段,并保存为多个单独的文件</a>                            <br />
    <a href="/Article/Index/733301">985大学新增专业,考数据结构+自然语言处理!中央民族大学新增语言信息安全...</a>                            <br />
    <a href="/Article/Index/1042212">DNS域名解析过程</a>                            <br />
    <a href="/Article/Index/1059765">安装linux子系统以及配置环境</a>                            <br />
    <a href="/Article/Index/884272">APP兼容性测试如何测试?</a>                            <br />
    <a href="/Article/Index/649471">HUDI(搭建详细记录附加jar)</a>                            <br />
                        </nobr>
                    </li>
                    <li class="list-group-item from-a mb-2">
                        原文地址:https://blog.csdn.net/weixin_44116302/article/details/132725912
                    </li>
    
                </ul>
            </div>
    
            <div class="col-lg-4 col-sm-12">
                <ul class="list-group" style="word-break:break-all;">
                    <li class="list-group-item ul-li-bg" aria-current="true">
                        最新文章
                    </li>
                    <li class="list-group-item ul-li">
                        <nobr>
    <a href="/Article/Index/1484446">攻防演习之三天拿下官网站群</a>                            <br />
    <a href="/Article/Index/1515268">数据安全治理学习——前期安全规划和安全管理体系建设</a>                            <br />
    <a href="/Article/Index/1759065">企业安全 | 企业内一次钓鱼演练准备过程</a>                            <br />
    <a href="/Article/Index/1485036">内网渗透测试 | Kerberos协议及其部分攻击手法</a>                            <br />
    <a href="/Article/Index/1877332">0day的产生 | 不懂代码的"代码审计"</a>                            <br />
    <a href="/Article/Index/1887576">安装scrcpy-client模块av模块异常,环境问题解决方案</a>                            <br />
    <a href="/Article/Index/1887578">leetcode hot100【LeetCode 279. 完全平方数】java实现</a>                            <br />
    <a href="/Article/Index/1887512">OpenWrt下安装Mosquitto</a>                            <br />
    <a href="/Article/Index/1887520">AnatoMask论文汇总</a>                            <br />
    <a href="/Article/Index/1887496">【AI日记】24.11.01 LangChain、openai api和github copilot</a>                            <br />
                        </nobr>
                    </li>
                </ul>
    
                <ul class="list-group pt-2" style="word-break:break-all;">
                    <li class="list-group-item ul-li-bg" aria-current="true">
                        热门文章
                    </li>
                    <li class="list-group-item ul-li">
                        <nobr>
    <a href="/Article/Index/888177">十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!</a>                            <br />
    <a href="/Article/Index/797680">奉劝各位学弟学妹们,该打造你的技术影响力了!</a>                            <br />
    <a href="/Article/Index/888183">五年了,我在 CSDN 的两个一百万。</a>                            <br />
    <a href="/Article/Index/888179">Java俄罗斯方块,老程序员花了一个周末,连接中学年代!</a>                            <br />
    <a href="/Article/Index/797730">面试官都震惊,你这网络基础可以啊!</a>                            <br />
    <a href="/Article/Index/797725">你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法</a>                            <br />
    <a href="/Article/Index/797702">心情不好的时候,用 Python 画棵樱花树送给自己吧</a>                            <br />
    <a href="/Article/Index/797709">通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!</a>                            <br />
    <a href="/Article/Index/797716">13 万字 C 语言从入门到精通保姆级教程2021 年版</a>                            <br />
    <a href="/Article/Index/888192">10行代码集2000张美女图,Python爬虫120例,再上征途</a>                            <br />
                        </nobr>
                    </li>
                </ul>
    
            </div>
        </div>
    </div>
    <!-- 主体 -->
    
    
        <!--body结束-->
        <!--这里是footer模板-->
        
        <!--footer-->
    <nav class="navbar navbar-inverse navbar-fixed-bottom">
        <div class="container">
            <div class="row">
                <div class="col-md-12">
                    <div class="text-muted center foot-height">
                        Copyright © 2022 侵权请联系<a href="mailto:2656653265@qq.com">2656653265@qq.com</a>   
                        <a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2022015340号-1</a>
                    </div>
                    <div style="width:300px;margin:0 auto; padding:0px 5px;">
                        <a href="/regex.html">正则表达式工具</a>
                        <a href="/cron.html">cron表达式工具</a>
                        <a href="/pwdcreator.html">密码生成工具</a>
                    </div>
                    <div style="width:300px;margin:0 auto; padding:5px 0;">
                        <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010502049817" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;">
                        <img src="" style="float:left;" /><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">京公网安备 11010502049817号</p></a>
                    </div>
                </div>
            </div>
        </div>
      
    </nav>
    <!--footer-->
    
        <!--footer模板结束-->
    
        <script src="/js/plugins/jquery/jquery.js"></script>
        <script src="/js/bootstrap.min.js"></script>
    
        <!--这里是scripts模板-->
        
    
        
     
    
    
        <!--scripts模板结束-->
    
    </body>
    </html>