Reading binary data using jQuery Ajax

Standard

jQuery is an excellent tool to make web development easy and straightforward. It helps while doing DOM manipulation and makes Ajax requests painless across different browsers and platforms. But if you want make an Ajax request, which is giving binary data as a response, you will discover that it does not work for jQuery, at least for now. Changing “dataType” parameter to “text”, does not help, neither changing it to any other jQuery supported Ajax data type.

Problem here is that jQuery still does not support HTML5 XMLHttpRequest Level 2 binary data type requests – there is even a bug in jQuery bug tracker, which asks for this feature. Although there is a long discussion about this subject on the GitHub, it seems that this feature will not become part of jQuery soon.

To find a solution for this problem, we have to modify XMLHttpRequest itself. To read binary data correctly, we have to treat response type as blob data type.

But what happens if we have to directly modify received binary data? XHR level 2 also introduces  “ArrayBuffer” response type. An ArrayBuffer is a generic fixed-length container for binary data. They are super handy if you need a generalized buffer of raw data, but the real power is that you can create “views” of the underlying data using JavaScript typed arrays.

This request creates an unsigned 8-bit integer array from data buffer. ArrayBuffer is especially useful if you have to read data for WebGL project, WebSocket or Canvas 2D

Binary data ajax tranport for jQuery

Sometimes making complete fallback to XMLHttpRequest is not a good idea, especially if you want to keep jQuery code clean and understandable. To solve this problem, jQuery allows us to create Ajax transports – plugins, which are created to make custom Ajax requests.

Our idea is to make “binary” Ajax transport based on our previous example. This Ajax transport creates new XMLHttpRequest and passes all the received data back to the jQuery.

For this script to work correctly, processData must be set to false, otherwise jQuery will try to convert received data into string, but fails.

Now it is possible to read binary data using usual jQuery syntax:

If you want receive ArrayBuffer as response type, you can use responseType parameter while creating Ajax request:

How to setup custom headers?

It is possible to set multiple custom headers when you are making the request. To set custom headers, you can use “header” parameter and set its value as an object, which has list of headers:

Another options

Asynchronous or synchronous execution

It is possible to change execution type from asynchronous to synchrous when setting parameter “async” to false.

async:false,

Login with user name and password

If your script needs to have authentication during the request, you can use username and password parameters.

username:'john', password:'smith',

Supported browsers

BinaryTransport requires XHR2 responseType, ArrayBuffer and Blob response type support from your browser, otherwise it does not work as expected. Currently most major browsers should work fine.

Firefox: 13.0+ Chrome: 20+ Internet Explorer: 10.0+ Safari: 6.0 Opera: 12.10

Binary transport jQuery plugin is also available in my GitHub repository.

  • I used the code on this website, and combined it with datastream to decode binary data

    var structure_type = [
    ‘name1’, cstring:32,
    ‘array1’, [‘[]’,’uint8′,6
    ]

    success: function(result){

    var ds = new DataStream(result)

    ds.seek(0)

    var data_type = ds.readStruct(structure_type)

    }

  • captainigloo

    I am using this approach to retrieve binary data using ajax, but I can’t figure out how client-side caching works. When I do a simple “hello world” example, the server returns a Last-Modified header, a subsequent request has If-Modified-Since, the server returns 304 and the content is available.

    But when I include this in my application, there is no If-Modified-Since header so I get a 200 response instead of the 304 that I want. I can’t for the life of me see what the difference is between my simple example and my application, the snippet of code is identical but produces different results.

  • Tim Jeanes

    Extremely useful, thanks!

    I made one small change though. After:

    xhr.open(type, url, true);

    … I added:

    if (options.contentType)
    xhr.setRequestHeader(“Content-type”, options.contentType);

    Without that, I found my POSTed parameters got lost.

    • henryat

      Thank you for your comment, Tim.

      I guess transport already has an option to do that without adding any additional code (e.g. you can set custom request headers).

      For example:

      $.ajax({
      url: “images/blob.png”,
      type: “GET”,
      dataType: ‘binary’,
      headers:{‘Content-Type’:’image/png’},
      processData: false,
      success: function(result){
      },
      error: function (xhr, ajaxOptions, thrownError) {
      });

      In that case xhr.setRequestHeader(“Content-type”, ‘image/png’) will be added while making the request.

      Or you tried that and it was not working?

  • Pingback: Deploy binary files from SharePoint Hosted App | Stefan Bauer - n8d()

  • Pingback: Deploy binary files from SharePoint Hosted App to Host Web : n8dev()

  • Hi! Thank you for the code.

    BTW it seems you have to check xhr.status before passing it to callback, as jQuery does.

    E.g.:

    xhrSuccessStatus = {
    // file protocol always yields status code 0, assume 200
    0: 200,
    // Support: IE9
    // #1450: sometimes IE returns 1223 when it should be 204
    1223: 204
    }

    callback(xhrSuccessStatus[xhr.status] || xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());

    • henryat

      Hi Pavel!

      Thank you for your comment. I think that you have a point here. I will play around with your idea a little to find best possible solution.

      Thanks!

  • Pingback: How to load foreign image via POST request in browser? - DexPage()

  • Juan

    I get no conversion from text to binary. am I missing something. The script loaded correctly

  • Izzeddeen Huneyhen

    Dear Henry. Your solution helped me a lot. It’s a useful post.
    But after using your plugin, I used to handle the progress of the download using

    $.ajax(
    beforeSend: function(xhr){
    xhr.progress(function(evt){
    //my logic here
    });
    }
    )

    After using the plugin, progress function never gets called.

    Any advice?

  • Pingback: Ajax file download and store - CSS PHP()

  • async = options.async || true

    This will always default to true, even if async is explicitly set to false 😉 I suggest an explicit check for undefined:

    async = (options.async === (void 0)) || true

  • Brian Tilma

    This solved my problem beautifully. Thank you.

  • Mike

    Could someone talk a little more about ArrayBuffer?

    More specifically, I’d like to transfer an array of binary data over HTTP. Instead of creating an HTTP request for one image, I’d like to request a group of images (10-20); would ArrayBuffer be used for that?

    I am currently Base64 encoding the binary data and sticking it in a JSON array, but I’d be interested to pack more binary images into the response and avoid encoding them if I could skip that step.

    Thanks

  • Doni Kokabayev

    Excellent solution! Thank you!

  • Pingback: Uploading files using AJAX directly to S3 | Bram.us()

  • Rob de Beir

    Thank you so much for this post. After a day of trying to download binary files form SharePoint, it finally works due to your code 🙂

  • Rob de Beir

    This is an example of how I can now copy a binary file from Document library to List item:


    uploadForm = function (itemID)
    {
    var formTemplateUrl = "https://myaccount.sharepoint.com/MyDocLib/SourceForm.dotx"
    var fileName = "Destination.dotx"
    var listname = "MyList";

    // Chain ajax requests
    // https://medium.com/coding-design/writing-better-ajax-8ee4a7fb95f#.mm6rfde8a

    // dataType binary
    // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/

    var a1 = $.ajax({
    url: formTemplateUrl,
    method: "GET",
    dataType: "binary",
    processData: false,

    }),
    a2 = a1.then(function(data) {
    // .then() returns a new promise

    console.log('Success downloading file: '+formTemplateUrl);

    // Uploading Attachments to SharePoint Lists Using REST
    // http://sympmarc.com/2016/04/20/uploading-attachments-to-sharepoint-lists-using-rest/#comment-135064

    return $.ajax({
    url: _spPageContextInfo.webAbsoluteUrl +
    "/_api/web/lists/getbytitle('" + listname + "')/items(" + itemID + ")/AttachmentFiles/add(FileName='" + fileName + "')",
    method: 'POST',
    data: data,
    processData: false,
    headers: {
    "Accept": "application/json; odata=verbose",
    "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,

    }
    });
    });

    a2.done(function(data) {
    console.log('Success uploaded file: '+fileName);
    });

    }

  • Rodrigo V. Lagos Eustáquio

    I’m receiving CORS ‘Access-Control-Allow-Origin’ not present, but my server is all open.
    Tnks

  • Pingback: SharePoint: Use Rest API to copy files (not in apps) – time is .net()

  • Kar Thik

    Hello , this code works good in Chrome and IE but not in FireFox there is my code

    $.ajax({
    type: ‘POST’,
    url: (extraDetails.data.portalContext ? extraDetails.data.portalContext : “”) + $(this).attr(‘action’) + “?ts=” + new Date().getTime() + “&tnt=” + extraDetails.data.tenant,
    data: new FormData(this),
    processData: false,
    contentType: false,
    dataType: ‘binary’

    })
    .done(function (response, status, xhr) {
    //alert(‘success’);
    // check for a filename
    var filename = “”;
    if(response.size && response.type ){
    filename = “error.xlsx”;
    }
    // var disposition = xhr.getResponseHeader(‘Content-Disposition’);
    // if (disposition && disposition.indexOf(‘attachment’) !== -1) {
    // var filenameRegex = /filename[^;=n]*=(([‘”]).*?2|[^;n]*)/;
    // var matches = filenameRegex.exec(disposition);
    // if (matches != null && matches[1]) filename = matches[1].replace(/[‘”]/g, ”);
    // }
    //
    // var type = xhr.getResponseHeader(‘Content-Type’);
    // var blob = new Blob([response], { type: type });
    var blob = response ;
    if (typeof window.navigator.msSaveBlob !== ‘undefined’) {
    // IE workaround for “HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed.”
    window.navigator.msSaveBlob(blob, filename);
    } else {
    var URL = window.URL || window.webkitURL;
    var downloadUrl = URL.createObjectURL(blob);

    if (filename) {
    // use HTML5 a[download] attribute to specify filename
    var a = document.createElement(“a”);
    // safari doesn’t support this yet
    if (typeof a.download === ‘undefined’) {
    window.location = downloadUrl;
    } else {
    a.href = downloadUrl;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    }
    extraDetails.data.dialogService.showAlert(“warning”,
    “Unable to upload all the Code set codes”,
    //”Fee schedule file uploaded. But there are few validation failures “,
    “Please refer to the error.xlsx file downloaded on your machine.”);
    }

    setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
    }

    fileUploadSuccess = true;
    extraDetails.data.iframeDetails.src=extraDetails.data.iframeDetails.src;
    }).fail(function (jqXHR, textStatus) {
    // alert(‘dialogservice after error ‘ + dialogService)
    fileUploadSuccess = false;
    extraDetails.data.iframeDetails.src=extraDetails.data.iframeDetails.src;
    extraDetails.data.dialogService.showAlert(“warning”, “Error uploading Code set”, jqXHR.status + “: ” + (jqXHR.responseText ? jqXHR.responseText : ‘no response text from service. Generic error’));
    })
    arguments[0].preventDefault();
    return false;
    }

    Can anyone help me out while this

  • Takeo Nishioka

    Thank you for your post. However This code doesn’t work.
    The latest code only supports when dataType is either “blob” or “arraybuffer” 🙂