When I tried to use onProgress to display progress, I found that the total displayed in onProgress is always 0.

Why is that?

I don’t know if you have encountered sometimes when downloading files with download software, some downloads can display the total size, some can not display, looking like there is a bug. The reason for this is similar to the fact that onProgress shows a total of 0.

To find out why, you should first understand the principle of downloading files, and usually file download is resumable, you can start from this aspect.

In order for file downloads to be paused and then restarted from the paused part, it is important to understand HTTP content-Length, Accept-Ranges, content-range, and Range.

Content-length: indicates the size in bytes of the response header

Accept-ranges: used in response headers to inform clients that range requests can be made. The following value indicates the unit of content returned, usually bytes, such as Accept-ranges :bytes

Content-range: used in the response header to describe the Range and overall length of the Content in the response request. For example, content-range: Bytes 201-220/326 Indicates that the server returns the contents of the requested resource in the Range of 201 to 220bytes. The total size of the requested resource is 326 bytes. If the total size is unknown, content-range: bytes 201-220/\* is displayed

Range: used as the request header to tell the server what part of the file to return. For example, Range: bytes=500-1000 tells the server I want 500 to 1000 bytes of the file.

Using HTTP Range, content-range can achieve breakpoint download.

We can use Ajax to simulate a breakpoint download as follows, requesting an index.html file from the Nginx server.

let entryContentLength = 0,
  entryContent = "";
getContentLength("http://localhost:8083/test/index.html").then(res => {
  if (res) {
    entryContentLength = res;
  } else {
    entryContentLength = "The length cannot be known.";
  }
  sectionDownload(0, 20, "http://localhost:8083/test/index.html");
});

function sectionDownload(start, end, url) {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.setRequestHeader("Range", `bytes=${start}-${end}`);
  xhr.onload = function() {
    if(xhr.status == 206) { entryContent += xhr.response; SectionDownload (end + 1, end + 20, url); }else if(xhr.status == 416) {// Complete download after a series of operations console.log("Get content: \n" + entryContent,
        "\n Content Length: \n" + entryContentLength
      );
    } else if (xhr.status == 200) {
      console.log(
        "Get content: \n" + entryContent,
        "\n Content Length: \n" + entryContentLength
      );
    } else{ console.log(xhr); }}; xhr.send(); }function getContentLength(url) {
  return new Promise(resolve => {
    const xhr = new XMLHttpRequest();
    xhr.open("HEAD", url);
    xhr.onload = function() {
      resolve(xhr.getResponseHeader("content-length"));
    };
    xhr.send();
  });
}

Copy the code

This code sends a HEAD request to the server to get the size of the response header content-Length, which is the size of the request index.html, and then starts getting the contents of the index.html, only 20 bytes at a time. And piece it together into the entryContent variable. When no bytes are eventually returned, the entryContent is the entire contents of index.html.

HTTP response header with content returned:

HTTP response header when the request scope cannot be met:

As you can see, when some bytes are returned in the response, the return status is 206. When the Range of bytes requested by the client exceeds the size of the requested resource, the return status code is 416. 206 indicates that some data of the resource is captured, and 416 indicates that the Range of the requested resource cannot be satisfied. Based on this return status, we can determine whether to continue the request and thus whether the file download is complete.

So let’s go back to the problem at the beginning, when you can see that the total file size is obtained from the beginning, why some downloads show the size of the downloaded file and some don’t.

If gzip compression is enabled on the server, you will find that the HTTP response header does not return the content-Length. You can see that the response header does not have content-Length. Do you know why the total size is sometimes not displayed when downloading files?

Sometimes the server does not calculate the total size of the file if compression is enabled, or to reduce CPU stress, etc., so the total size of the resource cannot be obtained from the response header.

However, if you are using a Node server and do not use any plugins, you may find that the file contents are still retrieved even if the request header Range:bytes=xx-xx is included. Nginx, Apache, and other servers have their own implementation methods, so how to implement Node server?

The following code is based on the IMPLEMENTATION of the KOA framework with the ability to download files in segments.

const fs = require("fs");
const path = require("path");
const Koa = require("koa");
const app = new Koa();
const PATH = "./public";

app.use(async ctx => {
  const file = path.join(__dirname, `${PATH}${ctx.path}`); // 1, 404 Check try {fs.accesssync (file); } catch (e) {return (ctx.response.status = 404);
  }
  //ctx.set('content-encoding'.'gzip'); const method = ctx.request.method; const { size } = fs.statSync(file); // return the file size in response to the head requestif ("HEAD" == method) {
    return ctx.set("Content-Length", size);
  }
  const range = ctx.headers["range"]; // 3. Notify the browser that it can make partial requestsif(! Ctx. body = fs.createreadStream (file);return ctx.set("Accept-Ranges"."bytes");
  } else{ const { start, end } = getRange(range); // check the scope of the requestif (start >= size) {
      ctx.response.status = 416;
      return ctx.set("Content-Range", `bytes */${size}`); } // ctx.response.status = 206; ctx.set("Accept-Ranges"."bytes");
    ctx.set("Content-Range", `bytes ${start}-${end ? end : size - 1}/${size}`); ctx.body = fs.createReadStream(file, { start, end }); }}); app.listen(3000, () => console.log("partial content server start"));

function getRange(range) {
  const match = /bytes=([0-9]*)-([0-9]*)/.exec(range);
  const requestRange = {};
  if (match) {
    if (match[1]) requestRange.start = Number(match[1]);
    if (match[2]) requestRange.end = Number(match[2]);
  }
  return requestRange;
}

Copy the code

As you can see, segmented downloading is very simple. Node reads the binary stream of the file in segments based on the Range of the request header.

Reference: blog.csdn.net/weixin_3383…