简体   繁体   English

如何从IE中的Javascript访问XHR responseBody(二进制数据)?

[英]how do I access XHR responseBody (for binary data) from Javascript in IE?

I've got a web page that uses XMLHttpRequest to download a binary resource. 我有一个使用XMLHttpRequest下载二进制资源的网页。

In Firefox and Gecko I can use responseText to get the bytes, even if the bytestream includes binary zeroes. 在Firefox和Gecko中,我可以使用responseText来获取字节,即使字节流包含二进制零。 I may need to coerce the mimetype with overrideMimeType() to make that happen. 我可能需要使用overrideMimeType()强制mimetype来实现这一点。 In IE, though, responseText doesn't work, because it appears to terminate at the first zero. 但是,在IE中,responseText不起作用,因为它似乎终止于第一个零。 If you read 100,000 bytes, and byte 7 is a binary zero, you will be able to access only 7 bytes. 如果读取100,000个字节,而字节7是二进制零,则只能访问7个字节。 IE's XMLHttpRequest exposes a responseBody property to access the bytes. IE的XMLHttpRequest公开了一个responseBody属性来访问字节。 I've seen a few posts suggesting that it's impossible to access this property in any meaningful way directly from Javascript. 我已经看到一些帖子暗示直接从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. xhr.responseBody 从VBScript访问,因此明显的解决方法是在VBScript在网页中定义的方法,然后从JavaScript调用该方法。 See jsdap for one example. 有关一个示例,请参阅jsdap EDIT: DO NOT USE THIS VBScript!! 编辑:不要使用这个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. 还有一种可能的技术使用ADODB.Stream,它是一个与MemoryStream等效的COM。 See here for an example. 请看这里的例子。 It does not require VBScript but does require a separate COM object. 它不需要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. 在IE8开发人员工具 - 相当于Firebug的IE中 - 我可以看到responseBody是一个字节数组,我甚至可以看到字节本身。 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? 我可以用responseText读取它吗?

hints? 提示? (other than defining a VBScript method) (除了定义VBScript方法)

Yes, the answer I came up with for reading binary data via XHR in IE, is to use VBScript injection. 是的,我想通过IE中的XHR读取二进制数据的答案就是使用VBScript注入。 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). (常规XHR和responseText在其他浏览器中工作正常;您可能必须使用XMLHttpRequest.overrideMimeType()强制mime类型。这在IE上不可用)。

This is how I got a thing that works like responseText in IE, even for binary data. 这就是我在IE中得到像responseText一样的东西,即使对于二进制数据也是如此。 First, inject some VBScript as a one-time thing, like this: 首先,将一些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. 我正在使用的JS类读取二进制文件暴露了一个有趣的方法readCharAt(i) ,它在第i个索引处读取字符(实际上是一个字节)。 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. 转换代码由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. 我使用这种方法从Javascript读取和提取zip文件,也在一个用Javascript读取和显示EPUB文件的类中。 Very reasonable performance. 表现非常合理。 About half a second for a 500kb file. 500kb文件大约半秒钟。

XMLHttpRequest.responseBody is a VBArray object containing the raw bytes. XMLHttpRequest.responseBody是一个包含原始字节的VBArray对象。 You can convert these objects to standard arrays using the toArray() function: 您可以使用toArray()函数将这些对象转换为标准数组:

var data = xhr.responseBody.toArray();

I would suggest two other (fast) options: 我建议另外两个(快速)选项:

  1. First, you can use ADODB.Recordset to convert the byte array into a string. 首先,您可以使用ADODB.Recordset将字节数组转换为字符串。 I would guess that this object is more common that ADODB.Stream, which is often disabled for security reasons. 我猜这个对象比ADODB.Stream更常见,ADODB.Stream由于安全原因经常被禁用。 This option is VERY fast, less than 30ms for a 500kB file. 此选项非常快,500kB文件小于30 毫秒

  2. Second, if the Recordset component is not accessible, there is a trick to access the byte array data from Javascript . 其次,如果Recordset组件不可访问,则有一个从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. 将xhr.responseBody发送到VBScript,将其传递给任何VBScript字符串函数(如CStr)(不花时间),然后将其返回给JS。 You will get a weird string with bytes concatenated into 16-bit unicode (in reverse). 您将得到一个奇怪的字符串,其字节连接成16位unicode(反向)。 You can then convert this string quickly into a usable bytestring through a regular expression with dictionary-based replacement. 然后,您可以通过基于字典的替换的正则表达式将此字符串快速转换为可用的字节字符串。 Takes about 1s for 500kB. 500kB大约需要1s

For comparison, the byte-by-byte conversion through loops takes several minutes for this same 500kB file, so it's a no-brainer :) Below the code I have been using, to insert into your header. 为了比较,对于同一个500kB文件,通过循环逐字节转换需要几分钟 ,所以这是一个明智的:)在我一直使用的代码下面,插入到你的标题中。 Then call the function ieGetBytes with your xhr.responseBody. 然后调用你的xhr.responseBody功能ieGetBytes。

<!--[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]-->

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. 顺便说一句,我需要二进制数据将它提供给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不能用于下载二进制数据。长话短说..奇怪的MS身份验证不能通过applets(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以进行进一步处理。

I was trying to download a file and than sign it using CAPICOM.DLL. 我试图下载一个文件,然后使用CAPICOM.DLL进行签名。 The only way I coud do it was by injecting a VBScript function that does the download. 我做的唯一方法是注入一个执行下载的VBScript函数。 That is my solution: 这是我的解决方案:

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);
}

Thank you for this post. 感谢您对这篇文章。

I found this link usefull: 我发现这个链接很有用:

http://www.codingforums.com/javascript-programming/47018-help-using-responsetext-property-microsofts-xmlhttp-activexobject-ie6.html http://www.codingforums.com/javascript-programming/47018-help-using-responsetext-property-microsofts-xmlhttp-activexobject-ie6.html

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. 我已将此添加到我的htm页面。 Then I call this function from my javascript: 然后我从我的javascript中调用此函数:

 responseText = BinaryToString(xhr.responseBody);

Works on IE8, IE9, IE10, FF & Chrome. 适用于IE8,IE9,IE10,FF和Chrome。

You could also just make a proxy script that goes to the address you're requesting & base64's it. 你也可以只创建一个代理脚本,转到你要求的地址和base64的地址。 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. 在IE中,您必须在JS中手动执行base64。 But this is a way to go if you don't want to use VBScript. 但如果您不想使用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) { }
}
?>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM