13

I have a downloading problem in Google Chrome. I am using Ruby 2.2, Rails 4.2, AngularJS 1.2.

We dont have a database here. Everything we are getting through API. The file which we are trying to download is around 7 mb. It gives us "Failed: Network Error". Though it works fine on Firefox.

From the API we are getting binary data in JSON. We are parsing it. And then:

send_data response_fields["attachment"], type: response_fields["mimeType"], disposition: 'attachment', filename: params[:filename]

As we are using AngularJS, we are catching that value in AngularJS Controller and then converting it as:

var str = data;
var uri = "data:" + mimeType + ";base64," + str;

var downloadLink = document.createElement("a");
downloadLink.href = uri;
downloadLink.download = filename;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

This works in Firefox & even Chrome for smaller file size. Not sure why it is giving error for bigger size on Chrome.

Any suggestions?

Thanks.

Avi
  • 391
  • 2
  • 3
  • 20

2 Answers2

19

This is an almost duplicate of these questions 1 and 2, but since they do deal particularly with the canvas element, I'll rewrite a more global solution here.

This problem is due to a size limit chrome has set in the anchor (<a>) download attribute. I'm not quite sure why they did it, but the solution is pretty easy.

Convert your dataURI to a Blob, then create an ObjectURL from this Blob, and pass this ObjectURL as the anchor's download attribute.

// edited from https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
function dataURIToBlob(dataURI) {

  var binStr = atob(dataURI.split(',')[1]),
    len = binStr.length,
    arr = new Uint8Array(len),
    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

  for (var i = 0; i < len; i++) {
    arr[i] = binStr.charCodeAt(i);
  }

  return new Blob([arr], {
    type: mimeString
  });

}

var dataURI_DL = function() {

  var dataURI = this.result;
  var blob = dataURIToBlob(dataURI);
  var url = URL.createObjectURL(blob);
  var blobAnchor = document.getElementById('blob');
  var dataURIAnchor = document.getElementById('dataURI');
  blobAnchor.download = dataURIAnchor.download = 'yourFile.mp4';
  blobAnchor.href = url;
  dataURIAnchor.href = dataURI;
  stat_.textContent = '';

  blobAnchor.onclick = function() {
    requestAnimationFrame(function() {
      URL.revokeObjectURL(url);
    })
  };
};

// That may seem stupid, but for the sake of the example, we'll first convert a blob to a dataURI...
var start = function() {

  stat_.textContent = 'Please wait while loading...';
  var xhr = new XMLHttpRequest();
  xhr.responseType = 'blob';
  xhr.onload = function() {
    status.textContent = 'converting';
    var fr = new FileReader();
    fr.onload = dataURI_DL;
    fr.readAsDataURL(this.response);
  };
  xhr.open('GET', 'https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4?dl=0');
  xhr.send();

  confirm_btn.parentNode.removeChild(confirm_btn);
};

confirm_btn.onclick = start;
<button id="confirm_btn">Start the loading of this 45Mb video</button>
<span id="stat_"></span>
<br>
<a id="blob">blob</a>
<a id="dataURI">dataURI</a>

And a jsfiddle version for FF, since they don't allow the downloadattribute from stack-snippets...

doppelgreener
  • 4,809
  • 10
  • 46
  • 63
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • It's sad that chrome limited URI to 2MB http://stackoverflow.com/a/41755526/704008. Great workaround to use blob for large size. – Pranav Singh Feb 07 '17 at 06:18
  • What is difference between FF & chrome Fiddle ? Seems same – Pranav Singh Feb 07 '17 at 06:44
  • 1
    @PranavSingh, yep they're the same (IIRC). It's just that FF did something weird with `download` attribute and stacksnippets (on mine, it didn't show the download popup, and saved with the blob's name without extension) But it actually seems to be fixed on FF 52 – Kaiido Feb 07 '17 at 06:48
  • Apparently not showing any download popup on current Firefox developer edition (53.0a2 (2017-02-06) (64-bit)) too & nothing is downloaded too. what will be solution? – Pranav Singh Feb 07 '17 at 06:52
  • 1
    @PranavSingh, you mean not even on jsfiddle nor in your own page ? That sounds like a bug in dev-edition. Anyway, don't change the returned blobURI. Only the `download` attribute should be used to set the file name. And for a larger browser-support, you may want to use FileSaver.js library. You just have to feed it with the produced blob and a filename and it will workaround a lot of quirks in different implementations. (also Works on my 54 nightly, I don't have a dev edition) – Kaiido Feb 07 '17 at 06:57
  • Now I am trying on FF51.0.1 which is latest available. Seems firefox has no limit on URI too so old way is working but blob is not downloading. :) Anyway, thanks for your help. – Pranav Singh Feb 07 '17 at 07:07
  • How to do the same using the POST method? I want to send some data in the request body. – Raghu Chahar Jan 30 '20 at 13:09
  • @RaghuChahar to send a Blob to a server, either post it directly (`fetch( url, { method: 'post', body: blob } )`, or `xhr.open( "post", url ); xhr.send( blob );`), or inside a FormData. But there is to my knowledge no limitation to the side of a POST request from browsers, so you probably are facing one from the server. Sending the File as binary will save 37% of the size, but this may still may be not enough. You'll probably have to change your server's limitation. – Kaiido Jan 30 '20 at 13:22
  • 1
    @Kaiido I meant that I have a rest API(POST) which creates and streams zip to the client on the fly. How can I consume it on the client/browser side? I have already done it using a form submit click but for very large files it gives network failed after some time. Also, This works fine when the server is on local but gives network failed error when deployed on google k8s. I am stuck on this for the past 18-20 days. – Raghu Chahar Jan 30 '20 at 13:30
0

This answer is more specific to the question: [Creating iframe with dataURI fails for large files, any workaround?] (Creating iframe with dataURI fails for large files, any workaround?)

However, that question was marked as a duplicate of this question ( it really isn't a duplicate ), the answers that were here previously did not directly address that question. It would have helped me if there was a good answer to that question. I was able to come up with a solution to that question, and I have reproduced it below.

Here is an example function that will take a dataURI, convert it to a blob and open it in an iframe.

function dataURIToiFrame(dataURI) {

        function dataURIToBlob(dataURI) {

            let binStr = window.atob(dataURI.split(',')[1]),
              len = binStr.length,
              arr = new Uint8Array(len),
              mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

            for (let i = 0; i < len; i++) {
              arr[i] = binStr.charCodeAt(i);
            }

            return new Blob([arr], {
              type: mimeString
            });

          }

        let iframe = document.getElementById("iframe1");
        if (!iframe) {
            $("body").append(`
<a id="iframe1_wrapper" href="javascript:$('#iframe1_wrapper').remove();" style="position:absolute;right:0; top:50px; bottom:0; height:calc(100vh - 60px); width:650px; padding:20px;">
    <iframe id="iframe1" style="position: absolute;height:calc(100vh - 60px); width:650px; padding:0px;"></iframe><span style="position: relative; color: white;left: 10px">x</span>
</a>`);
            iframe = document.getElementById("iframe1");
        }

        iframe.src = URL.createObjectURL(dataURIToBlob(dataURI));       
}


Dan Willett
  • 945
  • 2
  • 10
  • 20