I am trying to download a file using JS, without linking to it in HTML and without creating temporary DOM elements like some of the old tricks suggest.
I found this snippet that almost works, it lets me download the file but it assigns it a generated name. Adding the Content-Disposition option to header works for the fetch part but it is disregarded later with blob.
const options = {
headers: {
"Content-Disposition": 'attachment; filename="report.pdf"'
}
}
function downloadPdf() {
fetch("./report.pdf", options)
.then(res => res.blob())
.then(blob => {
let file = window.URL.createObjectURL(blob);
window.location.assign(file);
});
}
downloadButton.addEventListener("click", downloadPdf, false);
How can I pass the name of the file from Content-Disposition header to the created blob?
Related
The API call to the server is returning a zip file with Content-Disposition in format attachment, <filename>
I am using FileSaver's saveAs to save the file.
this.ajax.raw(requestUrl, {
dataType: 'binary',
xhr: () => {
const myXhr = $.ajaxSettings.xhr()
myXhr.responseType = 'blob'
return myXhr
}
}).then((response) => {
this.downloadSuccess(response, minTimeString, maxTimeString, downloadCompletedMessage)
}).catch((e) => {
this.downloadError(e)
})
downloadSuccess (response, minTime, maxTime, downloadCompletedMessage) {
const filename = (response.jqXHR.getResponseHeader('Content-Disposition').split('"')[1])
saveAs(response.payload, filename, 'application/zip')
This works fine for small files but fails if the file is more than 2Gb (The file is downloaded successfully but the saved file is of 1Kb only).
During my research, I saw that browser can download the file without FileSaver if the response has Content-Disposition which is true in my case. But I am not able to figure out how.
Do I need to use request differently?
From docs:
Content-Disposition attachment header is the best preferred way to
download files from the browser. It has better cross browser
compatibility, won't have any memory limit and it doesn't require any
JavaScript.
You don't need ajax request to download the file. Only ensure that server add Content-Disposition header and provide a link to download.
If you can also use the anchor download attribute from HTML5.
Causes the browser to treat the linked URL as a download.
const link = document.createElement('a');
link.href = '/xyz/abc.pdf';
link.download = "file.pdf";
link.dispatchEvent(new MouseEvent('click'));
I have a react client that takes in user input as a file and sends it to my remote Flask server for storage. I send the file in the form of a Werkzeug FileStorage object and in the remote server I store it with file.save(path). In the react client I'm trying to build a way to download the file from the server, however I'm running into problems. Currently my program is working for downloading .txt files. I'm able to do this though a fetch javascript request:
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
var a = response.body.getReader();
a.read().then(({ done, value }) => {
saveAsFile(new TextDecoder("utf-8").decode(value), 'filename.txt');
}
);
});
function saveAsFile(text, filename) {
const type = 'application/text'; // modify or get it from response
const blob = new Blob([text], {type});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
Thanks to some help I got in this post: Download file in react client from flask remote server
I know this code is specifically made to work only with .txt files based on the type being passed in to Blob, but the front end is not the real problem.
The real problem is in my remote flask server, the following code is what is called in the flask server:
with open(filename, 'r') as f:
contents = f.read()
return contents
I tried returning the file itself but the server gives an error:
"ValueError: I/O operation on closed file."
So I decided to return the contents of the file as shown above.
The problem arises when I try to get a file for example "download.jpeg". Reading the file gives the following error:
"UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte"
From what I understand Flask works exclusively with 'utf-8' and I assume this means the file in the server is on 'utf-8' encoded.
Does anyone have a suggestion or guidance on a solution or a workaround maybe a way to change the files encoding when I save it on the server or something else that could help me with what I'm trying to do?
Fetch's Response has blob() to convert the response directly to blob, so you don't have to read the stream, you don't have to find out it's content type or anything. Just try the below solution.
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
response.blob().then((blob) => {
saveBlob(blob, 'filename');
});
});
function saveBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
Try this: make sure to install axios. Also you probably won't have to deal with content type like above said. Obviously changing the method type to POST and bring ur data in.
axios(FETCH_URL, {
method: 'GET',
responseType: 'blob', // important
}).then((response) => { //Creates an <a> tag hyperlink that links the excel sheet Blob object to a url for downloading.
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${Date.now()}.xlsx`); //set the attribute of the <a> link tag to be downloadable when clicked and name the sheet based on the date and time right now.
document.body.appendChild(link);
link.click(); //programmatically click the link so the user doesn't have to
document.body.removeChild(link);
URL.revokeObjectURL(url); //important for optimization and preventing memory leak even though link element has already been removed.
});
The code is very simple:
<a download href="http://www.pdf995.com/samples/pdf.pdf">Download</a>
I expect it to save the pdf file but it always open the file on the browser.
It works with other file type, just have problem with PDF file.
See the MDN documentation:
This attribute only works for same-origin URLs.
Presumably, the other file types, where you see it "working", are ones where the default behaviour is to download the file.
If the URL that you're trying to fetch has an Access-Control-Allow-Origin header, you can work around this by using fetch and blobs:
function forceDownload(blob, filename) {
// Create an invisible anchor element
const anchor = document.createElement('a');
anchor.style.display = 'none';
anchor.href = window.URL.createObjectURL(blob);
anchor.setAttribute('download', filename);
document.body.appendChild(anchor);
// Trigger the download by simulating click
anchor.click();
// Clean up
window.URL.revokeObjectURL(anchor.href);
document.body.removeChild(anchor);
}
function downloadResource(url, filename) {
fetch(url, {
headers: new Headers({
Origin: window.location.origin,
}),
mode: 'cors',
})
.then(response => response.blob())
.then(blob => forceDownload(blob, filename))
.catch(e => console.error(e));
}
downloadResource('https://memegen.link/xy/fetch/all_the_things.jpg?watermark=none');
This has a few limitations:
the file size limit of blobs is about 500MB
some websites will not allow for cross-origin requests, leading to errors like this one below
Failed to load https://example.com/example.jpg: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://example.com' is therefore not allowed access.
Ref 1: Leeroy in https://stackoverflow.com/a/49500465/1268612
Ref 2: https://davidwalsh.name/javascript-download
Full explanation: https://blog.logrocket.com/programmatic-file-downloads-in-the-browser-9a5186298d5c/
Using a pre-signed URL I am trying to PUT an object (an image) in my AWS S3 bucket using JavaScript's fetch.
Below is the snippet of code I'm using, triggered by a change event on a file upload input.
// The event here is from a change
// trigger on the file input
const target = event.target
const file = target.files[0]
const reader = new FileReader()
reader.onload = (event) => {
fetch(PRESIGNED_S3_URL, {
method: 'put',
body: event.target.result,
headers: new Headers({
'Content-Type': file.type
}),
}).then((response) => {
response.json().then((data) => {
// Success
})
})
}
// Read the selected file
reader.readAsText(file)
This does indeed upload the file, setting its name and filetype correctly, but when I try to view the file it is corrupt.
I am stuck on what I am doing incorrectly to read and upload the file contents. Any help is appreciated!
To upload an image, you need to set content handling to CONVERT_TO_BINARY.
I have a backend that I set up to return a file through setting the header
Content-Disposition: attachment;filename=somefile.csv
It works directly in the browser and downloads the file immediately upon invoking the URL that points to that resource.
My goal is to have a button in an Angular 2 template. When the user clicks that button, I'd need to collect some data from the client-side (some IDs) and send it to the server to invoke that file download URL.
I'd like the user to stay on the same page and not have any new tabs open up but simply have the file start downloading (just like when the URL is invoked directly).
It will need to be done through a POST request because I can have quite a bit of data to send to identify what resource needs to be downloaded.
What does the call on the Angular 2 side look like for this? I tried a couple of things but I am obviously on the wrong path.
Any help would be appreciated!
I had a similar issue when i was trying to download a PDF file which my Node server was sending. I was making a GET request on my server with some id details.
This is what worked for me.
Function Calling my service
printBill(receiptID) {
this.BillingService.printBill(receiptID)
.subscribe(res => {
saveAs(res,"InvoiceNo"+receiptID+".pdf");
let fileURL = URL.createObjectURL(res);
window.open(fileURL);
})
}
Service
printBill(billID) {
return this.http.get('/billing/receipt/'+billID,
{ responseType: ResponseContentType.Blob })
.map((res) => {
return new Blob([res.blob()], { type: 'application/pdf' })
})
}
And dont forget to import ResponseContentType
Hope this helps you
i have implemented it like this.
i have a service requesting file download. The response return a url, which is on amazon s3. This is a zip file containing what i want to download.
the below works on all browsers.
in your controller
requestDownload() {
this.downloadservice.downloadImages(obj)
.subscribe(
response => this.download(response )
);
}
// res is an object
download(res){
var link = document.createElement("a");
link.download = "a";
link.href = res.link;
document.body.appendChild(link);
link.click();
}
downloadservice file
downloadImages(body): Observable<any>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post("/Camera51Server/getZippedImages", body, options)
.map((res:Response) => res.json())
.catch(this.handleError);
}
if you like i can give you a link to the repository.