Ajax - Get size of file before downloading
Asked Answered
S

4

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.

Slit answered 2/7, 2013 at 1:58 Comment(4)
possible duplicate? #1484803 and #1441223Escapement
@Escapement Thanks, for some reason, they didn't show up on stackexchange's list of duplicates... no idea why. Does that only download the header?Slit
you can get XHR-respone data manually: w3.org/TR/XMLHttpRequest/#the-getresponseheader()-methodSelfeffacement
@hungdoan Thanks!! That answers my question perfectly =D Could you convert it into an answer?Slit
S
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.");
});
Selfeffacement answered 4/7, 2013 at 1:47 Comment(5)
And if I used a variable instead of alert? Why they return 'undefined'? How can i store xhr.getResponseHeader("Content-Length") in a variable?Semblance
@FabioCalvosa xhr.open("HEAD", url, true); // false makes the request asynchronous So when you call that function like this: var myVar; get_filesize('sample.com'); alert(myVar); // your variable is 'undefined' But if you change your code like this: var myVar; get_filesize('sample.com', function(){ alert(myVar); // your variable will not be 'undefined' be cause you call a call back method });Selfeffacement
I called it in Chrome console, but it got NaN.Overvalue
readyState when headers are available could be sooner with HEADERS_RECEIVEDAdscription
thank you! It's helps me Content-LengthUyekawa
C
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);
Convexoconvex answered 14/12, 2017 at 21:5 Comment(1)
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?Unread
P
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();
  }
);
Paryavi answered 19/8, 2019 at 14:57 Comment(0)
R
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.

Rance answered 19/1, 2019 at 10:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.