I've got a web page that uses XMLHttpRequest to download a binary resource.
In Firefox and Gecko I can use responseText to get the bytes, even if the bytestream includes binary zeroes. I may need to coerce the mimetype with overrideMimeType()
to make that happen. In IE, though, responseText doesn't work, because it appears to terminate at the first zero. If you read 100,000 bytes, and byte 7 is a binary zero, you will be able to access only 7 bytes. IE's XMLHttpRequest exposes a responseBody
property to access the bytes. I've seen a few posts suggesting that it's impossible to access this property in any meaningful way directly from Javascript. This sounds crazy to me.
xhr.responseBody
is accessible from VBScript, so the obvious workaround is to define a method in VBScript in the webpage, and then call that method from Javascript. See jsdap for one example. EDIT: DO NOT USE THIS VBScript!!
var IE_HACK = (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)); // no no no! Don't do this! if (IE_HACK) document.write('<script type="text/vbscript">\n\ Function BinaryToArray(Binary)\n\ Dim i\n\ ReDim byteArray(LenB(Binary))\n\ For i = 1 To LenB(Binary)\n\ byteArray(i-1) = AscB(MidB(Binary, i, 1))\n\ Next\n\ BinaryToArray = byteArray\n\ End Function\n\ </script>'); var xml = (window.XMLHttpRequest) ? new XMLHttpRequest() // Mozilla/Safari/IE7+ : (window.ActiveXObject) ? new ActiveXObject("MSXML2.XMLHTTP") // IE6 : null; // Commodore 64? xml.open("GET", url, true); if (xml.overrideMimeType) { xml.overrideMimeType('text/plain; charset=x-user-defined'); } else { xml.setRequestHeader('Accept-Charset', 'x-user-defined'); } xml.onreadystatechange = function() { if (xml.readyState == 4) { if (!binary) { callback(xml.responseText); } else if (IE_HACK) { // call a VBScript method to copy every single byte callback(BinaryToArray(xml.responseBody).toArray()); } else { callback(getBuffer(xml.responseText)); } } }; xml.send('');
Is this really true? The best way? copying every byte? For a large binary stream that's not going to be very efficient.
There is also a possible technique using ADODB.Stream, which is a COM equivalent of a MemoryStream. See here for an example. It does not require VBScript but does require a separate COM object.
if (typeof (ActiveXObject) != "undefined" && typeof (httpRequest.responseBody) != "undefined") { // Convert httpRequest.responseBody byte stream to shift_jis encoded string var stream = new ActiveXObject("ADODB.Stream"); stream.Type = 1; // adTypeBinary stream.Open (); stream.Write (httpRequest.responseBody); stream.Position = 0; stream.Type = 1; // adTypeBinary; stream.Read.... /// ???? what here }
But that's not going to work well - ADODB.Stream is disabled on most machines these days.
In The IE8 developer tools - the IE equivalent of Firebug - I can see the responseBody is an array of bytes and I can even see the bytes themselves. The data is right there. I don't understand why I can't get to it.
Is it possible for me to read it with responseText?
hints? (other than defining a VBScript method)
XMLHttpRequest (XHR) objects are used to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing. XMLHttpRequest is used heavily in AJAX programming.
Yes, the answer I came up with for reading binary data via XHR in IE, is to use VBScript injection. This was distasteful to me at first, but, I look at it as just one more browser dependent bit of code. (The regular XHR and responseText works fine in other browsers; you may have to coerce the mime type with XMLHttpRequest.overrideMimeType()
. This isn't available on IE).
This is how I got a thing that works like responseText
in IE, even for binary data. First, inject some VBScript as a one-time thing, like this:
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { var IEBinaryToArray_ByteStr_Script = "<!-- IEBinaryToArray_ByteStr -->\r\n"+ "<script type='text/vbscript' language='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); }
The JS class I'm using that reads binary files exposes a single interesting method, readCharAt(i)
, which reads the character (a byte, really) at the i'th index. This is how I set it up:
// see doc on http://msdn.microsoft.com/en-us/library/ms535874(VS.85).aspx function getXMLHttpRequest() { if (window.XMLHttpRequest) { return new window.XMLHttpRequest; } else { try { return new ActiveXObject("MSXML2.XMLHTTP"); } catch(ex) { return null; } } } // this fn is invoked if IE function IeBinFileReaderImpl(fileURL){ this.req = getXMLHttpRequest(); this.req.open("GET", fileURL, true); this.req.setRequestHeader("Accept-Charset", "x-user-defined"); // my 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); } } // call into VBScript utility fns var rawBytes = IEBinaryToArray_ByteStr(binary); var lastChr = IEBinaryToArray_ByteStr_Last(binary); return rawBytes.replace(/[\s\S]/g, function( match ) { return byteMapping[match]; }) + lastChr; }; this.req.onreadystatechange = function(event){ if (that.req.readyState == 4) { that.status = "Status: " + that.req.status; //that.httpStatus = that.req.status; if (that.req.status == 200) { // this doesn't work //fileContents = that.req.responseBody.toArray(); // this doesn't work //fileContents = new VBArray(that.req.responseBody).toArray(); // this works... var fileContents = convertResponseBodyToText(that.req.responseBody); fileSize = fileContents.length-1; if(that.fileSize < 0) throwException(_exception.FileLoadFailed); that.readByteAt = function(i){ return fileContents.charCodeAt(i) & 0xff; }; } if (typeof callback == "function"){ callback(that);} } }; this.req.send(); } // this fn is invoked if non IE function NormalBinFileReaderImpl(fileURL){ this.req = new XMLHttpRequest(); this.req.open('GET', fileURL, true); this.req.onreadystatechange = function(aEvt) { if (that.req.readyState == 4) { if(that.req.status == 200){ var fileContents = that.req.responseText; fileSize = fileContents.length; that.readByteAt = function(i){ return fileContents.charCodeAt(i) & 0xff; } if (typeof callback == "function"){ callback(that);} } else throwException(_exception.FileLoadFailed); } }; //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] this.req.overrideMimeType('text/plain; charset=x-user-defined'); this.req.send(null); }
The conversion code was provided by Miskun.
Very fast, works great.
I used this method to read and extract zip files from Javascript, and also in a class that reads and displays EPUB files in Javascript. Very reasonable performance. About half a second for a 500kb file.
XMLHttpRequest.responseBody
is a VBArray
object containing the raw bytes. You can convert these objects to standard arrays using the toArray()
function:
var data = xhr.responseBody.toArray();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With