Turning image response from server into downloadable file - javascript

I'm making an ajax POST call to my server, generating an ugly remote download URL, passing that to nginx with proxy_pass and then serving the file to the client. See here for process. The image seems to make it to the client, I just can't get it to download.
As seen in screenshots, the chrome response preview shows the jpeg, the headers look good (content-disposition attachment).
How can I turn this response into a downloadable file for the user?
I've tried https://stackoverflow.com/a/23797348/5697126, however, the file that gets downloaded is corrupted and 150% size of the real image. Here's my attempt, it does download a file, but the file is corrupted with the message - Error interpreting JPEG image file (Not a JPEG file: starts with 0xef 0xbf)
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(_responseHeaders['content-disposition']);
let fileName = '';
if (matches != null && matches[1])
{
fileName = matches[1].replace(/['"]/g, '');
}
let fileType = _responseHeaders['content-type'];
let blob = new Blob([_response], {type: fileType});
let URL = window.URL || window.webkitURL;
let downloadUrl = URL.createObjectURL(blob);
if (fileName)
{
// use HTML5 a[download] attribute to specify filename
let a = document.createElement('a');
// safari doesn't support this yet
if (typeof a.download === 'undefined')
{
window.location = downloadUrl;
}
else
{
a.href = downloadUrl;
a.download = fileName;
document.body.appendChild(a);
a.click();
}
}
else
{
window.location = downloadUrl;
}
setTimeout(function ()
{
URL.revokeObjectURL(downloadUrl);
}, 100); // cleanup

Solved!
I had to set the ajax (axios) response type to 'blob' as the default is json. See - https://github.com/axios/axios/issues/448
Once the response was blob, I just piped the _response to URL.createObjectURL and then rest of the code I posted worked like a charm

Related

Create XLSX file from file contents from server and save it

I got Node JS server which gets XLSX file contents from metabase:
app.get('/channels', async (req, res) => {
// make request to metabase and take response as XLSX
const queryRequestURL = `${api}/public/card/${cardId}/query/xlsx?parameters=${params}`;
const result = got(queryRequestURL);
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader("Content-Disposition", "attachment; filename=file.xlsx");
return res.send(res);
});
It returns file contents like
So when i make request to server and receive response - it comes as file contents above.
I need to download this data as ordinary excel file on browser side.
What i've tried:
// make request with typical fetch and get result to res variable.
const filename = 'file.xlsx';
const file = new File(res, filename ,{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
// create link and click it virtually to download created file
var a = document.createElement('a');
a.href = window.URL.createObjectURL(file);
a.download = filename;
a.click();
But I'm getting the error:
Uncaught (in promise) TypeError: Failed to construct 'Blob': The provided value cannot be converted to a sequence.
I think that I'm doing something wrong and there is more simple way to download file.
Without seeing how you're fetching, it's hard to know. But you should be able to use response.blob() to download the result.
fetch("${api}/channels}", {
method: "GET",
})
.then((response) => response.blob())
.then((blob) => {
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
a.remove();
});
As Joey Ciechanowicz mentioned, we should return response.buffer() from backend and work with its data as blob at frontend.
I mean
NodeJS side (using Got):
const result = got(queryRequestURL, {
headers: headers
});
return await result.buffer()
Frontend side (pure JavaScript):
// fetch data
const result = await fetch(api + path);
return result.blob();
// download file
const filename = 'export.xlsx';
var url = window.URL.createObjectURL(result);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
a.remove();

Download base64 encoded file

In React, I uploaded a file using:
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = function() {
let base64data = reader.result;
uploadFile(base64data);
return;
}
This gives me a Base64 encoded text data:application/octet-stream;base64,JVBERi0xLj...
This is fine as when I decode 'JVBERi0xLj...' I get the correct text in case of a text file.
When a download request is made to the server I get the same data back but I'm having a difficulty downloading the file. I receive the same base64 encoded string in the response from the server but unable to open the downloaded file.
I have done the following:
const blob = new Blob([fetchData], { type: 'application/pdf' })
let url = window.URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = blob;
a.download = 'doc.pdf';
a.click();
Any ideas?
Note: The upload file is converted to base64 to avoid any http communication issues.
Solution following your suggestions:
let fetchDataModified = `data:application/pdf;base64,${fetchData }`;
let a = document.createElement("a");
a.href = fetchData;
a.download = 'doc.pdf';
a.click();
When converting to Base64 during upload the data type was set to 'application/octet-stream'. However, when downloading I changed that to 'application/pdf' following Vaibhav's suggestion and used createElement instead of createObjectURL and it worked. Thank you
“data:application/pdf” + the base64 string that you saved into our database

Saving file from blob:http using javascript with no redirection

I have already passed through some of the suggested solutions here in SO, but with no success. This is the problem. When the blob is generated using the data retrieved from the API endpoint, I want to force browser to download the blob. I have tried three solutions so far, but no of them works. Code follows. Notice that I put some comments in the code to explain further.
const FILE_NAME_REGEX = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
export function Download(url) {
return APICall.get(url).then(response => {
const disposition = response.request.getResponseHeader('Content-Disposition');
//^This line gives the 'Refused to get unsafe header "Content-Disposition"' error, so next few lines won't execute and part with generating anchor is not used, but the part of code under the 'else' branch.
if (disposition && disposition.indexOf('attachment') !== -1) {
const matches = FILE_NAME_REGEX.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
const type = response.request.getResponseHeader('Content-Type');
const blob = new Blob([response.data], { type });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(blob, filename);
} else {
const URL = window.URL || window.webkitURL;
const downloadUrl = URL.createObjectURL(blob);
if (filename) {
const a = document.createElement('a');
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
} else {
// 1. soultion
window.location = downloadUrl;
}
setTimeout(() => {
URL.revokeObjectURL(downloadUrl);
}, 100);
}
});
}
// 2. solution
const ifrmDownloader = document.createElement('iframe');
ifrmDownloader.setAttribute('src', downloadUrl);
ifrmDownloader.style.width = '0px';
ifrmDownloader.style.height = '0px';
document.body.appendChild(ifrmDownloader);
// 3. solution
window.open(downloadUrl,_blank);
Solution does not work because it opens another tab and returns empty little square instead of the file. This is probably due to the lost temp blob when new tab got opened.
Solution simply does not work, and I do not know why. Iframe gets added to the dom and request is recorded in the Network tab under developer console, but no download is trigerred. Probably the same reason as in the solution 1. In addition this message is logged:
Resource interpreted as Document but transferred with MIME type image/png: "blob:https://example.com/e53bf47b-7f39-4758-b3dd-3cc2df5889ad".
Solution does not work as well. First of, browser blocked pop up, and after te pop up is allowed, no file download is initiated.
I feel that something is missing here, but what?
Your code is set up to download the File only when the filename variable is set.
This variable is set only when you are able to read the Content-Disposition header (i.e only when same-origin).
So the simple fix would be to set a dummy value for filename when you failed to get it from the Headers, this way, filename is always set, and you always try to download it insted of just opening it in a new page.
The smarter fix would be to try to parse the filename from the url variable, but without knowing the format of possible urls, it's hard to make one for you.
const FILE_NAME_REGEX = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
export function Download(url) {
return APICall.get(url).then(response => {
const disposition = response.request.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
const matches = FILE_NAME_REGEX.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
if (!filename) {
// getFileNameFromURL(url);
filename = 'dummy.png';
}
const type = response.request.getResponseHeader('Content-Type');
const blob = new Blob([response.data], {
type
});
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(blob, filename);
} else {
const URL = window.URL || window.webkitURL;
const downloadUrl = URL.createObjectURL(blob);
// if (filename) { // always truthy
const a = document.createElement('a');
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// }
/* else { // useless
window.location = downloadUrl;
}
*/
setTimeout(() => {
URL.revokeObjectURL(downloadUrl);
}, 100);
}
});
}
Here is what I have found in regards to the problem I posted. There were two problems actually:
Content-Disposition header was getting refused and therefore no filename nor the dispostion information were available. This was bridged according to #Kaiido's reply, by extracting the filename from the url and adding ',png'. E.G.
const chunks = extendedURl.split('/');
const pngExtension = '.png';
const baseName = chunks[chunks.length - 1].split('?')[0];
filename = `${baseName}${pngExtension}`;
More serious problem that I did not realize until today was that when axios' Get method was called, responseType was not set to 'arraybuffer'.
Therefore, I was able to download files that were 'broken'. This question helped me figure out where the actual problem was: File is corrupted while downloading using angular $http
Once the calling Get method was provided with the 'responseType: "arraybuffer"', files started to appear normal, not 'broken'.
To sum it up, when calling .net Core Web API endpoint that returns FileStreamResult, in order to be able to create Blob in your FE JavaScript, you should explicitly set responseType to 'arraybuffer' as follows:
axios.get(extendedURl, { responseType: 'arraybuffer' }).then(response =>...

PDF is blank when downloading using javascript

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:
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();
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;
};
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);
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)
})
}
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();
});
Changing the request from POST to GET fixed it for me

Saving a Uint8Array to a binary file

I am working on a web app that opens binary files and allows them to be edited.
This process is basically ondrop -> dataTransfer.files[0] -> FileReader -> Uint8Array
Essentially, I want to be able to save the modified file back as a binary file. Ideally as a file download with a specified file name.
There doesn't seem to be any standard method of doing this, and that sucks, because everything up to that point is well supported.
I am currently converting the array to a string using String.fromCharCode(), base64 encoding that, and using a data uri in a hyperlink like data:application/octet-stream;base64,.., along with the download attribute to specify filename.
It seems to work, but it's quite hacky and I think converting the raw bytes to a string might introduce encoding issues depending on the byte values. I don't want the data to become corrupt or break the string.
Barring that, is there a better/proper method for getting an array of bytes as a binary file to the user?
These are utilities that I use to download files cross-browser. The nifty thing about this is that you can actually set the download property of a link to the name you want your filename to be.
FYI the mimeType for binary is application/octet-stream
var downloadBlob, downloadURL;
downloadBlob = function(data, fileName, mimeType) {
var blob, url;
blob = new Blob([data], {
type: mimeType
});
url = window.URL.createObjectURL(blob);
downloadURL(url, fileName);
setTimeout(function() {
return window.URL.revokeObjectURL(url);
}, 1000);
};
downloadURL = function(data, fileName) {
var a;
a = document.createElement('a');
a.href = data;
a.download = fileName;
document.body.appendChild(a);
a.style = 'display: none';
a.click();
a.remove();
};
Usage:
downloadBlob(myBinaryBlob, 'some-file.bin', 'application/octet-stream');
(shorter) ES6 version of the top answer:
const downloadURL = (data, fileName) => {
const a = document.createElement('a')
a.href = data
a.download = fileName
document.body.appendChild(a)
a.style.display = 'none'
a.click()
a.remove()
}
const downloadBlob = (data, fileName, mimeType) => {
const blob = new Blob([data], {
type: mimeType
})
const url = window.URL.createObjectURL(blob)
downloadURL(url, fileName)
setTimeout(() => window.URL.revokeObjectURL(url), 1000)
}

Categories

Resources