This is the 11th day of my participation in the August More Text Challenge
The preface
In our work, we often meet the scene of downloading large files, such as requesting video, audio, or some compressed packages from the server. If it is just a simple download, it will take a lot of time, and the user experience is very poor. So how do we do a good job of downloading large files, improve the speed, and strengthen the user experience?
Ways to speed things up
In life, there is a complicated thing, such as to move 100 boxes of water, one person may take a morning to move, but 10 people move, the efficiency can be improved 10 times. Another example is that there are 100 cars to cross a bridge, only one lane is open, and only one lane can pass at a time. If we want to pass all of them as soon as possible, we need to open more lanes, which is the concept of space for time.
In our large file download, we will also apply this concept, we will take this concept as the core, design a reasonable download way, to increase our download channels, improve the speed.
Implementation method preset
-
We need to figure out how to split a large file on the server into multiple requests.
-
How to control the download flow.
-
How to assemble after downloading.
-
And then SAVE it
Let’s look at a simplified flow chart
Example Query whether the server supports HTTP range requests
Accept-Ranges
The HTTP header of the Accept-Ranges response is a marker used by the server to notify that it supports part of the request. The value of this field represents the units that can be used to define the scope.
If there is an Accept-range header, the browser may try to resume the interrupted download rather than start again from scratch
Accept-range has two states
Accept-Ranges: bytes
Accept-Ranges: none
Copy the code
Example Query whether the server supports HTTP range requests
Accept-Ranges
The HTTP header of the Accept-Ranges response is a marker used by the server to notify that it supports part of the request. The value of this field represents the units that can be used to define the scope.
If there is an Accept-range header, the browser may try to resume the interrupted download rather than start again from scratch
Accept-range has two states
Accept-Ranges: bytes
Accept-Ranges: none
Copy the code
Example Query whether the server supports HTTP range requests
Accept-Ranges
The HTTP header of the Accept-Ranges response is a marker used by the server to notify that it supports part of the request. The value of this field represents the units that can be used to define the scope.
If there is an Accept-range header, the browser may try to resume the interrupted download rather than start again from scratch
Accept-range has two states
Accept-Ranges: bytes
Accept-Ranges: none
Copy the code
None has no scope unit supported, which makes its title equivalent to its own absence and is therefore rarely used, although some browsers, such as IE9, use it to disable or remove the pause button in the download manager.
The bytes range is in bytes.
Range
The range is used to request multiple parts at once, which the server returns as a multipart file.
206 Partial Content: The server returned a Partial Content response.
The status code is 416 Range Not Satisfiable: Indicates that the request Range is invalid or the client is wrong.
Status code 200: the server is allowed to ignore the Range header and return the entire file
Method of use
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>
Copy the code
Unit: The unit in which a range request is made, usually bytes.
Range-start: specifies the start value of a range, an integer.
Range-end: Indicates the end value of the range. If it does not exist, it continues to the end of the file.
Send range request
Write a method that starts with input and output parameters
First we send the Range request, first the URL, then the start and end of the Range. But since we are sending a range request, we also need to consider the concatenation of the file, so we also need the current index value of the file, and finally we confirm that our input parameter is (URL, start, end, I).
function getBinaryContent(url, start, end, i) { return new Promise((resolve, reject) => { try { let xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.setRequestHeader("range", `bytes=${start}-${end}`); Xhr. responseType = "arrayBuffer "; // ArrayBuffer xhr.onload = function () {resolve({index: I, // file block's index buffer: xhr.response, // range request corresponding data}); }; xhr.send(); } catch (err) { reject(new Error(err)); }}); }Copy the code
Get file size
Request via HEAD
var url = 'http://'; Var fileSize = 0; Var XHR = new XMLHttpRequest(); xhr.open('HEAD', url, true); Xhr.onreadystatechange = () => {if (xhr.readyState == 4) {if (xhr.status == 200) {fileSize = xhr.getResponseHeader('Content-Length'); console.log(fileSize) } else { alert('ERROR'); }}}; xhr.send()Copy the code
After we get the file size in the request header, we can send the scope request. The next thing to consider is designing asyncPool concurrency control to execute our scope request
Concurrent download
We realize concurrent download, actually is the application of async-pool implementation principle. There is a good article about this library, click here if you want to understand more.
We can see that the input parameter is the maximum number of tasks, task array
async function asyncPool(poolLimit, array, iteratorFn) { const ret = []; Const Executing = []; // Store all asynchronous tasks const executing = []; For (const item of array) {const p = promise.resolve ().then(() => iteratorFn(item, array)); ret.push(p); If (poolLimit <= array.length) {if (poolLimit <= array.length) { Const e = p.chen (() => executing.splice(executing.indexof (e), 1)) executing.push(e); // Save the executing asynchronous task if (executing.length >= poolLimit) {await promise.race (executing); // Wait for faster tasks to complete}}} return promise.all (ret); }Copy the code