This article will talk about front-end file downloads.

Said to the front to download files, I first think of is in school, their structures, nginx + PHP environment, after open the page, http://localhost:80/index.php, it’s strange how, every open into a file download.

Later I learned that there would be an Accept field in the request header and a Content-Type field in the response header. The former tells the S-side what Type of Content it can Accept, and the latter tells the C-side what Type of Content it will return.

MIME

MIME is a standardized way to represent the nature and format of a document, and browsers often use MIME to determine types (rather than file extensions).

Content-type uses MIME types, JPG files correspond to image/ JPEG, js files correspond to Application /javascript, XLSX is application/VND. Openxmlformats – officedocument. Spreadsheetml. Sheet.

MIME has two default types:

  • text/plainRepresents the default value for a text file. A text file should be human-readable and contain no binary data.
  • application/octet-streamRepresents the default value for all other cases. An unknown file type should use this type.

A complete list of MIME types

πŸ‘†index.php becomes a file download because I did not parse the PHP file properly due to an installation error. Nginx accesses the file directly and adds the default contentType application/octet-stream. Since Chrome cannot execute application/octet-stream files, the default action is to download them. (Different browsers do different things to unprocessed files, and some browsers will try to sniff them out.)

This also explains why when we go directly to a resource like https://xxx/foo/bar.zip, the browser downloads it directly.

Safety lesson:

When a server returns a MIME type that is not supported by the browser, some browsers will try to sniff it out and help careless developers fix the error, but this can lead to attacks on your site. For example, a user uploads a picture of a giant panda with the following content:

It’s actually an HTML file, but the suffix is uploaded as jpeg. If no contentType is set, the server reads the file and returns it to the front end.

Router. Get ('/assets/:file.jpeg', (ctx) => { ctx.body = fs.createReadStream(`./public/assets/${ctx.params.file}.jpeg`); });Copy the code

A well-meaning browser gets a MIME type of Application /octet-stream, reads it, and says, hey, this is HTML, we should display it. 🌚 🌚 🌚

When users saw the cute pandas, they also gave their personal information to the hackers.

To avoid such a security incident, set

  • Append the corresponding contentType to the returned content.
  • Add a response headerX-Content-Type-Options: nosniff, let the browser not try to sniff
router.get('/assets/:file.jpeg', (ctx) => {
  ctx.type = 'image/jpeg';
  ctx.set('X-Content-Type-Options'.'nosniff');
  ctx.body = fs.createReadStream(`./public/assets/${ctx.params.file}.jpeg`);
});
Copy the code

For demonstration purposes only, koA should provide static resource services using open source packages such as KOA-static, which automatically add contentType.

How do I get my browser to download images

The document types are not supported by the browser and will be downloaded by default. What about the types that can be handled? What about images, JS, JSON, etc.?

In the case of JSON, the browser will print out the content of JSON on the page because it knows how to parse it.

What if the requirement is for the user to download a JSON file?

There is another response header field, conten-Disposition πŸ‘Ή, where content-disposition specifies whether the Content of the response should be displayed inline (i.e. as a web page or part of a page) or downloaded and saved locally as an attachment, Correspond to inline and attachment, respectively.

Content-Disposition: inline
Content-Disposition: attachment
Copy the code

Attachment mode, you can also specify the file name and file extension of the downloaded file.

Content-Disposition: attachment; filename="filename.jpg"
Copy the code

Sample code:

router.get('/hello.json', (ctx) => {
  ctx.type = 'application/json';
  ctx.set('Content-Disposition'.'attachment; filename="hello.json"');
  // Ctx.attachment ('hello.json');
  ctx.body = {
    hello: 'world'}; });Copy the code

Then access the route and see the file download.

Download HTML attributes

There is also a way for the browser to save the file locally. This is the DOWNLOAD attribute added to the HTML5 A tag.

<a href="/images/xxx.jpg" download="panda.jpg" >My Panda</a>
Copy the code

When the user clicks on the tag, the user downloads the href file, and the value of the Download attribute corresponds to the name of the downloaded file. A more flexible approach is to encapsulate methods, dynamically create links, and trigger click to download and save as directly.

<script>
function downloadAs (url, fileName) {
  const link = document.createElement('a');
  link.href = url;
  link.download = fileName;
  link.target = '_blank'

  document.body.appendChild(link);
  link.click();
  link.remove();
}

downloadAs('http://localhost:3001/hello.json'.'world.json');
</script>
Copy the code

Asynchronously obtain resources and then download them

In other scenarios, binary content can only be returned by an asynchronous request and downloaded by the front end.

The Download attribute, combined with Blob and url.createObjecturl (), enables the front-end to request resources and export files asynchronously.

const xhr = new XMLHttpRequest();
xhr.open('GET'.'http://localhost:3001/pack.zip');
xhr.responseType = 'blob';

xhr.onload = function () {
  const blob = xhr.response;
  const url = URL.createObjectURL(blob);
  downloadAs(url, 'mypack.zip');
  URL.revokeObjectURL(url);
};
xhr.send();
Copy the code

Set xhr.responseType = ‘blob’ and when the request completes, xhr.responseType returns the blob object, url.createObjecturl (blob), and returns a link to the blob that looks like this: Blob: http://localhost:3001/11a01a60-e10c-4515-825f-fb4a4219b33b. And then you can set the href of the A tag as if it were a normal URL.

A Blob object represents an immutable, raw data-like file object. The File object is also based on this extension, temporarily understood as an abstract File object.

CreateObjectURL creates a URL linking to a Blob or File object. The lifetime of the URL is bound to the window, so to avoid memory leaks you should call url.revokeobjecturl ().

Blobs can accept Javascript primitively typed data as arguments, such as pure front-end mock data, and export it to CSV files.

const rows = [
  ["id"."firstname"."lastname"],
  ["1"."foo"."foo"],
  ["2"."bar"."baz"]];const data = rows.reduce(function(cur, next) {
  return cur + next.join(', ') + '\n';
}, ' ');
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
downloadAs(url, 'mock.csv');
Copy the code

compatibility

The Download property is not very compatible, currently only 80%. FileSaver. Js can be used directly to do fallback processing.

Further reading

  • FileSaver.js
  • Saving a remote file

teasing

The article was originally titled “Universe’s Strongest Front End Drag upload and File Download”, but in the middle of the article, I found that many people had written similar articles about Nuggets.

  • Suger team _ front-end implementation of file download and drag upload
  • LuffyZhou_ This should be the most complete front-end download summary you’ve ever seen
  • .

It’s too late to change the draft. That’s it. Wish you all a happy Spring Festival and a thriving year-end bonus.