Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing documentation for downloading binary files #448

Closed
demetriusnunes opened this issue Sep 14, 2016 · 35 comments
Closed

Missing documentation for downloading binary files #448

demetriusnunes opened this issue Sep 14, 2016 · 35 comments

Comments

@demetriusnunes
Copy link

I had to do a lot of trial and error, until I found out that adding "responseType" as "arraybuffer" would do the trick.

@aendra-rininsland
Copy link

+1 for this. I was really lost until I found this issue.

@beeant
Copy link

beeant commented Jan 5, 2017

me too! thank you @demetriusnunes !!

@ahallora
Copy link

ahallora commented Feb 9, 2017

In case other stumple upon this thread when looking for an answer to serve external images from node using axios and express without having to save the external file to disk first. To clarify where to put responseType parameter as stated by @demetriusnunes , here's an example:

var imageurl = 'https://avatars2.githubusercontent.com/u/5302751?v=3&s=88';

axios.get(imageurl, {
    responseType: 'arraybuffer'
})
.then(function (response) {
    res.type('image/jpeg');
    res.end( response.data, 'binary' );
});

Happy coding 😄

@bjunc
Copy link

bjunc commented Mar 21, 2017

And if you were like me, and you stumble upon this thread looking for a method to download images client-side, here's an example:

var imageUrl = "myfavoriteimage.jpg";
var imageEl = el.querySelector("img");

axios.get(imageUrl, { responseType:"blob" })
    .then(function (response) {

	var reader = new window.FileReader();
	reader.readAsDataURL(response.data); 
	reader.onload = function() {
					
	    var imageDataUrl = reader.result;
	    imageEl.setAttribute("src", imageDataUrl);
	}
});

@timster
Copy link

timster commented Apr 25, 2017

@bjunc Thank you! Wish I would have discovered this thread 2 hours ago.

@vinikatyal
Copy link

What about when downloading excel files?

@imzyf
Copy link

imzyf commented Jun 13, 2017

@vinikatyal Have you solved your problem?

@2ming
Copy link

2ming commented Jun 29, 2017

@imzyf @vinikatyal Have you solved your problem?

@imzyf
Copy link

imzyf commented Jun 29, 2017

@2ming no, I use GET request to download excel file.

@Itachi-Uchiha78
Copy link

...and excels in a zipped file...? Tried many approach nothing works from browser...

@ribeiroguilherme
Copy link

@vinikatyal @imzyf did you solve the excel problem? I'm facing with the same here.

@MauScheff
Copy link

I solved the excel problem using responseType: blob, the right 'Accept' header, and the external library downloadjs:

      this.axios({
        url: baseURL + '/employees/all/download/',
        method: 'get',
        withCredentials: true,
        responseType: 'blob',
        headers: {
          'Accept': 'application/vnd.openxmlformats-officedocument'
           + '.spreadsheetml.sheet',
        },
      }).then(function(response) {
        download(response.data, 'export-directorio.xlsx');
      });

cc/ @ribeiroguilherme @2ming @imzyf @vinikatyal

@Itachi-Uchiha78
Copy link

Itachi-Uchiha78 commented Jul 25, 2017

yes, same here, but what about the name? Let's say you want to pass the name to frontend...
In my project I can pass an .xlsx or a .zip file ( if i need to return more than one excel file).
That means I need to pass the correct file name plus correct extension. Now I've amended using return type int to understand how many file there are (ex: 200 = 1 xlsx file , 201 = zip...), but still don't know how to pass a String name plus the blob.

@MauScheff
Copy link

@Itachi-Uchiha78 You should somehow resolve that in the backend. Why not always zip it and just download a zip file?

@Itachi-Uchiha78
Copy link

because customers don't want unzip a single file....they just want it download and open....

@MauScheff
Copy link

@Itachi-Uchiha78 You should ask how to solve your issue in Stack Overflow. This is not an issue with axios.

@Nevosis
Copy link

Nevosis commented Aug 21, 2017

Thank you for this thread @demetriusnunes , half a day to find this.

@fritx
Copy link

fritx commented Sep 26, 2017

I use js-file-download, matching the filename given from backend.

import fileDownload from 'js-file-download'
import axios from 'axios'

let res = await axios({
  // ...
  responseType: 'blob'
})
let disposition = res.headers['content-disposition']
let filename = decodeURI(disposition.match(/filename="(.*)"/)[1])
fileDownload(res.data, filename)

Note that if you have to handle error in json while downloading files in blob:
I had a hack mentioned at #815 (comment)

@webdevbyjoss
Copy link

And if you were like me, and you stumble upon this thread looking for a method to download images client-side, and would like to avoid FileReader(); due to number of reasons here's an example:

import axios from 'axios'

axios.request({
   url: 'http://mydomain.com/path/myimage.jpeg',
   responseType: 'blob',
})
    .then(response => response.data)
    .then(blob => URL.createObjectURL(blob))
    .then(src => this.setState({ src }))  // OR imageEl.setAttribute("src", src);
    .catch(onFail);

Don't forget to cleanup the image once you don't need it, for ex:

  componentWillUnmount() {
    if (this.state.src !== null) {
      URL.revokeObjectURL(this.state.src)
    }
  }

@kkbxxi
Copy link

kkbxxi commented Oct 19, 2017

And is there a way to download the file directly to disk as a stream, instead of first getting it into response.data?

Hopefully, with a "resume download" happening automagically?

@kgrosvenor
Copy link

kgrosvenor commented Nov 15, 2017

For me this worked, and it's also passing Authorisation headers which i set globally elsewhere @mauzepeda thank you for your comment example it helped me solve the issue of opening in a new window and changing my backend code.

axios.get(finalUrl, {
          responseType: 'blob',
          headers: {
            'Accept': 'application/vnd.ms-excel'
          }
        })
          .then(function (response) {
            self.isExporting = false;
            download(response.data, 'WeightData.xlsx')
          });

@Superjisan
Copy link

This is money 💰. Thanks for reporting on this.

Just in case someone else was doing something similar to me, this is what I did

//initial request
const config = {
        params,
        url: `${endPoint}`,
        headers: {
            'Accept': 'application/vnd.ms-excel'
        },
        responseType: 'blob'
    };
 return request(endPoint, config)
   .then(data => {
        let blob = new Blob([data], {type: ''});
        const blobHref = window.URL.createObjectURL(blob);
        //... dispatching actions to download the file within a react app
 })

@Pixelatex
Copy link

Pixelatex commented Jan 8, 2018

I'm puzzled, I have to download any possible file but it keeps corrupting my binary files.. I tried download as blob, array buffer,... anyone got any idea?

I noticed a lot of people including the exact file type they are expecting as a header which is not possible for me :/

 axios(url, {
                method: 'GET',
                responseType: 'arrayBuffer',
                headers: {
                    'x-user': getEnvironment().DB_USER,
                    'x-password': getEnvironment().DB_PASSWORD
                }
            })
                .then(response => {
                    res.json({
                        error: null,
                        data: response.data
                    })
                })
                .catch(e => {
                    console.log(e, 'error download')
                    res.json({error: e.message, documents: []})
                })

Fetching the files with postman actually works with these headers so that's not the issue

@Superjisan
Copy link

Superjisan commented Jan 8, 2018

@Pixelatex, not sure if this might be the issue or a typo, but you should set the responseType as blob not blog
Also, make sure to add 'Accept': 'application/vnd.ms-excel' in the header object if that is what the server is passing back to you.

@Pixelatex
Copy link

autocorrect in fact, sadly it is not that easy a fix :/

@fritx
Copy link

fritx commented Jan 9, 2018

The workaround is worth a try: #815 (comment)

@thevangelist
Copy link

I was one of the unlucky ones as well.. Here's my solution:

  • set responseType: 'arraybuffer' in Axios
  • response handling /w js-file-download:
.then((response) => {
    this.filedownLoad(response, 'filename.pdf')
})

  • in Node/Express.js backend /w html-pdf:
    pdf.create(htmlDocumentWithStyles, pdfOptions).toStream((err, result) => {
      let body = [] // Store all the chunks of binary data.
      result.on('data', (data) => {
        body.push(data) // Build up an array of Buffers.
      })

      result.on('end', () => {
        res.set('Content-Type', 'application/pdf')
        res.end(Buffer.concat(body), 'binary')
      })
    })

@ekaitzht
Copy link

ekaitzht commented Feb 21, 2018

@mzabriskie @nickuraltsev

You don't think this should be treated as a bug? Why by default any request has be treated as utf8 string?

I think axios should be check 'Content-type' if is binary file (octet-stream, jpeg, zip, etc) and not to stringify the response:

responseData = responseData.toString('utf8');

I think this implementation will avoid a lot of bugs in projects that are using Axios.

@jasperf
Copy link

jasperf commented Jun 12, 2018

Was thinking about something like

saveSearchedUnsplashPhotoToServer : function(result) {
                axios({
                    url: '/backend/upload-image/',
                    method: 'get',
                    responseType: 'stream',
                    headers: {
                    'Accept': '*/* '
                    },
                }).then(function(response) {
                    download(result.links.download_location, 'image-file-name-as-on-other-server');
                })
            }

based on what @mauzepeda wrote. But how can I use the image to be downloaded as name instead of a hardcoded one? And is / the best header for all sorts of images?

@pke
Copy link

pke commented Aug 20, 2018

I really struggle to understand why one has to know the type of response before sending the request. Since the HTTP spec allows the server to respond with anything he wants, and treat Accept as SHOULD how can we know beforehand what the response will be? Can't Axios figure out the responseType internally based on the actual servers response?
Figuring out why axios corrupted a download that downloaded just fine with cURL took hours :(

@quirinobrizi
Copy link

quirinobrizi commented Sep 12, 2018

As pointed out this is a fairly easy fix just using basics of HTTP, just check the content-type response header and/or the accept header instead of require the requestType option.

At the same time I think I understand the idea of responseType as way of making the library more flexible, and I do agree on it as I think flexibility bring more goods than constant guess.
I believe that in a well organised application we can just set the responseType based on the repository we are implementing, reference to DDD, but in general our DALs should have single responsibility so it becomes easy to set the responseType.

Now for cases where the implementation is/need-to-be generic and there is no such luxury we can still guess based on request and response and the media type associate with Accept and Content-Type headers, the difference is that this guess will by based on our business needs which is for sure narrower than the possible scenarios this library should account for.

Anyway my workaround is that:

  1. try determine the responseType based on the accept header, by default return arraybuffer so that we can try guess the response type later:
determineResponseType(req) {
    let answer = 'arraybuffer';
    if (req.headers.accept) {
      const match = /.+\/(.+)/gi.exec(req.headers.accept);
      if (['javascript', 'json', 'plain', 'csv', 'xml', 'css', 'html',].some(st => match[1] == st)) {
        answer = match[1];
      }
    }
    return answer;
  }
  1. if Accept request header is not provided the responseType is set to arraybuffer and as per axios/lib/adapters/http.js the response is returned as-is, that means we can try guess the format from the response.
extractResponseData(res) {
    let answer = res.data;
    if (res.headers['content-type']) {
      const match = /.+\/(.+)/gi.exec(res.headers['content-type']);
      if (['javascript', 'json', 'plain', 'csv', 'xml', 'css', 'html',].some(st => match[1] == st)) {
        answer = answer.toString('utf-8');
      }
    }
    return answer;
  }

After this we have the proper response data and setting the proper Content-Type on your client response, whatever is the framework you use, just copy/cherrypick server response header to client response headers and we will have proper rendering/download on our client. i.e. if we are using express we can simply do something like this:

let responseData = this.extractResponseData(serverResponse);
clientResponse.status(serverResponse.status);
Object.entries(serverResponse.headers || {}).forEach(entry => clientResponse.set(entry[0], entry[1]));
clientResponse.send(responseData);

In short I believe this is not really an issue of the library but a question of interpretation so here the greater good of the library purpose should prevail.

@RadioAc
Copy link

RadioAc commented Mar 15, 2019

thanks, really solved my problem

@darkhorse007
Copy link

Nice

@MoRongQin
Copy link

我试了上面的所有方法,发现都不行
I tried all the above methods, but I found none

@mkupiniak
Copy link

I was one of the unlucky ones as well.. Here's my solution:

  • set responseType: 'arraybuffer' in Axios
  • response handling /w js-file-download:
.then((response) => {
    this.filedownLoad(response, 'filename.pdf')
})
  • in Node/Express.js backend /w html-pdf:
    pdf.create(htmlDocumentWithStyles, pdfOptions).toStream((err, result) => {
      let body = [] // Store all the chunks of binary data.
      result.on('data', (data) => {
        body.push(data) // Build up an array of Buffers.
      })

      result.on('end', () => {
        res.set('Content-Type', 'application/pdf')
        res.end(Buffer.concat(body), 'binary')
      })
    })

You can make is simpler:
// node.js part

pdf.create(html, options).toStream((err, stream) => {
  res.contentType('application/pdf')
  res.attachment() // if you want to download the file
  stream.pipe(res)
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests