Today, everybody is good I am the autumn wind brings about is the theme of the file to download, before I had to send a file upload the article (article, understand the file upload process (1.8 w word depth resolution, advanced for 200 + thumb up), the response is good, after many days, because recently have some media related work, therefore intend to do to download a arrange, So was born his brother, a guide to the secrets of file downloading. This article will take you a long time to read, so I suggest you bookmark/like it first, and then check out the sections that interest you. It can also act as a dictionary.
🙂 not whole do not know, whole, incredibly whole so much situation, I just want to simply make a page son.
preface
Read the full text first to see if the outline is suitable for you. If you like, read on.
What I want to do in this video is to do a little bit of pre-knowledge, a little bit of basic knowledge, if you think you are this. ⬇️⬇️⬇️, you can skip the foreword.
The front end downloads files mainly through , plus the download properties, which make downloading easy.
Download This property instructs the browser to download the URL rather than navigate to it, so the user is prompted to save it as a local file. If the property has a value, this value will be used as a prepopulated file name during the download save process (the user can still change the file name if needed). This property has no restrictions on the allowed values, but/and \ are converted to underscores. Most file systems restrict the punctuation in filenames, so the browser will adjust the suggested filenames accordingly. (from developer.mozilla.org/zh-CN/docs/…).
Note:
- This property applies only to same-origin urls.
- Although the HTTP URL needs to be in the same source, blob: URL and Data: URL can be used to make it easy for users to download content generated using JavaScript (such as photos created using an online drawing Web application).
So there are three main ways to download urls. (Most of this article is demonstrated as bloBs)
compatibility
As you can see, the compatibility is impressive (www.caniuse.com/#search=dow…
To avoid a lot of code duplication, I’ve pulled out a few common functions. (This part can be skipped, the names are more readable, if you don’t understand later, you can look here)
export function downloadDirect(url) {
const aTag = document.createElement('a');
aTag.download = url.split('/').pop();
aTag.href = url;
aTag.click()
}
export function downloadByContent(content, filename, type) {
const aTag = document.createElement('a');
aTag.download = filename;
const blob = new Blob([content], { type });
const blobUrl = URL.createObjectURL(blob);
aTag.href = blobUrl;
aTag.click();
URL.revokeObjectURL(blob);
}
export function downloadByDataURL(content, filename, type) {
const aTag = document.createElement('a');
aTag.download = filename;
const dataUrl = `data:${type}; base64,The ${window.btoa(unescape(encodeURIComponent(content)))}`;
aTag.href = dataUrl;
aTag.click();
}
export function downloadByBlob(blob, filename) {
const aTag = document.createElement('a');
aTag.download = filename;
const blobUrl = URL.createObjectURL(blob);
aTag.href = blobUrl;
aTag.click();
URL.revokeObjectURL(blob);
}
export function base64ToBlob(base64, type) {
const byteCharacters = atob(base64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const buffer = Uint8Array.from(byteNumbers);
const blob = new Blob([buffer], { type });
return blob;
}
Copy the code
🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅 🚅
(Manually draw a dividing line for the big guy who doesn’t read the above)
🇨 🇳
All sample Github addresses: github.com/hua1995116/…
Online Demo: qiufeng. Blue/Demo/file – d…
The back-end
All of the examples in this backend are implemented in KOA/native JS.
The back end returns a file stream
In this case, we simply open the file stream returned by the backend in a new window and download it directly.
// Front end code<button id="oBtnDownload">Click on the download</button>
<script>
oBtnDownload.onclick = function(){
window.open('http://localhost:8888/api/download? filename=1597375650384.jpg'.'_blank')}</script>
Copy the code
// The backend code
router.get('/api/download'.async (ctx) => {
const { filename } = ctx.query;
const fStats = fs.statSync(path.join(__dirname, './static/', filename));
ctx.set({
'Content-Type': 'application/octet-stream'.'Content-Disposition': `attachment; filename=${filename}`.'Content-Length': fStats.size
});
ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
})
Copy the code
There are two main situations in which the browser can automatically download files:
One for useContent-Disposition
Properties.
Let’s look at the description of this field.
In a regular HTTP response, the Content-Disposition response header indicates how the Content of the response should be displayed, inline (i.e., a web page or part of a page), Or download it as an attachment and save it locally – source MDN(developer.mozilla.org/zh-CN/docs/…)
Let’s look at the syntax
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"
Copy the code
It’s easy, just set it to the last form and I’m able to get the files to download from the back end.
The other type is not recognized by the browser
Such as http://localhost:8888/static/demo.sh, the browser can’t identify the type, will automatically download.
We entered a correct static JS address with no Content-disposition, but it was accidentally downloaded.
For example, this is the case.
This is most likely because your Nginx is missing this configuration line.
include mime.types;
Copy the code
By default, the application/octet-stream file is downloaded.
The back end returns the static site address
Through static site download, here to be divided into two cases, one is the service may come with static directory, that is, homology, the second case is applicable to the third-party static storage platform, such as Ali Cloud, Tencent cloud hosting, that is, non-homology (of course, some platforms will return directly).
homologous
In the homologous case, it’s very simple. You can download the code and call the function directly.
import {downloadDirect} from '.. /js/utils.js';
axios.get('http://localhost:8888/api/downloadUrl').then(res= > {
if(res.data.code === 0) { downloadDirect(res.data.data.url); }})Copy the code
The homologous
We can also see from MDN, although Download limits non-homologous cases, but!! But!!!!! But blob: URL and Data: URL are available, so we just need to download the contents of the file and convert them into bloBs.
The process is as follows
<button id="oBtnDownload">Click on the download</button>
<script type="module">
import {downloadByBlob} from '.. /js/utils.js';
function download(url) {
axios({
method: 'get',
url,
responseType: 'blob'
}).then(res= > {
downloadByBlob(res.data, url.split('/').pop());
})
}
oBtnDownload.onclick = function(){
axios.get('http://localhost:8888/api/downloadUrl').then(res= > {
if(res.data.code === 0) { download(res.data.data.url); }})}</script>
Copy the code
Now non-homologous ones can happily download as well.
Back end returns string (base64)
Sometimes we will encounter some new back end return strings, this is rare, but we do not panic, by the way, back end little boy show a wave of operations, no matter what data, we can download for you.
Ps: the premise is a safe and pollution-free resource :), the sign of serious articles is shining.
In this case, I need to simulate the back end operator’s SAO operation, so there is back end code.
Core process
/ / the node
router.get('/api/base64'.async (ctx) => {
const { filename } = ctx.query;
const content = fs.readFileSync(path.join(__dirname, './static/', filename));
const fStats = fs.statSync(path.join(__dirname, './static/', filename));
console.log(fStats);
ctx.body = {
code: 0.data: {
base64: content.toString('base64'),
filename,
type: mime.getType(filename)
}
}
})
Copy the code
/ / the front<button id="oBtnDownload">Click on the download</button>
<script type="module">
import {base64ToBlob, downloadByBlob} from '.. /js/utils.js';
function download({ base64, filename, type }) {
const blob = base64ToBlob(blob, type);
downloadByBlob(blob, filename);
}
oBtnDownload.onclick = function(){
axios.get('http://localhost:8888/api/base64? filename=1597375650384.jpg').then(res= > {
if(res.data.code === 0) { download(res.data.data); }})}</script>
Copy the code
Pure front end
The above describes the related methods to complete the file download with the help of the back end. Next, let’s introduce some methods to complete the file download purely on the front end.
Method 1: BLOb: URL
Method 2: data: URL
Since data: URLS are limited in length, all of the following examples will be shown as bloBs.
json/text
Downloading text and JSON is easy enough to construct a Blob directly.
Blob(blobParts[, options]) returns a newly created Blob object whose contents are concatenated from the array given in the arguments.Copy the code
// html
<textarea name="" id="text" cols="30" rows="10"></textarea>
<button id="textBtn">Download the text</button>
<p></p>
<textarea name="" id="json" cols="30" rows="10" disabled>{"name": "note of autumn wind"}</textarea>
<button id="jsonBtn">Download the JSON</button>
Copy the code
//js
import {downloadByContent, downloadByDataURL} from '.. /js/utils.js';
textBtn.onclick = () = > {
const value = text.value;
downloadByContent(value, 'hello.txt'.'text/plain');
// downloadByDataURL(value, 'hello.txt', 'text/plain');
}
jsonBtn.onclick = () = > {
const value = json.value;
downloadByContent(value, 'hello.json'.'application/json');
// downloadByDataURL(value, 'hello.json', 'application/json');
}
Copy the code
rendering
This is the first example, so I’ll show you the code, but you can also call the downloadByDataURL method. If you can’t find the definition of this method, slide to the beginning of this article
excel
Excel can be said to be part of our front end dealing with a very deep scene, what data in the platform, every day need to export a variety of reports. It used to be that the front end asked the back end to get an Excel file address. Now let’s show you how the pure front end implements downloading Excel.
A simple excel
The form looks like this. It’s a crude form
const template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" '
+'xmlns:x="urn:schemas-microsoft-com:office:excel" '
+'xmlns="http://www.w3.org/TR/REC-html40">'
+'<head>'
+'</head>'
+'
{table}
<\/body>'
+'<\/html>';
const context = template.replace('{table}'.document.getElementById('excel').innerHTML);
downloadByContent(context, 'qiufengblue.xls'.'application/vnd.ms-excel');
Copy the code
But it is not complicated to write, as we have done before, through the construction of excel format, converted into bloB to download.
The result of the final export
Element-ui exports the table
Yes, this is an example of the element-UI official table.
The export looks like this, which is perfect.
Here we use a plugin github.com/SheetJS/she…
Very simple to use.
<template>
<el-table id="ele" border :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180">
</el-table-column>
<el-table-column prop="name" label="Name" width="180">
</el-table-column>
<el-table-column prop="address" label="Address">
</el-table-column>
</el-table>
<button @click="exportExcel">Export excel</button>
</template>
<script>.methods: {
exportExcel() {
let wb = XLSX.utils.table_to_book(document.getElementById('ele'));
XLSX.writeFile(wb, 'qiufeng.blue.xlsx'); }}...</script>
Copy the code
word
Now that we’re done with Excel we’re going to talk about Word which is another great tool of the Office three musketeers. Here we are still using the bloB method mentioned above to download.
A simple example
The code shown
exportWord.onclick = () = > {
const template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" '
+'xmlns:x="urn:schemas-microsoft-com:office:word" '
+'xmlns="http://www.w3.org/TR/REC-html40">'
+'<head>'
+'</head>'
+'<body>{table}<\/body>'
+'<\/html>';
const context = template.replace('{table}'.document.getElementById('word').innerHTML);
downloadByContent(context, 'qiufeng.blue.doc'.'application/msword');
}
Copy the code
Results show
usedocx.js
The plug-in
If you want more advanced usage, you can use the docx.js library. Of course, the above method can also be advanced customization.
code
<button type="button" onclick="generate()">Download the word</button>
<script>
async function generate() {
const res = await axios({
method: 'get'.url: 'http://localhost:8888/static/1597375650384.jpg'.responseType: 'blob'
})
const doc = new docx.Document();
const image1 = docx.Media.addImage(doc, res.data, 300.400)
doc.addSection({
properties: {},
children: [
new docx.Paragraph({
children: [
new docx.TextRun("Welcome to the official account of [Qiufeng's Notes]").break(),
new docx.TextRun("").break(),
new docx.TextRun("Send quality articles regularly").break(),
new docx.TextRun("").break(),
new docx.TextRun("2020 College Recruitment of Meituan-Dianping - Internal Promotion").break(),
],
}),
new docx.Paragraph(image1),
],
});
docx.Packer.toBlob(doc).then(blob= > {
console.log(blob);
saveAs(blob, "qiufeng.blue.docx");
console.log("Document created successfully");
});
}
</script>
Copy the code
Effect (without advertising… Randomly find a picture, forced not to admit the series)
Download the zip
Front-end compression can also be very useful, in certain scenarios, to save traffic. This scenario is more used in, for example, front packaging image download, front packaging download icon.
At first I thought that was what I was using on Tinypng.com/, but I was wrong… Come to think of it, because the compressed images are stored in the back end, if you use the front end packaging, you have to request all the compressed images to get the image stream. If you use back – end compression, you can effectively save traffic. B: well… The failure example ended.
Later thought that www.iconfont.cn/ package download icon, so that… .
Its official website is full of SVG rendered ICONS, for SVG download, you can use the front-end package download. However, it also supports font and JPG formats, so for the sake of uniformity, back-end downloads are used, which makes sense. So let’s implement this unfinished function, and of course we need to use a plug-in, which is jszip.
Here I found two SVG ICONS from above.
The implementation code
download.onclick = () = > {
const zip = new JSZip();
const svgList = [{
id: 'demo1'}, {id: 'demo2',
}]
svgList.map(item= > {
zip.file(item.id + '.svg'.document.getElementById(item.id).outerHTML);
})
zip.generateAsync({
type: 'blob'
}).then(function(content) {
// The file name to download
var filename = 'svg' + '.zip';
// Create a hidden downloadable link
var eleLink = document.createElement('a');
eleLink.download = filename;
// Convert the downloaded content to the bloB address
eleLink.href = URL.createObjectURL(content);
// Trigger the click
eleLink.click();
// Then remove
});
}
Copy the code
Check the folder directory where you have packed and downloaded SVG.
Browser File System (experimental)
I have one on my PC to learn about and debug the latest features in Chrome. If you don’t have one, I recommend you install one.
Chrome ://flags => #native-file-system-api => enable Download a Canary version of Chrome to play with.
<textarea name="" id="textarea" cols="30" rows="10"></textarea>
<p><button id="btn">download</button></p>
<script>
btn.onclick = async() = > {const handler = await window.chooseFileSystemEntries({
type: 'save-file'.accepts: [{
description: 'Text file'.extensions: ['txt'].mimeTypes: ['text/plain']],}});const writer = await handler.createWritable();
await writer.write(textarea.value);
await writer.close();
}
</script>
Copy the code
Very simple to implement. I feel like I’m flying.
Other scenarios
H5 file download
Generally in THE H5 download is more PDF or APK download.
Android
In android, the browser downloads the file directly.
ios
Due to ios restrictions, it is not possible to download, so you can use the copy URL instead of downloading.
import {downloadDirect} from '.. /js/utils.js';
const btn = document.querySelector('#download-ios');
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
const clipboard = new ClipboardJS(btn);
clipboard.on('success'.function () {
alert('Copy the link, open the browser and paste the link to download');
});
clipboard.on('error'.function (e) {
alert('System version is too early, replication link failed');
});
} else {
btn.onclick = () = > {
downloadDirect(btn.dataset.clipboardText)
}
}
Copy the code
More and more
For apK and other download packages can use this package (I have not tested, contact is not much, back to familiar with the supplement.)
Github.com/jawidx/web-…
Shard download of large files
Recently, when I was working on the development of media streaming, I found an interesting phenomenon when I was loading an MP4 file. The video stream did not need to be downloaded before playing, and there were many requests with status code 206. At first sight, it looked like streaming media (HLS, etc.).
I think this phenomenon is very interesting, because it can load resources in fragments, which is very helpful for experience or traffic saving. It turns out it has a header called Range. Let’s look at the MDN explanation.
The Range is a request header that tells The server which part of The file to return. In a Range header, you can request more than one part at a time, and the server returns it as a multipart file. If the server returns a range response, use the 206 Partial Content status code. From MDN
grammar
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
The Node implementation
Now that we know how it works, let’s implement it ourselves.
router.get('/api/rangeFile'.async(ctx) => {
const { filename } = ctx.query;
const { size } = fs.statSync(path.join(__dirname, './static/', filename));
const range = ctx.headers['range'];
if(! range) { ctx.set('Accept-Ranges'.'bytes');
ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
return;
}
const { start, end } = getRange(range);
if (start >= size || end >= size) {
ctx.response.status = 416;
ctx.set('Content-Range'.`bytes */${size}`);
ctx.body = ' ';
return;
}
ctx.response.status = 206;
ctx.set('Accept-Ranges'.'bytes');
ctx.set('Content-Range'.`bytes ${start}-${end ? end : size - 1}/${size}`);
ctx.body = fs.createReadStream(path.join(__dirname, './static/', filename), { start, end });
})
Copy the code
Nginx implementation
I found out that Nginx supports the range header by default without writing any code. I thought I must know whether it supports it, what modules have been added, or what configuration I have enabled by default.
Just when I was about to give up, I had an Epiphany and went to look at the source code. Maybe I might find something. After looking up the relevant content of nginx source code, I used the usual backward push method and found that it was the max_ranges field.
Github.com/nginx/nginx…
It was also my fault that I didn’t read the documents carefully enough at the beginning and wasted a lot of time.
In fact, I am not familiar with the nginx source code, here can use a small technique, directly in the source code library search 206 and found a macro command
#define NGX_HTTP_PARTIAL_CONTENT 206
Copy the code
Then we can follow the instructions directly to where the NGX_HTTP_PARTIAL_CONTENT macro is used, so that we can find what we want step by step.
By default, the range header is automatically enabled in nginx. If this is not required, set max_range: 0.
Nginx configuration document nginx.org/en/docs/htt…
conclusion
To sum up, in fact, the full text mainly discusses the two core knowledge of (XBB), one is bloB and the other is A tag. In addition, we should pay attention to the optimization strategy of the server for large files, which can be loaded in fragments through Range.
The resources
Github.com/dolanmiu/do…
Github.com/SheetJS/she…
Juejin. Im/post / 684490…
The last
If my article can help you, I hope you can also help me, welcome to pay attention to my public account Qiufeng notes, reply friends two times, can add friends and join the communication group, Qiufeng notes will always accompany you around.