Search code examples
javascripthtmliframexmlhttprequestfileapi

Download a large file or show error


On a website we are working on, we have a download link, that must be served to the user. However, when fetching the url the server can either serve an error message in JSON (the appropriate headers and an appropriate http status code will then be set) or serve the file.

Currently, we are using an iframe to download this file, but this prevents us from viewing the error message. While, this can be done in principle, it cannot be done cross-domain and the reading the error data seems to be different between browsers (as the browser will interpret the json as html and create html tags around it)

I have considered using xmlhttprequest2 to download the file, and serve it to the user, however the downloaded file can be large and thus, it must be streamed to the user.

Therefore, I'm looking for a way to either download a file or read the error message depending on the http status code.

API Setup

I'm able to change the API to my wishes, however the API is designed to be a public API and is designed as a REST API. This means, that the API should stay as simple as possible, and workarounds to make specific client-side code work should not have cause any hazzle for other client-side (thus the API and client-side code are decoupled).

The file that is being downloaded is encrypted on the server, and can only be decrypted by information given in the URL. Therefore, chunked transfer is difficult, as extracting a chunk would require the server to decrypt the whole file.


Solution

  • the appropriate headers and an appropriate http status code will then be set

    If that statement is correct, you could use the same concept as preflighted requests for Cross-site requests. Preflighted requests first send an HTTP request with the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send or not.

    In your case, instead of having an OPTIONS request automatically send, you could send manually an HEAD request. The HEAD method is identical to GET except that the server do not return a message-body in the response. The informations contained in the HTTP headers in response to a HEAD request should be identical to the information sent in response to a GET request. Since you're only fetching the headers and not the body, you would have no problem with large or even any file, nothing is downloaded except the headers.

    Then, you can read these headers or even the status code, and depending on the result of this manually preflighted request, decide if you should either stream the file to the user or fetch the error message if you're encountering an error.

    A basic implementation without knowledge of your project using status code could be the following:

    function canDownloadFile(url, callback)
    {
      var http = new XMLHttpRequest();
      http.open('HEAD', url);
      http.onreadystatechange = function() {
        if (http.readyState === XMLHttpRequest.DONE) {
          callback(http.status);
        }
      };
    
      http.send();
    }
    
    var canDownloadCallback = function (statusCode) {
      if (statusCode === 200) {
        // The HEAD request returned an OK status code, the GET will do the same,
        // let's download the file...
      } else {
        // The HEAD request returned something else, something is wrong,
        // let's fetch the error message and maybe display it...
      }
    }