I'm trying to implement the download of a file through angular.js
The file comes from the server in binary format, the content type is application/octet-stream
The download is a GET using $resource. Looking at the parameter passed to the callback (called content below), it's an object containing the byte array and also a list of properties for $resource.
Tried several ways to serve the file, but without success.
First of them:
...
var a = document.createElement('a');
a.href = "data:attachment/zip," + content;
a.download = zipName;
a.click();
In this case, the content of the zip file is [object Object]
I tried extracting the array from the object and joining everything into a string variable. The zip file in this case is way larger than the normal size. I had to set isArray: true in the service that calls $resource, otherwise there was no way to extract the byte content from the response object.
Here is how I did it:
var str = '';
for (var i = 0; i < content.length; i++) {
str += content[i][0];
}
...
var a = document.createElement('a');
a.href = "data:attachment/zip," + str;
a.download = zipName;
a.click();
Worth mentioning that calling encodeURI on str increments drastically the size of the downloaded zip, but the archive remains invalid.
I also tried creating a Blob from the str and setting the content type to application/octet-stream, without any luck.
var blob = new Blob([str], {'type':"application/octet-stream"});
a.href = window.URL.createObjectURL(blob);
...
Don't know what I'm missing here, but it looks rather a problem of getting the right format for the byte array content and setting the correct href before simulating the click for downloading.
Help is appreciated.
Thanks
I just found your post and fixed an answer using what you enlist.
First you have to ensure that your angular $http request includes, like the following get example (include responseType: 'arraybuffer')
$http.get('/downloadZip', {
params: {
file: encodeURIComponent(filepath)
},
responseType: 'arraybuffer'
//your code
Second on your success or promise handler you should change your window.URL.createObjectURL(blob) to URL.createObjectURL(blob). Implementing something similar to the following:
var a = document.createElement('a');
var blob = new Blob([data], {'type':"application/octet-stream"});
a.href = URL.createObjectURL(blob);
a.download = "filename.zip";
a.click();
With these you are creating a new anchor element and simulating to opening it. With a correct Blob creation since the request had been modified correctly.
Angular is no needed.
var zip_file_path = "" //put inside "" your server path with file.zip
var zip_file_name = "" //put inside "" file name or something
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = zip_file_path;
a.download = zip_file_name;
a.click();
document.body.removeChild(a);
In case anyone is still on AngularJS (like me) and wants to do this, I took David's answer and made it work with the angular $resource instead of using the lower level $http directly. If you use $resource, this should help you:
var myReportingResource = $resource(baseURL + '/mypath/:command', {},{
getExportZip: {
method: 'GET',
params: {
command: 'exportzip'
},
responseType: 'arraybuffer',
// don't try to convert the zip to JSON
// instead take the data that comes back and put it in an object under a content key
transformResponse: function(data){
return {content: data};
}
}
});
// call the resource like this
myReportingResource.getExportZip(yourParams).$promise.then(function(zipData){
// create a anchor element, stick the zip data in it, and click it to download
var anchor = angular.element('<a/>');
anchor.attr({
href: URL.createObjectURL(new Blob([zipData.content], {'type':'application/octet-stream'})),
download: 'myfilename.zip'
})[0].click();
});
You need the transformResponse bit because otherwise AngularJS will convert your response to JSON- which is wrong with binary data. This is why later you use zipData.content to pass the data into the Blob. You can get rid of the content part, it's there for simplicity with my error handling code.
This works in Chrome and Safari as of May 2019. Didn't test anywhere else.
Related
An Axios request to server response the content of a PDF as a binary string.
export const fetchPDFfile = async (id: string): Promise<string> => {
const { data } = await http.get<string>(`${baseUrl}/${id}.pdf`);
return data;
};
The response in Chrome devtools and also console logging the data is like:
%PDF-1.4
%âãÏÓ
2 0 obj <</ColorSpa ......
..........
startxref
10991
%%EOF
is defining string as the expected type of Axios response body, correct? or it should be (cast to) Blob?
Now I want to download this as a PDF file in the client-side. There are plenty of questions regarding this but none worked for me and also none had a clear answer.
So what I did so far was (in a React component):
const data = await fetchPDFfile(props.id);
const blob = new Blob([data], { type: 'application/pdf' });
const href = window.URL.createObjectURL(blob);
const theLink = document.createElement('a');
theLink.href = href;
theLink.download = props.id + '.pdf';
document.body.appendChild(theLink);
theLink.click();
document.body.removeChild(theLink);
This downloads a PDF file with 3 blank pages. The number of pages is correct the original doc should bee 3 pages. But I see the white paper.
const href = window.URL.createObjectURL(data); // istead of blob throw Error.
How should I convert and download this PDF file? In general, is the process above needed, or should I directly download it from the server? (something like what cordova-plugin-file-transfer does)
Scenario
You want the file to be downloaded when the user clicks the link.
Solution 1-
Directly put the link in <a> tag.
Cons- Error message can not be shown on the screen if something went wrong.
So it leads to the next solution.
Solution 2-
Hit the URL as an API and download the file if you get the success message.
For this, I use File-server.js
**Don't forget to set the {responseType: 'blob'}, while making the request
http.get<string>(`${baseUrl}/${id}.pdf`, {responseType: 'blob'})
as we don't want the response with Content-Type: application/json
sample code:
import FileSaver from 'file-saver';
downloadPdf() {
var blob = new Blob([data], {type: "application/pdf"});
FileSaver.saveAs(blob, "filename");
}
Firstly use Blob as generic argument for Promise.
I will use fetch API as it can be tested quite easily.
fetch('https://www.jianjunchen.com/papers/CORS-USESEC18.slides.pdf').then(x => x.blob()).then(b => console.log(b.type))
This will log "application/pdf" it the file is trully pdf.
If you got a blob that is not PDF and you will re-wrap it to Blob with pdf type you might break the data. If you got trully a string and you convert it to Blob with pdf type the file will be broken as the PDF would be invalid.
If you want to know if b is trully a blob just console.log(b instanceof Blob) and it should say true. If you have recieved trully a blob you do not have to create new one as you did in new Blob([data]).
This example works just fine:
fetch('https://www.jianjunchen.com/papers/CORS-USESEC18.slides.pdf').then(x => x.blob()).then(b => {
const url = window.URL.createObjectURL(b);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "a.pdf";
a.click();
window.URL.revokeObjectURL(url);
})
Sorry for broken code style but I was unable to paste it properly.
I have an API-Server who responds to requests like this:
http://localhost:8080/slim3/public/api/v1/files/Test1.jpg
http://localhost:8080/slim3/public/api/v1/files/Test2.txt
...
If I put such URL into my browser I can get the download prompt. Now I'm struggeling to process the download of a file via jQuery / Ajax.
Every thread I found here on Stackoverflow tells me to send back the actual download url and open it via window.location. I don't understand how this is possible
when my server already has the file downloaded for me and I just need to "grab" it somehow on the client-side?
It is clear to me that I can't force the download dialog via jQuery / Javascript. I read this in multiple threads here. But the same threads don't tell me
how I can get the direct download url. Or do I mix things up here unfortunately?
Here is what I have:
Client (jQuery)
$(document).ready(function(){
$(document).on('click', '#file', function(e){
e.preventDefault();
var filename = $(this).data('url');
$.ajax({
type : "GET",
cache: false,
url : "http://localhost:8080/slim3/public/api/v1/files/" + filename,
success : function(data) {
console.log(data) // the console writes nothing
//window.location = "data:application/octet-stream," + encodeURIComponent(data); // not working
//var downloadUrl = data.url; // not working
//window.location = downloadUrl; // // not working
},
error : function(data) {}
});
});
});
Server (PHP)
public function show($request, $response, $args)
{
$file = 'C:\xampp\htdocs\slim3\storage\Test1.jpg';
$res = $response->withHeader('Content-Description', 'File Transfer')
->withHeader('Content-Type', 'application/octet-stream')
->withHeader('Content-Disposition', 'attachment;filename="'.basename($file).'"')
->withHeader('Expires', '0')
->withHeader('Cache-Control', 'must-revalidate')
->withHeader('Pragma', 'public')
->withHeader('Content-Length', filesize($file));
readfile($file);
return $res;
}
Solution:
Rob pointed me in the right direction. I actually don't need to do an GET Ajax request. So the final jQuery function looks exacty like this and works:
$(document).on('click', '#file', function(e){
e.preventDefault();
var filename = $(this).data('url');
window.location = "http://localhost:80/slimmi/public/api/v1/files/" + filename;
});
In Client,filename variable will be wrong. It should be Test1.jpgor Test2.txt. I think that $(this).data('url'); returns the current url instead of Test1.jpgor Test2.txtnames. Do you try to substract the file name by using:
var url = $(this).data('url');
var filename = url.substring(url.lastIndexOf("/") + 1, url.length);
Your server just sends back the actual file requested by name in the URL right?
It looks to me like you just need to replace all of the ajax code with
document.location = "http://localhost:8080/slim3/public/api/v1/files/" + filename;
The headers that you set in the PHP will determine whether the browser shows the save dialog or attempts to display the file - those look right.
What you could do if the files are generated on demand is have PHP encode your file in Base64 - like this, setting the appropriate type - and return that to the client. Convert the Base64 to a Blob - you can put Base64 in an anchor's href but IE has a prohibitively small URI size - then create a URL object from that Blob. Among other things this ensures that the data is URL safe. Finally, create an "invisible" anchor tab and click it.
$.ajax({
type: "GET",
url: target,
success: function (response) {
// create a download anchor tag
var downloadLink = document.createElement('a');
downloadLink.target = '_blank';
downloadLink.download = 'your-file-name-here';
// convert Base64 to Blob - don't forget to set content type!
var blob = b64toBlob(response, [file type here]);
// create an object URL from the Blob
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
// set object URL as the anchor's href
downloadLink.href = downloadUrl;
// append the anchor to document body
document.body.appendChild(downloadLink);
// fire a click event on the anchor
downloadLink.click();
// cleanup: remove element and revoke object URL
document.body.removeChild(downloadLink);
URL.revokeObjectURL(downloadUrl);
}
});
Convert the Base64 to Blob like this - source.
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
This is what I use to download PDF's generated on demand by our Django server and she seems to run pretty darn well.
Addendum
The reason why our website does it this way instead of just returning the file name for a subsequent call is because it's a bit easier on the server I/O. The solution that was chosen means that the requested file has to exist somewhere on the server - most likely on disk. (One might be able to keep the generated file in memory using PHP's tmpfile() but my knowledge of PHP is limited so I do not know how you would keep that file around between HTTP calls).
My project makes big honking PDF's - possibly hundreds of pages. I really, really don't want to have to make an actual file object out of this data, save it to disk, and then almost immediately read it back off the disk (I am aware that that isn't exactly how the server is doing it, but anyway you slice it it's doing more work than necessary). The server has the PDF made, it's in memory, why not just ... give it back to the client?
Returning files like this means that one doesn't need to do any extra clean up work - once the Base64 has left the building, that's it. There's no file on disk so there's nothing that has to be dealt with later (good or bad depending on your needs).
I have a PDF on my .NET Core server, which I need to somehow send across the wire as a BLOB, so that my JS AJAX request can convert it back to a PDF so it can be downloaded.
The reason for the indirection is because the file comes from my API, which is only accessed through AJAX. Due to the need for a Bearer token, I can't just use a form behind the scenes, as there's no cookie for the site created. (Weird, I know, but that's how it is presently, and I'm not looking to change that part)
So, on my C# side, I've tried several variations, shown below. ApiResponse is just a container I use that holds a bool and a string (named message) so I can tell if the request was good or not.
These are what I've been trying
return new ApiResponse(true, File.ReadAllText(path));
return new ApiResponse(true, Convert.ToBase64String(File.ReadAllBytes(path)));
return new ApiResponse(true, JsonConvert.SerializeObject(File.ReadAllBytes(path)));
And on the JS side, in the same order to parse it back out, I have:
// Get the response in object form, since it's sent as an ApiResponse
const response = JSON.parse(xmlhttp.response);
const text = response.message;
const text = atob(response.message)
const text = JSON.parse(response.message)
I've also tried things like
const text = atob(JSON.parse(response.message))
Then, with the text I'm doing this:
const blob = new Blob([text], {type: "application/pdf"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
And this does correctly generate a file that's downloaded. However, that file is not valid: it's corrupted.
I'm pretty much stuck at this point, and I haven't been able to find something that goes from start to finish using this method to download files with Javascript. It's either the back side, or the front side, but never tied together.
So, how can I successfully send a PDF BLOB across the wire, and recreate it on the front end so it can be downloaded?
The easy answer to how to do the convert is don't.
Every modern browser supports base64 encoding natively, so there's no need to convert the data back to a BLOB before putting it into download.
Thus, the end code is:
const a = document.createElement("a");
a.href = "data:application/pdf;base64," + response.message;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
I'm calling an api and returning a byte array, name, document type and mime type of a file and trying to add Javascript to download this file.
I've all the code wired in and it downloads a file but the file is empty. I'm using the following code:
result.success(function (data, status, headers, config) {
var arr = data.Document;
var byteArray = new Uint8Array(arr);
var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob([byteArray], { type: data.MimeType}));
a.download = agreementId+data.DocType;
// Append anchor to body.
document.body.appendChild(a);
a.click();
// Remove anchor from body
document.body.removeChild(a);
});
I've tried the same code without the conversion to Uint8Array but this just opens a document with the binary data in it, close but not quite there. Eg I get a download of a word document that contains:
0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAATwAAAAAAAAAAEAAAUQAAAAEAAAD+////AAAAAE4AAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////spcEAJ2AJBAAA
ect
During implementation of file transfer use case through WebRTC protocol, i am recieving data from queue at reciever end in some variables but unable to use that. Through some digging, i came to know that it can be done using Blob,
code snippet that i used :
var data=reciever.dequeue();
if(data)
{ var blob = new Blob(_base64ToArrayBuffer(data), {type: 'text/plain'});
// need to know how to proceed now?
}
file is need to be saved in local system.Thanks in advance.
You can create a temp anchor element element and append it to document.body, trigger click event. Done.
Here is the demo code:
var url = objectURL = URL.createObjectURL(blob); //your blob object here
var anchor = document.createElement("a");
anchor.href = url;
anchor.download = "YourFileName";
anchor.click(); //This will trigger browser download event.
Here is document about blob to URL.
Here is the blob document.
Hope this works. : )