【问题标题】:how do I access XHR responseBody (for binary data) from Javascript in IE?如何从 IE 中的 Javascript 访问 XHR responseBody(用于二进制数据)?
【发布时间】:2010-12-27 13:24:25
【问题描述】:

我有一个使用XMLHttpRequest 下载二进制资源的网页。

在 Firefox 和 Gecko 中,我可以使用 responseText 来获取字节,即使字节流包含二进制零。我可能需要用overrideMimeType() 强制mimetype 来实现这一点。但是,在 IE 中,responseText 不起作用,因为它似乎在第一个零处终止。如果您读取 100,000 个字节,并且字节 7 是二进制零,您将只能访问 7 个字节。 IE 的 XMLHttpRequest 公开了一个 responseBody 属性来访问字节。我看过一些帖子表明不可能直接从 Javascript 以任何有意义的方式访问此属性。这对我来说听起来很疯狂。

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

这是真的吗?最好的方式?复制每个字节?对于不会非常有效的大型二进制流。

还有一个可能技术使用 ADODB.Stream,它是一个 COM 等效的 MemoryStream。 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 方法)

【问题讨论】:

    标签: javascript xmlhttprequest


    【解决方案1】:

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

    这就是我如何在 IE 中获得类似于 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 提供。

    非常快,效果很好。

    我使用这种方法从 Javascript 中读取和提取 zip 文件,并在一个用 Javascript 读取和显示 EPUB 文件的类中。很合理的表现。一个 500kb 的文件大约需要半秒。

    【讨论】:

    • 你在其他浏览器中用什么来读取二进制数据的?我看到您的问题引用了 getBuffer 函数。那是什么? AFAIK 只有 IE 支持responseBody。那么你用了什么 ??
    • 我在 IE 中使用了 MSXML2.XMLHTTP,在非 IE 中使用了 XMLHttpRequest()。在非 IE 浏览器中,我能够使用 responseText 来获取字节流。显然 IE 认为它是一个字符串,因此在 IE 中,除了第一个零之外,responseText[i] 返回“未定义”。但FF3.5并非如此。它只是工作。
    • 但是......那么这可能是一个不同的问题:IE 不允许在字符串上使用 [] 运算符。它改用.charAt(num)。所以可能只是你需要responseText.charAt(i)
    • 我使用了 responseText.charCodeAt(i) - 它在 IE 中不起作用,在第一个零之后。我刚刚用更多代码更新了帖子,以进行说明。
    • 谢谢。现在我要洗澡了……我觉得好脏。但很高兴这解决了我的问题。
    【解决方案2】:

    非常感谢这个解决方案。 VbScript 中的 BinaryToArray() 函数非常适合我。

    顺便说一句,我需要将二进制数据提供给 Applet。 (不要问我为什么 Applets 不能用于下载二进制数据。长话短说……奇怪的 MS 身份验证无法通过 applet (URLConn) 调用。在用户使用代理的情况下尤其奇怪)

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

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

    【讨论】:

      【解决方案3】:

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

      1. 首先,您可以使用 ADODB.Recordset 将字节数组转换为字符串。我猜这个对象比 ADODB.Stream 更常见,出于安全原因,它经常被禁用。这个选项非常快,对于 500kB 的文件不到 30ms

      2. 其次,如果 Recordset 组件不可访问,则有一个从 Javascript 访问字节数组数据的技巧。将您的 xhr.responseBody 发送到 VBScript,将其传递给任何 VBScript 字符串函数,例如 CStr(无需时间),然后将其返回给 JS。您将得到一个奇怪的字符串,其中字节连接成 16 位 unicode(反向)。然后,您可以通过 正则表达式 使用基于字典的替换将此字符串快速转换为可用的字节串。 500kB 大约需要 1 秒

      为了比较,对于同一个 500kB 文件,通过循环进行逐字节转换需要 几分钟,所以这很容易 :) 在我一直使用的代码下方,插入你的标题。然后用你的 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]-->
      

      【讨论】:

      • ADODB 方法不起作用。尽管速度可能很快,但它返回的字符超出了标准 ASCII 范围。因此,您的数据最终会损坏。现在,如果有一种巧妙的方法可以将“字节数组”直接转换为 JS 数组(不使用 VBArray),那会更快。
      • “通过循环逐字节转换”是否对应this answer
      【解决方案4】:

      您也可以只制作一个代理脚本,该脚本将转到您请求的地址和 base64 的地址。然后,您只需将查询字符串传递给告诉它地址的代理脚本。不过,在 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) { }
      }
      ?>
      

      【讨论】:

        【解决方案5】:

        XMLHttpRequest.responseBody 是一个包含原始字节的 VBArray 对象。您可以使用toArray() 函数将这些对象转换为标准数组:

        var data = xhr.responseBody.toArray();
        

        【讨论】:

        • 我相信我试过了,但没有成功;不过,现在不记得是什么问题了。
        • 再试一次,我刚刚确认它按预期工作。应该只是替换你正在做的地方: var fileContents = BinaryToArray(that.req.responseBody).toArray();
        • 还应该考虑进行特征检测而不是查看用户代理字符串。只需检查“overridemimetype”是否存在,如果不存在,则检查 VBArray 是否存在,如果不存在,则弃之;)
        • @timrice: "VBArray" 方法不适用于 IE8 或更早版本。它将抛出“VBArray 预期”类型错误。特征检测也不起作用,因为“VBArray”对象本身存在于 IE8 上。请停止盲目传播有缺陷的方法。虽然它适用于 IE9 及更高版本(即使在 IE8 模式下),但它并不比“VBScript+CStr”方法快。
        • 谢谢,在 IE9 中非常适合我(无论如何,我支持的最低 IE 版本),并且是一个更简单的解决方案。
        【解决方案6】:

        我试图下载一个文件,然后使用 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);
        }
        

        【讨论】:

          【解决方案7】:

          感谢您的这篇文章。

          我发现这个链接很有用:

          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 页面。 然后我从我的 javascript 调用这个函数:

           responseText = BinaryToString(xhr.responseBody);
          

          适用于 IE8、IE9、IE10、FF 和 Chrome。

          【讨论】:

            猜你喜欢
            • 2011-04-11
            • 2012-07-02
            • 2016-05-01
            • 1970-01-01
            • 2017-01-30
            • 1970-01-01
            • 1970-01-01
            • 2020-02-29
            • 1970-01-01
            相关资源
            最近更新 更多