28

Basically, I want to figure out whether I should download a file using AJAX, depending on how large the filesize is.

I guess this question could also be rephrased as: How do I get only the header of an ajax request?


EDIT: ultima-rat0 in the comments told me of two questions that had already been asked that apparently are the same as this one. They are very similar, but they both want jQuery. I want a non-jQuery solution to this.

Community
  • 1
  • 1
MiJyn
  • 5,327
  • 4
  • 37
  • 64
  • 2
    possible duplicate? http://stackoverflow.com/questions/1484303/get-size-of-file-requested-via-ajax and http://stackoverflow.com/questions/1440723/find-size-of-file-behind-download-link-with-jquery – ultima_rat0 Jul 02 '13 at 02:01
  • 1
    @ultima_rat0 Thanks, for some reason, they didn't show up on stackexchange's list of duplicates... no idea why. Does that _only_ download the header? – MiJyn Jul 02 '13 at 02:07
  • 1
    you can get XHR-respone data manually: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method – Hung Doan Jul 02 '13 at 03:01
  • @hungdoan Thanks!! That answers my question perfectly =D Could you convert it into an answer? – MiJyn Jul 04 '13 at 00:19

4 Answers4

44

You can get XHR response header data manually:

http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method

This function will get the filesize of the requested URL:

function get_filesize(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open("HEAD", url, true); // Notice "HEAD" instead of "GET",
                                 //  to get only the header
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            callback(parseInt(xhr.getResponseHeader("Content-Length")));
        }
    };
    xhr.send();
}

get_filesize("http://example.com/foo.exe", function(size) {
    alert("The size of foo.exe is: " + size + " bytes.");
});
MiJyn
  • 5,327
  • 4
  • 37
  • 64
Hung Doan
  • 1,167
  • 12
  • 19
  • 2
    And if I used a variable instead of alert? Why they return 'undefined'? How can i store `xhr.getResponseHeader("Content-Length")` in a variable? – Fabio Calvosa Feb 26 '14 at 15:27
  • @FabioCalvosa `xhr.open("HEAD", url, true);` // `false` makes the request asynchronous So when you call that function like this: var myVar; get_filesize('https://sample.com'); alert(myVar); // your variable is 'undefined' But if you change your code like this: var myVar; get_filesize('https://sample.com', function(){ alert(myVar); // your variable will not be 'undefined' be cause you call a `call back` method }); – Hung Doan Feb 27 '14 at 03:34
  • 1
    I called it in Chrome console, but it got NaN. – Hzzkygcs Jun 12 '18 at 10:11
  • readyState when headers are available could be sooner with HEADERS_RECEIVED – Ricardo Seromenho Jun 25 '19 at 01:21
  • thank you! It's helps me `Content-Length` – forest smoker Jun 01 '23 at 14:35
3

Sometimes HEAD can act differently than GET so I suggest something like this that aborts the request after getting Content-Length header:

new Promise(resolve => {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/a.bin', true);
    xhr.onreadystatechange = () => {
        resolve(+xhr.getResponseHeader("Content-Length"));
        xhr.abort();
    };
    xhr.send();
}).then(console.log);
Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
  • What if `Content-Length` is less than what is actually received in the response body? Is there a way to get the uncompressed size of the response from the server? – bytrangle Sep 11 '21 at 17:04
2

If HEAD request is not possible:

The solution Ebrahim did only not work in firefox for me because context-length was not available for aborted request in firefox. So I used 'onprogress' event instead of 'onreadystatechange' event:

new Promise(
  (resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onprogress = (event) => {
      if (event.lengthComputable) {
        resolve(event.total);
      } else {
        reject(new Error('No content-length available'));
      }
      xhr.abort();
    };
    xhr.send();
  }
);
1

1) If only headers are needed, 'HEAD' should be always preferred over 'GET' because of a simple but not widely known detail: Even if you use 'GET' and immediately abort on readyState === 2 (as suggested by other answers), you will have already received not only the headers, but the full first chunk of information (headers + part of the body) that can vary in size, but usually transfer size will be at least doubled unnecessarily. Using 'HEAD' instead, you can be sure that only headers will be transferred.

2) Content-Length header must be exposed by 'Access-Control-Expose-Headers' to be accessible client-side. If you are dealing with multiple origin resources and you are not sure if Content-Length has been exposed, to prevent exceptions, you can check that, inside an event handler, like this (or other many different ways):

let contentLength = null;

if (checkHeaders(e.target, ['*','Content-Length'])) {
    // YOU CAN ACCESS HEADER
    contentLength = parseInt(e.target.getResponseHeader("Content-Length"));
} else {
    // YOU CAN NOT ACCESS HEADER
    console.log('Content-Length NOT AVAILABLE');
}

function checkHeaders(request, headers) {
    return (headers.some(function (elem) {
        return (request.getResponseHeader("Access-Control-Expose-Headers").includes(elem));
    }));
}

3) Content-Length header IS NOT forbidden when any type of encoding is applied (as suggested in some comments). But, be careful that Content-Length will be usually the size of the decoded body (even if it should not). This can be prevented in many different ways, but it is a server-side consideration.

Ernesto Stifano
  • 3,027
  • 1
  • 10
  • 20