代码之家  ›  专栏  ›  技术社区  ›  Cheeso

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

  •  24
  • Cheeso  · 技术社区  · 15 年前

    XMLHttpRequest 下载二进制资源。

    overrideMimeType() 为了实现这一点。不过,在IE中,responseText不起作用,因为它似乎在第一个零处终止。如果读取100000个字节,而字节7是二进制零,则只能访问7个字节。IE的XMLHttpRequest公开了一个 responseBody 属性来访问字节。我看到一些帖子表明,直接从Javascript以任何有意义的方式访问此属性是不可能的。这听起来很疯狂。

    xhr.responseBody 可以从VBScript访问,因此显而易见的解决方法是在网页的VBScript中定义一个方法,然后从Javascript调用该方法。看见 jsdap 举个例子。

    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('');
    

    这是真的吗?最好的方法是什么?复制每个字节?对于一个大的二进制流,这将不是很有效。

    还有一个 可能的 使用ADODB.Stream的技术,它是与MemoryStream等效的COM。 See here 举个例子。它不需要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
    }
    

    但这不会很好地起作用——现在大多数机器上都禁用了ADODB.Stream。


    在IE8开发工具中——相当于Firebug的IE——我可以看到responseBody是一个字节数组,我甚至可以看到字节本身。数据是 就在那里 . 我不明白为什么我做不到。

    我可以用responseText阅读吗?

    暗示(除定义VBScript方法外)

    7 回复  |  直到 14 年前
        1
  •  14
  •   Knu    8 年前

    是的,我在IE中通过XHR读取二进制数据的答案是使用VBScript注入。起初,这让我很反感,但是,我把它看作是更多依赖于浏览器的代码。 (常规的XHR和responseText在其他浏览器中运行良好;您可能必须强制mime类型为 XMLHttpRequest.overrideMimeType()

    这就是我如何得到一个像 responseText 首先,一次性注入一些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);
    }
    

    我使用的读取二进制文件的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);
    }
    

    这个 conversion code 由Miskun提供。

    非常快,效果很好。

        2
  •  11
  •   Andy E    10 年前

    XMLHttpRequest.responseBody 是一个 VBArray 对象,该对象包含原始字节。可以使用将这些对象转换为标准阵列 toArray() 功能:

    var data = xhr.responseBody.toArray();
    
        3
  •  3
  •   Louis LC    15 年前

    我建议另外两个(快速)选项:

    1. 首先,你可以使用 记录集 将字节数组转换为字符串。我猜这个对象比ADODB.Stream更常见,它经常因为安全原因而被禁用。此选项非常快,比 对于500kB的文件。

    2. 其次,如果记录集组件不可访问,则存在 . 将xhr.responseBody发送到VBScript,通过任何VBScript字符串函数(如CStr)传递它(不需要时间),然后将其返回给JS。您将得到一个奇怪的字符串,其中字节连接成16位unicode(相反)。然后,您可以通过 基于字典的替换。大约 1s 500kB。

    为了进行比较,通过循环进行逐字节转换需要 几分钟 对于同一个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]-->
    
        4
  •  1
  •   rk2010    15 年前

    非常感谢这个解决方案。VbScript中的BinaryToArray()函数对我来说非常有用。

    顺便说一句,我需要二进制数据来提供给Applet(不要问我为什么小程序不能用于下载二进制数据。长话短说。。奇怪的MS身份验证无法通过小程序(URLCON)调用。尤其是当用户在代理背后时,情况更为奇怪)

    小程序需要一个来自此数据的字节数组,因此我要做的是:

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

    然后可以将字节数组转换为bytearrayinputstream以进行进一步处理。

        5
  •  1
  •   Ashley Medway Karthi    11 年前

    我试图下载一个文件,然后使用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);
    }
    
        6
  •  1
  •   George G    11 年前

    我发现这个链接很有用:

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

    特别是本部分:

    </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>
    

    我已将此添加到我的htm页面。

     responseText = BinaryToString(xhr.responseBody);
    

    IE8、IE9、IE10、FF&铬。

        7
  •  1
  •   Jeremy Meo    10 年前

    您还可以创建一个代理脚本,该脚本指向您请求的地址&就这样。然后,您只需向代理脚本传递一个查询字符串,该脚本告诉您地址。但在IE中,您必须在JS中手动执行base64。但如果您不想使用VBScript,这是一种方法。

    my GameBoy Color emulator .

    下面是发挥神奇作用的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) { }
    }
    ?>