Reputation: 812
So I am trying to download a zip file with an ajax call to my api. The api responds with a base 64 encoded byte array. Now for most of the downloads this is working just fine, but when the zip file gets too large it starts to fail in Chrome. Works fine in all other browsers. From what I have found on stack overflow this is a known issue in chrome, and people suggested using blobs. Thing is I am using blobs and still have the issue. Here is my code for handling the download. I use this to download pdf and zip files by passing in different values for contentType. Has anyone run into this issue before? Are there any work arounds or scripts I can add to the page that will remedy this problem?
// data is base 64 encoded byte array
function download(data, filename, contentType) {
var byteCharacters = atob(data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], { type: contentType });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
// IE / edge workaround
window.navigator.msSaveOrOpenBlob(blob, filename);
} else if (navigator.userAgent.indexOf("Firefox") > 0) {
var a = window.document.createElement('a');
a.download = filename;
a.style.cssText = "display: none";
a.target = "_blank";
// Append anchor to body.
document.body.appendChild(a);
a.href = "data:" + contentType + ";base64," + data;
a.click();
// Remove anchor from body
document.body.removeChild(a);
} else //Chrome, safari, etc
{
var a = document.createElement("a");
a.style = "display: none";
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}
}
Upvotes: 10
Views: 3700
Reputation: 12040
In addition to these solution methods above you can also try to set maxRequestLength
property in the web.config.
<system.web>
...
<httpRuntime targetFramework="4.5.1" maxRequestLength="51200" executionTimeout="30" />
<!-- for 50 MB set maxRequestLength="51200"-->
</system.web>
Hope this helps...
Upvotes: 0
Reputation: 8689
Problem with large blob download is mostly about memory leak. Some additional code can solve this problem, I think.
Your blob is storing in memory, and if it very large is bad, because browser process has limit of available memory. This can solve indexedDB indexeddb example, You should add a blob to indexedDB and then immideately remove from it. When you writing blob to indexeddb, browser creates cached file at file system, and from this moment your blob will be stored at file system, like a File. And this action will free a lot of memory.
When you create ObjectUrl, you will have an additional link of blob in memory, and garbage collector will remove it, only on document unload event. So you need to revokeObjectURL.
var previousBlob = null; downloadFile(source, filename) { if(previousBlob){ URL.revokeObjectURL(previousBlob); previousBlob = null; } //.. do stuff }
P.S.
IndexedDB helped me solve one bug of Chrome. In the Chrome browser you can't create 1000 blobs (totaly in all opened tabs), so I solve this problem by writing blobs to indexeddb.Because blobs became a files and limit of blobs don't sprad on it. You can see the result by you own yeys at firefox "about:memory" tool, is the best tool of prifiling memory (better then other browsers)
Upvotes: 0
Reputation: 3247
Seems to be a bug. In the meantime of it being resolved here's what you could do.
Split the file into smaller parts, and make many ajax requests, then when everything has been downloaded reconstruct the original file client side by concatenating the parts. From there proceed as normal.
Upvotes: 0
Reputation: 136
The api responds with a base 64 encoded byte array
Why? I'm suggesting you to add a method which will respond with pure binary zip files.
When set xhr.responseType = "blob";
and just save it like you do it for Chrome.
If you want to keep your api as is, try to get rid of Array and fill in Uint8Array straightway.
UPD. I tested this on my Chrome 69 under Ubuntu 18.04 with 16gb RAM and I found that Chrome would not let me download much more than 1000 megabytes this way.
How large are file you are trying to download?
Check it by yourself (don't forget to make sure that nothing will block downloading):
function getBlob(size) {
let u8a = new Uint8Array(size);
for (var i = 0; i < size; i++) {
u8a[i] = 33 + Math.floor(Math.random() * 5)
}
return new Blob([u8a]);
}
function downloadFile(source, filename) {
if (source instanceof (File, Blob)) {
filename = source.name || filename;
source = URL.createObjectURL(source);
}
let link = document.createElement("a");
link.style.display = "none";
link.href = source;
link.download = filename || "blob.bin";
document.body.appendChild(link);
link.click();
setTimeout(function () {
document.body.removeChild(link);
URL.revokeObjectURL(source);
}, 3000);
}
Size (MB): <input id="in" type=number min=1 max=3000>
<button onclick="downloadFile(getBlob(1024*1024*(+document.getElementById('in').value)))">download</button>
Upvotes: 5