Como faço para acessar o responseBody XHR (para dados binários) de JavaScript no IE?

Eu tenho uma página da web que usa XMLHttpRequest para baixar um recurso binário.

No Firefox e no Gecko, eu posso usar o responseText para obter os bytes, mesmo que o bytestream inclua zeros binários. Eu posso precisar coagir o mimetype com overrideMimeType() para que isso aconteça. No IE, no entanto, responseText não funciona, porque parece terminar no primeiro zero. Se você ler 100.000 bytes e o byte 7 for um zero binário, você poderá acessar apenas 7 bytes. O XMLHttpRequest do IE expõe uma propriedade responseBody para acessar os bytes. Eu vi alguns posts sugerindo que é impossível acessar esta propriedade de qualquer maneira significativa diretamente do Javascript. Isso parece loucura para mim.

xhr.responseBody é acessível a partir do VBScript, portanto, a solução óbvia é definir um método no VBScript na página da Web e chamar esse método a partir do Javascript. Veja jsdap por um exemplo. EDIT: NÃO USE ESTE 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('\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\ '); 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(''); 

Isso é realmente verdade? A melhor maneira? copiando cada byte? Para um grande stream binário que não será muito eficiente.

Há também uma técnica possível usando ADODB.Stream, que é um equivalente COM de um MemoryStream. Veja aqui um exemplo. Ele não requer o VBScript, mas requer um object COM separado.

 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 } 

Mas isso não vai funcionar bem – ADODB.Stream está desativado na maioria das máquinas nos dias de hoje.


Nas ferramentas de desenvolvimento do IE8 – o IE equivalente ao Firebug – eu posso ver que o responseBody é uma matriz de bytes e eu posso ver os bytes eles mesmos. Os dados estão bem aqui . Eu não entendo porque não consigo chegar a isso.

É possível para mim lê-lo com responseText?

sugestões? (diferente de definir um método VBScript)

Sim, a resposta que eu obtive para ler dados binários via XHR no IE, é usar a injeção de VBScript. Isso foi desagradável para mim no início, mas, eu vejo isso como apenas mais um bit de código dependente do navegador. (O XHR regular e responseText funciona bem em outros navegadores; você pode ter que coagir o tipo MIME com XMLHttpRequest.overrideMimeType() . Isso não está disponível no IE).

É assim que eu tenho uma coisa que funciona como responseText no IE, mesmo para dados binários. Primeiro, injetar alguns VBScript como uma coisa de uma só vez, assim:

 if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { var IEBinaryToArray_ByteStr_Script = "\r\n"+ "\r\n"; // inject VBScript document.write(IEBinaryToArray_ByteStr_Script); } 

A class JS que estou usando que lê arquivos binários expõe um único método interessante, readCharAt(i) , que lê o caractere (um byte, na verdade) no i’th index. É assim que eu configuro:

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

O código de conversão foi fornecido por Miskun.

Muito rápido, funciona muito bem.

Eu usei esse método para ler e extrair arquivos zip do Javascript, e também em uma class que lê e exibe arquivos EPUB em Javascript. Desempenho muito razoável. Cerca de meio segundo para um arquivo de 500kb.

XMLHttpRequest.responseBody é um object VBArray contendo os bytes brutos. Você pode converter esses objects em matrizes padrão usando a function toArray() :

 var data = xhr.responseBody.toArray(); 

Eu sugeriria duas outras opções (rápidas):

  1. Primeiro, você pode usar ADODB.Recordset para converter a matriz de bytes em uma seqüência de caracteres. Eu acho que esse object é mais comum que ADODB.Stream, que muitas vezes é desativado por razões de segurança. Esta opção é muito rápida, menos de 30ms para um arquivo de 500kB.

  2. Segundo, se o componente Recordset não estiver acessível, há um truque para acessar os dados da matriz de bytes do Javascript . Envie seu xhr.responseBody para o VBScript, passe-o por qualquer function de string do VBScript, como CStr (não demora), e retorne-o para o JS. Você obterá uma string estranha com bytes concatenados em unicode de 16 bits (ao contrário). Você pode então converter essa string rapidamente em um bytestring utilizável através de uma expressão regular com substituição baseada em dictionary. Leva cerca de 1s para 500kB.

Para comparação, a conversão byte-by-by através de loops leva vários minutos para esse mesmo arquivo de 500kB, então é óbvio 🙂 Abaixo do código que tenho usado, para inserir no seu header. Em seguida, chame a function ieGetBytes com seu xhr.responseBody.

  

Muito obrigado por esta solução. A function BinaryToArray () em VbScript funciona muito bem para mim.

Aliás, eu preciso dos dados binários para fornecer a um Applet. (Não me pergunte por que os Applets não podem ser usados ​​para fazer o download de dados binários. Resumindo: uma estranha autenticação do MS que não pode passar por chamadas de applets (URLConn). É especialmente estranho nos casos em que os usuários estão atrás de um proxy)

O Applet precisa de uma matriz de bytes desses dados, então aqui está o que eu faço para obtê-lo:

  String[] results = result.toString().split(","); byte[] byteResults = new byte[results.length]; for (int i=0; i 

A matriz de bytes pode então ser convertida em um bytearrayinputstream para processamento adicional.

Eu estava tentando baixar um arquivo e assiná-lo usando CAPICOM.DLL. A única maneira que eu poderia fazer era injetar uma function VBScript que faz o download. Essa é a minha solução:

 if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { var VBConteudo_Script = '\r\n'+ ' 

Obrigado por este post.

Eu encontrei este link útil:

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

Especialmente esta parte:

   

Eu adicionei isso à minha página htm. Então eu chamo essa function do meu javascript:

  responseText = BinaryToString(xhr.responseBody); 

Funciona no IE8, IE9, IE10, FF e Chrome.

Você também pode simplesmente criar um script de proxy que vá para o endereço que você está solicitando do & base64. Então você só tem que passar uma string de consulta para o script de proxy que informa o endereço. No IE você tem que fazer manualmente base64 em JS embora. Mas este é um caminho a percorrer se você não quiser usar o VBScript.

Eu usei isso para o meu emulador GameBoy Color .

Aqui está o script PHP que faz a mágica: