Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I load binary image data using Javascript and XMLHttpRequest?

Tags:

I was trying to load an image client side and base64 encode the bytes returned by the server in order to pass it off to perform some processing. IE has a RequestBody property of the XMLHttpRequest object, but I can't seem to use it, and RequestText is truncated. In Firefox, RequestText is there, but seems corrupted.

like image 392
Emil Lerch Avatar asked Jul 07 '09 21:07

Emil Lerch


People also ask

Which of the following options would allow an XMLHttpRequest to return binary data?

The send method of the XMLHttpRequest has been extended to enable easy transmission of binary data by accepting an ArrayBuffer , Blob , or File object. The following example creates a text file on-the-fly and uses the POST method to send the "file" to the server.

How does JavaScript handle binary data?

JavaScript can handle binary data via typed arrays. And here is a library for dealing with binary files, that you can use as a reference point for your application.

What is the best way to download binary data in JSON?

The JSON format natively doesn't support binary data. The binary data has to be escaped so that it can be placed into a string element (i.e. zero or more Unicode chars in double quotes using backslash escapes) in JSON. An obvious method to escape binary data is to use Base64.

Can you send binary data over HTTP?

HTTP is perfectly capable of handling binary data: images are sent over HTTP all the time, and they're binary. People upload and download files of arbitrary data types all the time with no problem.


1 Answers

Here's how I did it.

This technique is provided in an answer to another SO question, but it's also relevant here.

I didn't want to base64 encode anything. I wanted to download and parse binary files in the browser via Javascript, without modifying the server to encode them specially. I found that in Firefox, by coercing the mimetype of the response via overrideMimeType(), I could use XMLHttpRequest.responseText. On IE, it's different because:

  • responseText on IE truncates at the first zero. For binary streams this is a big problem.

  • there is no XMLHttpRequest.overrideMimeType(), to force IE to treat binary streams as text.

  • while there is a XMLHttpRequest.responseBody (IE only!) that is specifically designed to be used with binary data streams, maddeningly that property is not usable from Javascript.

Therefore, the need is to convert IE's responseBody property into a thing that looks like responseText from FireFox, with the mime-type coercion. This is possible using injected VBScript.

To make it cross-browser, you need to just pack up the browser-specific logic in a conditional. This is what I used:

// one-time code if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {     var IEBinaryToArray_ByteStr_Script =     "<!-- IEBinaryToArray_ByteStr -->\r\n"+     "<script type='text/vbscript'>\r\n"+     "Function IEBinaryToArray_ByteStr(Binary)\r\n"+     "   IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+     "End Function\r\n"+     "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+     "   Dim lastIndex\r\n"+     "   lastIndex = LenB(Binary)\r\n"+     "   if lastIndex mod 2 Then\r\n"+     "       IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+     "   Else\r\n"+     "       IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+     "   End If\r\n"+     "End Function\r\n"+     "</script>\r\n";      // inject VBScript     document.write(IEBinaryToArray_ByteStr_Script); }   // each time you make a request for a binary resource: var req = (function() {     if (window.XMLHttpRequest) {         return new window.XMLHttpRequest();     }     else {         try {             return new ActiveXObject("MSXML2.XMLHTTP");         }         catch(ex) {             return null;         }     } })();  var fileContents = ""; var filesize = -1; var readByteAt = function(i){     return fileContents.charCodeAt(i) & 0xff; };  req.open("GET", url, true);  if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {     // IE-specific logic here     // helper to convert from responseBody to a "responseText" like thing     var convertResponseBodyToText = function (binary) {         var byteMapping = {};         for ( var i = 0; i < 256; i++ ) {             for ( var j = 0; j < 256; j++ ) {                 byteMapping[ String.fromCharCode( i + j * 256 ) ] =                     String.fromCharCode(i) + String.fromCharCode(j);             }         }         var rawBytes = IEBinaryToArray_ByteStr(binary);         var lastChr = IEBinaryToArray_ByteStr_Last(binary);         return rawBytes.replace(/[\s\S]/g,                                 function( match ) { return byteMapping[match]; }) + lastChr;     };      req.setRequestHeader("Accept-Charset", "x-user-defined");     req.onreadystatechange = function(event){         if (req.readyState == 4) {             if (req.status == 200) {                 fileContents = convertResponseBodyToText(req.responseBody);                 fileSize = fileContents.length-1;                 // invoke a callback here, if you like...             }             else{                 alert("download failed, status " + req.status);             }         }     };     req.send();  } else {     // ff/Gecko/Webkit specific stuff here     req.onreadystatechange = function(aEvt) {         if (req.readyState == 4) { // completed             if(req.status == 200){ // status == OK                 fileContents = binStream.req.responseText;                 filesize = fileContents.length;                 // invoke a callback here, if you like...             }             else {                 alert("download failed, status " + req.status);             }         }     };     // coerce response type     req.overrideMimeType('text/plain; charset=x-user-defined');     req.send(null); } 

...then call readByte(i) to get the byte at the ith position in the binary file.

Good luck.

Credit to Miskun for the VBScript conversion logic.

like image 160
Cheeso Avatar answered Oct 17 '22 18:10

Cheeso