Using jQuery AJAX to download a binary file
Asked Answered
D

5

38

I am attempting to use jQuery AJAX to download a binary audio file.

Normally I would just issue a command like this:

 windows.location.href = 'http://marksdomain(dot)com/audioFile.wav' ;

However, recently our server has been waiting too long to respond, and I get a nasty gateway timeout message.

It has been suggested that I use jQuery AJAX instead, which makes sense since then i would have more control over the timeout.

Here is the code i have played with so far:

$.ajax({
    url: 'http://marksdomain(dot)com/audioFile.wav',
    timeout: 999999,
    dataType: 'binary',
    processData: false, // this one does not seem to do anything ?

    success: function (result) {
        console.log(result.length);
    },
    error: function (result, errStatus, errorMessage) {
        console.log(errStatus + ' -- ' + errorMessage);
    }
};

When I omit the "dataType", the binary file is coming through about three times larger than it actually is on the server. However, when i make the dataType equal to "binary", AJAX throws an error:

"No conversion from text to binary"

From some earlier posts, it sounds as if jQuery AJAX cannot handle binary files in this manner.

I did discover Delivery.js which actually works quite well for what I am attempting, but I would rather not use a node solution if possible.

Any suggestions?

Desiraedesire answered 24/11, 2015 at 19:36 Comment(1)
The JS part of this answer https://mcmap.net/q/410841/-php-export-download-mysql-backup-via-ajax posted to a php question did the job for me. It showed how to trigger an automatic download of the file, which the chosen answer here stopped short of.Loring
S
47

Just use XHR directly. This example is taken from MDN:

var oReq = new XMLHttpRequest();
oReq.open("GET", "/myfile.png", true);
oReq.responseType = "arraybuffer";

oReq.onload = function(oEvent) {
  var arrayBuffer = oReq.response;

  // if you want to access the bytes:
  var byteArray = new Uint8Array(arrayBuffer);
  // ...

  // If you want to use the image in your DOM:
  var blob = new Blob([arrayBuffer], {type: "image/png"});
  var url = URL.createObjectURL(blob);
  someImageElement.src = url;

  // whatever...
};

oReq.send();
Staley answered 24/11, 2015 at 20:39 Comment(8)
the problem is that this solution still gets the timeout issue just like using "windows.location.href".Desiraedesire
But now you have access to the request object and can adjust the timeout before sending the request, which I think was your goal?Staley
true - i added "oReq.timeout = 9999999;" -- although now i am wondering if all this was some apache issue all along.Desiraedesire
it turns out that it was a simple setting in httpd.conf - but your solution also works too. thank you VERY much (but jQuery is more fun)Desiraedesire
See also this solution from a related questionPanacea
@Desiraedesire could you please further elaborate what was the setting in httpd.conf?Randy
Please see the solution I posted below for something that should work in all situations using jQueryPermeability
great answer..better use XMLHttpRequest() only if we intent to download file from background js event.we can use oReq.status in on load if we want to invoke download depending on status code(200,404,500..)Cannonade
P
13

You can set up an $.ajax transport to modify the settings as mentioned here: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/

// use this transport for "binary" data type

$.ajaxTransport("+binary", function (options, originalOptions, jqXHR) {
    // check for conditions and support for blob / arraybuffer response type
    if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob))))) {
        return {
            // create new XMLHttpRequest
            send: function (headers, callback) {
                // setup all variables
                var xhr = new XMLHttpRequest(),
                    url = options.url,
                    type = options.type,
                    async = options.async || true,
                    // blob or arraybuffer. Default is blob
                    dataType = options.responseType || "blob",
                    data = options.data || null,
                    username = options.username || null,
                    password = options.password || null;

                xhr.addEventListener('load', function () {
                    var data = {};
                    data[options.dataType] = xhr.response;
                    // make callback and send data
                    callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
                });

                xhr.open(type, url, async, username, password);

                // setup custom headers
                for (var i in headers) {
                    xhr.setRequestHeader(i, headers[i]);
                }

                xhr.responseType = dataType;
                xhr.send(data);
            },
            abort: function () {
                jqXHR.abort();
            }
        };
    }
});

and then make your ajax call:

return $.ajax({
    url: url,
    method: 'GET',
    dataType: 'binary',
    processData: 'false',
    responseType: 'arraybuffer',
    headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (response) {
    var data = new Uint8Array(response);
    //do something with the data
    return data;
}, function (error) {
    alertify.error('There was an error! Error:' + error.name + ':' + error.status)
});
Permeability answered 19/6, 2018 at 3:45 Comment(5)
Eventually, this is the only way to have binary files in Jquery ajax.Holocene
@VipulDessai thanks for the reply, it really helped solve an issue I was having that I couldn't figure out how to fix on my own and worked exceptionally well in loading data from multiple Excel Spreadsheets(stored in binary format .xlsb) since I couldn't use any 3rd party libraries like OpenXML where I was at the time. Think it loaded something like 20 files that were close to 1 MB each in about 35 seconds which was an immense improvement on every other method I tried.Permeability
yes certainly this method will download any binary file. just curious, did your project involved transferring binary data from server to client-side and load it in a web browser?Holocene
@VipulDessai the project actually loaded the files from a Sharepoint Directory and kept it in memoryPermeability
Thank you for this answer, MattE! I tried to use it in a scenario that Vipul described above (get data from server and use it in browser). So far it seems to work but there is one major drawback. When using 'GET' ike in your example, no parameters contained in 'options.data'. Usually JQuery handles the conversion of that parameters into part of the url. Here you would have to do it yourself, see developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/…. Problem is, that the call to 'send' transmits the 'data' in the body of the request, but GET does not have a body.Yalonda
M
4

If you must use jQuery, you can use $.ajaxSetup() to modify low-level settings.

Example:

  // Set up AJAX settings for binary files:
  $.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
      if (settings.dataType === 'binary') {
        settings.xhr().responseType = 'arraybuffer';
      }
    }
  })

  // Make the actual call:
  let result = await $.ajax({
    url: '/api/export/data',
    type: 'GET',
    contentType: 'application/json',
    dataType: 'binary',
    processData: false,
    headers: {
      token: localStorage.token,
    },
  });
Morphophonemics answered 25/1, 2018 at 6:57 Comment(4)
sorry: still fails with "No conversion from text to binary".Buzzard
Worked for me. Nice concise solution.Ashby
Because I needed to support IE 11 which doesn't allow you to set responseType before xhr.open(), I used xhrFields: { responseType: 'arraybuffer' } instead of beforeSend().Ashby
I don't see how this can possibly work. jQuery.ajaxSettings.xhr is defined in jquery.js as always returning a new XMLHttpRequest. So that object that you are setting the responseType on is immediately lost after you use it.Pentalpha
P
4

Building off of the answer from @user6269864, which is incorrect, if you set your dataType to binary and add the following, it will return your result as an arraybuffer. The processData: false is also not needed (that variable involves compiling data that is sent to the server, not return data).

 $.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
      if (settings.dataType === 'binary')
        settings.xhr = () => $.extend(new window.XMLHttpRequest(), {responseType:'arraybuffer'})
    }
  })
Pentalpha answered 7/11, 2022 at 7:56 Comment(0)
V
0

The answer from @Dakusan was perfect. My project (springboot) gets a video file as a stream from another server using Jaffree, stores in a temp folder and serves it to frontend. Just in case someone finds this useful I'm sharing the code.

Controller method:

@RequestMapping(value = "/index/descarga", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<byte[]> getDescarga(String dayDeviceIdAndChannel, String initTime, String endTime) throws IOException {

        
        //downloads file and transcode, stores it in the temp folder
        cameraService.requestFileForDownload(requestUri,jsonRequest,digest);

        //check if file is created in the storage path with the default name
        StorageProperties sp = new StorageProperties();
        Path rootPath = Paths.get(sp.getLocation());
        Path pathToDestiny = rootPath.resolve("video.mp4");
        try {
            Resource resource = new UrlResource(pathToDestiny.toUri());
            if (resource.exists() || resource.isReadable()) {
                log.info("download uri " + resource.getURI());

                String filename = dayDeviceIdAndChannel.split("_")[0]+"_"+dayDeviceIdAndChannel.split("_")[2] +
                        "_"+initTime+"_"+endTime+".mp4";
                HttpHeaders headers = new HttpHeaders();
                headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""+filename+"\"");
                headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
                headers.add("Pragma", "no-cache");
                headers.add("Expires", "0");
                return ResponseEntity.ok()
                        .contentType(MediaType.parseMediaType("video/mp4"))
                        .contentLength(resource.contentLength())
                        .headers(headers)
                        .body(resource.getContentAsByteArray());
            }
            else {
                throw new FileNotFoundException(
                        "Could not read file");
            }
        } catch (MalformedURLException mue) {
            mue.printStackTrace();
        }
        return ResponseEntity.notFound().build();
}

Frontend javascript AJAX call:

$.ajaxSetup({
      beforeSend: function (jqXHR, settings) {
        if (settings.dataType === 'binary')
          settings.xhr = () => $.extend(new window.XMLHttpRequest(), {responseType:'arraybuffer'})
      }
    })


$.ajax({
            type: 'GET',
            url: '/index/descarga',
            data: {dayDeviceIdAndChannel: dayDeviceIdAndChannel,
                    initTime: initTime,
                    endTime: endTime },
            //mimeType : "video/mp4",
            dataType: 'binary',
            success: function(response) {
                console.log("Enviada peticion descarga ");
                //var byteArray = new Uint8Array(response);
                var byteArray = response;
                console.log("bytearray length: "+byteArray.length);
                loadingScreen.css("display","none");
                const blob = new Blob([byteArray], { type: 'video/mp4' });
                const downloadUrl = window.URL.createObjectURL(blob);
                console.log("DLURL: "+downloadUrl);
                const a = document.createElement("a");
                a.href = downloadUrl;
                a.download = "lalala.mp4";
                document.body.appendChild(a);
                a.click();
                URL.revokeObjectURL(downloadUrl);
            },
            error: function(jqXHR) {
                alert('Error: ' + jqXHR.status);
            }
        });
Viv answered 20/10, 2023 at 10:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.