如何从 IE 中的 Javascript 访问 XHR responseBody(用于二进制数据)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1919972/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
how do I access XHR responseBody (for binary data) from Javascript in IE?
提问by Cheeso
I've got a web page that uses XMLHttpRequestto download a binary resource.
我有一个使用XMLHttpRequest下载二进制资源的网页。
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 responseBodyproperty 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.
在 Firefox 和 Gecko 中,我可以使用 responseText 来获取字节,即使字节流包含二进制零也是如此。我可能需要强制使用 mimetypeoverrideMimeType()来实现这一点。但是,在 IE 中,responseText 不起作用,因为它似乎在第一个零处终止。如果您读取 100,000 个字节,并且字节 7 是二进制零,则您将只能访问 7 个字节。IE 的 XMLHttpRequest 公开了一个responseBody访问字节的属性。我已经看到一些帖子表明不可能直接从 Javascript 以任何有意义的方式访问这个属性。这对我来说听起来很疯狂。
xhr.responseBodyisaccessible from VBScript, so the obvious workaround is to define a method in VBScript in the webpage, and then call that method from Javascript. See jsdapfor one example. EDIT: DO NOT USE THIS VBScript!!
xhr.responseBody是从VBScript访问,因此明显的解决方法是在VBScript在网页中定义的方法,然后从JavaScript调用该方法。参见jsdap的一个例子。 编辑:不要使用这个 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 possibletechnique using ADODB.Stream, which is a COM equivalent of a MemoryStream. See herefor an example. It does not require VBScript but does require a separate COM object.
还有一种可能的技术是使用 ADODB.Stream,它是 MemoryStream 的 COM 等价物。 有关示例,请参见此处。它不需要 VBScript,但需要一个单独的 COM 对象。
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.
但这不会很好地工作 - 现在大多数机器上都禁用了 ADODB.Stream。
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.
在 IE8 开发人员工具中——IE 相当于 Firebug——我可以看到 responseBody 是一个字节数组,我什至可以看到字节本身。数据就在那里。我不明白为什么我不能得到它。
Is it possible for me to read it with responseText?
我可以用 responseText 阅读它吗?
hints? (other than defining a VBScript method)
提示?(除了定义 VBScript 方法)
采纳答案by Cheeso
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).
是的,我想出的在 IE 中通过 XHR 读取二进制数据的答案是使用 VBScript 注入。起初这让我很反感,但是,我将其视为又一个依赖于浏览器的代码。(常规 XHR 和 responseText 在其他浏览器中工作正常;您可能必须使用 强制 mime 类型XMLHttpRequest.overrideMimeType()。这在 IE 上不可用)。
This is how I got a thing that works like responseTextin IE, even for binary data.
First, inject some VBScript as a one-time thing, like this:
这就是我如何得到一个responseText在 IE中工作的东西,即使对于二进制数据也是如此。首先,一次性注入一些 VBScript,如下所示:
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:
我正在使用的读取二进制文件的 JS 类公开了一个有趣的方法,readCharAt(i),它读取第 i 个索引处的字符(实际上是一个字节)。我是这样设置的:
// 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 codewas provided by Miskun.
的转换码被提供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.
我使用这种方法从 Javascript 中读取和提取 zip 文件,也在一个用 Javascript 读取和显示 EPUB 文件的类中使用。非常合理的表现。一个 500kb 的文件大约需要半秒。
回答by timrice
回答by Louis LC
I would suggest two other (fast) options:
我建议另外两个(快速)选项:
First, you can use ADODB.Recordsetto convert the byte array into a string. I would guess that this object is more common that ADODB.Stream, which is often disabled for security reasons. This option is VERY fast, less than 30msfor a 500kB file.
Second, if the Recordset component is not accessible, there is a trick to access the byte array data from Javascript. Send your xhr.responseBody to VBScript, pass it through any VBScript string function such as CStr (takes no time), and return it to JS. You will get a weird string with bytes concatenated into 16-bit unicode (in reverse). You can then convert this string quickly into a usable bytestring through a regular expressionwith dictionary-based replacement. Takes about 1sfor 500kB.
首先,您可以使用 ADODB.Recordset将字节数组转换为字符串。我猜想这个对象比 ADODB.Stream 更常见,出于安全原因,它经常被禁用。此选项非常快,对于 500kB 文件不到 30毫秒。
其次,如果 Recordset 组件不可访问,则有一个技巧可以从 Javascript 访问字节数组数据。将您的 xhr.responseBody 发送到 VBScript,将其传递给任何 VBScript 字符串函数,例如 CStr(不花时间),然后将其返回给 JS。你会得到一个奇怪的字符串,其中的字节连接成 16 位 unicode(反向)。然后,您可以通过带有基于字典的替换的正则表达式将此字符串快速转换为可用的字节字符串。500kB大约需要1秒。
For comparison, the byte-by-byte conversion through loops takes several minutesfor this same 500kB file, so it's a no-brainer :) Below the code I have been using, to insert into your header. Then call the function ieGetByteswith your xhr.responseBody.
为了进行比较,对于同一个 500kB 的文件,通过循环进行逐字节转换需要几分钟,因此很容易 :) 在我一直使用的代码下方,插入到您的标题中。然后调用函数ieGetBytes你xhr.responseBody。
<!--[if IE]>
<script type="text/vbscript">
'Best case scenario when the ADODB.Recordset object exists
'We will do the existence test in Javascript (see after)
'Extremely fast, about 25ms for a 500kB file
Function ieGetBytesADO(byteArray)
Dim recordset
Set recordset = CreateObject("ADODB.Recordset")
With recordset
.Fields.Append "temp", 201, LenB(byteArray)
.Open
.AddNew
.Fields("temp").AppendChunk byteArray
.Update
End With
ieGetBytesADO = recordset("temp")
recordset.Close
Set recordset = Nothing
End Function
'Trick to return a Javascript-readable string from a VBScript byte array
'Yet the string is not usable as such by Javascript, since the bytes
'are merged into 16-bit unicode characters. Last character missing if odd length.
Function ieRawBytes(byteArray)
ieRawBytes = CStr(byteArray)
End Function
'Careful the last character is missing in case of odd file length
'We Will call the ieLastByte function (below) from Javascript
'Cannot merge directly within ieRawBytes as the final byte would be duplicated
Function ieLastChr(byteArray)
Dim lastIndex
lastIndex = LenB(byteArray)
if lastIndex mod 2 Then
ieLastChr = Chr( AscB( MidB( byteArray, lastIndex, 1 ) ) )
Else
ieLastChr = ""
End If
End Function
</script>
<script type="text/javascript">
try {
// best case scenario, the ADODB.Recordset object exists
// we can use the VBScript ieGetBytes function to transform a byte array into a string
var ieRecordset = new ActiveXObject('ADODB.Recordset');
var ieGetBytes = function( byteArray ) {
return ieGetBytesADO(byteArray);
}
ieRecordset = null;
} catch(err) {
// no ADODB.Recordset object, we will do the conversion quickly through a regular expression
// initializes for once and for all the translation dictionary to speed up our regexp replacement function
var ieByteMapping = {};
for ( var i = 0; i < 256; i++ ) {
for ( var j = 0; j < 256; j++ ) {
ieByteMapping[ String.fromCharCode( i + j * 256 ) ] = String.fromCharCode(i) + String.fromCharCode(j);
}
}
// since ADODB is not there, we replace the previous VBScript ieGetBytesADO function with a regExp-based function,
// quite fast, about 1.3 seconds for 500kB (versus several minutes for byte-by-byte loops over the byte array)
var ieGetBytes = function( byteArray ) {
var rawBytes = ieRawBytes(byteArray),
lastChr = ieLastChr(byteArray);
return rawBytes.replace(/[\s\S]/g, function( match ) {
return ieByteMapping[match]; }) + lastChr;
}
}
</script>
<![endif]-->
回答by Grant Galitz
You could also just make a proxy script that goes to the address you're requesting & base64's it. Then you just have to pass a query string to the proxy script that tells it the address. In IE you have to manually do base64 in JS though. But this is a way to go if you don't want to use VBScript.
您也可以制作一个代理脚本,该脚本转到您请求的地址和 base64 的地址。然后,您只需将查询字符串传递给代理脚本,告诉它地址。在 IE 中,您必须在 JS 中手动执行 base64。但如果您不想使用 VBScript,这是一种方法。
I used this for my GameBoy Color emulator.
我将它用于我的 GameBoy Color 模拟器。
Here is the PHP script that does the magic:
这是发挥魔力的 PHP 脚本:
<?php
//Binary Proxy
if (isset($_GET['url'])) {
try {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, stripslashes($_GET['url']));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($curl, CURLOPT_POST, false);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
$result = curl_exec($curl);
curl_close($curl);
if ($result !== false) {
header('Content-Type: text/plain; charset=ASCII');
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + (3600 * 24 * 7)));
echo(base64_encode($result));
}
else {
header('HTTP/1.0 404 File Not Found');
}
}
catch (Exception $error) { }
}
?>
回答by rk2010
Thanks so much for this solution. the BinaryToArray() function in VbScript works great for me.
非常感谢这个解决方案。VbScript 中的 BinaryToArray() 函数对我很有用。
Incidentally, I need the binary data for providing it to an Applet. (Don't ask me why Applets can't be used for downloading binary data. Long story short.. weird MS authentication that cant go thru applets (URLConn) calls. Its especially weird in cases where users are behind a proxy )
顺便说一下,我需要将二进制数据提供给 Applet。(不要问我为什么 Applets 不能用于下载二进制数据。长话短说。奇怪的 MS 身份验证无法通过小程序 (URLConn) 调用。在用户位于代理后面的情况下尤其奇怪)
The Applet needs a byte array from this data, so here's what I do to get it:
Applet 需要从这些数据中获取一个字节数组,所以我这样做是为了得到它:
String[] results = result.toString().split(",");
byte[] byteResults = new byte[results.length];
for (int i=0; i<results.length; i++){
byteResults[i] = (byte)Integer.parseInt(results[i]);
}
The byte array can then converted into a bytearrayinputstream for further processing.
然后可以将字节数组转换为 bytearrayinputstream 以进行进一步处理。
回答by Renato Crivano
I was trying to download a file and than sign it using CAPICOM.DLL. The only way I coud do it was by injecting a VBScript function that does the download. That is my solution:
我试图下载一个文件,然后使用 CAPICOM.DLL 对其进行签名。我能做到的唯一方法是注入一个执行下载的 VBScript 函数。那是我的解决方案:
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
var VBConteudo_Script =
'<!-- VBConteudo -->\r\n'+
'<script type="text/vbscript">\r\n'+
'Function VBConteudo(url)\r\n'+
' Set objHTTP = CreateObject("MSXML2.XMLHTTP")\r\n'+
' objHTTP.open "GET", url, False\r\n'+
' objHTTP.send\r\n'+
' If objHTTP.Status = 200 Then\r\n'+
' VBConteudo = objHTTP.responseBody\r\n'+
' End If\r\n'+
'End Function\r\n'+
'\<\/script>\r\n';
// inject VBScript
document.write(VBConteudo_Script);
}
回答by George G
Thank you for this post.
感谢您对这篇文章。
I found this link usefull:
我发现这个链接很有用:
Specially this part:
特别是这部分:
</script>
<script language="VBScript">
Function BinaryToString(Binary)
Dim I,S
For I = 1 to LenB(Binary)
S = S & Chr(AscB(MidB(Binary,I,1)))
Next
BinaryToString = S
End Function
</script>
I've added this to my htm page. Then I call this function from my javascript:
我已将此添加到我的 htm 页面。然后我从我的 javascript 调用这个函数:
responseText = BinaryToString(xhr.responseBody);
Works on IE8, IE9, IE10, FF & Chrome.
适用于 IE8、IE9、IE10、FF 和 Chrome。

