46

I have a web service that returns PDF file content in its response. I want to download this as a pdf file when user clicks the link. The javascript code that I have written in UI is as follows:

$http.get('http://MyPdfFileAPIstreamURl').then(function(response){
var blob=new File([response],'myBill.pdf',{type: "text/pdf"});
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="myBill.pdf";
link.click();
});

'response' contains the PDF byte array from servlet outputstream of 'MyPdfFileAPIstreamURl'. And also the stream is not encrypted.

So when I click the link, a PDF file gets downloaded successfully of size around 200KB. But when I open this file, it opens up with blank pages. The starting content of the downloaded pdf file is in the image.

I can't understand what is wrong here. Help !

This is the downloaded pdf file starting contents:

This is the downloaded pdf file starting contents

mkl
  • 90,588
  • 15
  • 125
  • 265
programmer129
  • 469
  • 1
  • 4
  • 4
  • 2
    You are using a media type *"text/pdf"*; this is asking for trouble, **pdf is not a text format, it is binary**, and treating it as text can destroy it. Try **application/pdf** instead. – mkl Dec 23 '15 at 13:30
  • @mkl Changed it to application /pdf but issue is still same. – programmer129 Dec 29 '15 at 07:09
  • Still it is wrong to use text/pdf here. Furthermore your screenshot does look like somewhere along the line some code treats the file as text, reading it with a single byte encoding (Latin1) and writing it with UTF-8. – mkl Dec 29 '15 at 08:07
  • 3
    This might help you: https://stackoverflow.com/a/31763030/5042982. Adding `responseType :blob` worked for me in AngularJS. – AKJ Jul 20 '18 at 14:35

6 Answers6

59

solved it via XMLHttpRequest and xhr.responseType = 'arraybuffer'; code:

var xhr = new XMLHttpRequest();
    xhr.open('GET', './api/exportdoc/report_'+id, true);
    xhr.responseType = 'arraybuffer';
    xhr.onload = function(e) {
       if (this.status == 200) {
          var blob=new Blob([this.response], {type:"application/pdf"});
          var link=document.createElement('a');
          link.href=window.URL.createObjectURL(blob);
          link.download="Report_"+new Date()+".pdf";
          link.click();
       }
    };
xhr.send();
Alexey G
  • 1,068
  • 1
  • 14
  • 18
  • 3
    Thanks @Alexey, I had the same problem, using angularjs I had to put responseType = 'arraybuffer' to – jrey Feb 10 '18 at 02:12
  • 1
    Thanks @Alexey, I struggled with this same issue attempting it with axios in React and typescript. I implemented the above solution with a couple of modifications, and I have it working with a 1.1MB file as a download, as well as, a PDF view in an iframe. – agent-p Mar 24 '19 at 16:53
  • 2
    This applies to modern libraries and fetching as well, e.g. axios `responseType: 'blob'` is necessary – phil294 Apr 04 '20 at 21:49
  • 3
    I was only able to get it working using `responseType: 'arraybuffer'` with axios. I initially tried using `responseType: 'blob'` instead and then converting to an arraybuffer with `Buffer.from(exampleBlob,'binary')` but for whatever reason that resulted in a broken / improperly formatted pdf file – suntzu Dec 02 '20 at 19:51
42

i fetched the data from server as string(which is base64 encoded to string) and then on client side i decoded it to base64 and then to array buffer.

Sample code

function solution1(base64Data) {

    var arrBuffer = base64ToArrayBuffer(base64Data);

    // It is necessary to create a new blob object with mime-type explicitly set
    // otherwise only Chrome works like it should
    var newBlob = new Blob([arrBuffer], { type: "application/pdf" });

    // IE doesn't allow using a blob object directly as link href
    // instead it is necessary to use msSaveOrOpenBlob
    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveOrOpenBlob(newBlob);
        return;
    }

    // For other browsers: 
    // Create a link pointing to the ObjectURL containing the blob.
    var data = window.URL.createObjectURL(newBlob);

    var link = document.createElement('a');
    document.body.appendChild(link); //required in FF, optional for Chrome
    link.href = data;
    link.download = "file.pdf";
    link.click();
    window.URL.revokeObjectURL(data);
    link.remove();
}

function base64ToArrayBuffer(data) {
    var binaryString = window.atob(data);
    var binaryLen = binaryString.length;
    var bytes = new Uint8Array(binaryLen);
    for (var i = 0; i < binaryLen; i++) {
        var ascii = binaryString.charCodeAt(i);
        bytes[i] = ascii;
    }
    return bytes;
};
Eduardo Cuomo
  • 17,828
  • 6
  • 117
  • 94
Sajjad Ali Khan
  • 1,735
  • 2
  • 20
  • 17
  • 1
    Works ok in desktop and android browsers. Tested it on iPhone (safari and chrome) and didnt work. – Redder Dec 12 '17 at 17:50
  • 1
    Thank you! Using createObjectURL helped with the failed network error I was having, but my PDFs could not be opened, but your solution got it working. – Mathachew May 29 '18 at 17:33
  • 1
    As of 2018 using chrome / windows 10 this works as hoped. – macm Aug 14 '18 at 18:41
23

I was facing the same problem in my React project. On the API I was using res.download() of express to attach the PDF file in the response. By doing that, I was receiving a string based file. That was the real reason why the file was opening blank or corrupted.

In my case the solution was to force the responseType to 'blob'. Since I was making the request via axios, I just simply added this attr in the option object:

axios.get('your_api_url_here', { responseType: 'blob' })

After, to make the download happen, you can do something like this in your 'fetchFile' method:

const response = await youtServiceHere.fetchFile(id)
const pdfBlob = new Blob([response.data], { type: "application/pdf" })

const blobUrl = window.URL.createObjectURL(pdfBlob)
const link = document.createElement('a')
      link.href = blobUrl
      link.setAttribute('download', customNameIfYouWantHere)
      link.click();
      link.remove();
URL.revokeObjectURL(blobUrl);
romerompb
  • 860
  • 11
  • 15
3

solved it thanks to rom5jp but adding the sample code for golang and nextjs

in golang using with gingonic context

c.Header("Content-Description", "File-Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition","attachment; filename="+fileName)
c.Header("Content-Type",  "application/pdf; charset=utf-8")

c.File(targetPath)
//c.FileAttachment(targetPath,fileName)
os.RemoveAll(targetPath)

in next js

  const convertToPDF = (res) => {
  const uuid = generateUUID();

  var a = document.createElement('a');
  var url = window.URL.createObjectURL(new Blob([res],{type: "application/pdf"}));
  a.href = url;
  a.download = 'report.pdf';
  a.click();
  window.URL.revokeObjectURL(url);

}

  const convertFile = async() => {
    axios.post('http://localhost:80/fileconverter/upload', {
      "token_id" : cookies.access_token,
      "request_type" : 1,
      "url" : url
    },{
      responseType: 'blob'
    }).then((res)=>{

     convertToPDF(res.data)

    }, (err) => {
      console.log(err)
    })
  }
2

I was able to get this working with fetch using response.blob()

fetch(url)
  .then((response) => response.blob())
  .then((response) => {
    const blob = new Blob([response], {type: 'application/pdf'});
    const link = document.createElement('a');
    link.download = 'some.pdf';
    link.href = URL.createObjectURL(blob);
    link.click();
  });
Turnip
  • 352
  • 4
  • 7
-4

Changing the request from POST to GET fixed it for me