как получить доступ к xhr responseBody (для двоичных данных) из Javascript в IE?

у меня есть веб-страницу, которая использует XMLHttpRequest для загрузки двоичного ресурса.

в Firefox и Gecko я могу использовать responseText для получения байтов, даже если bytestream включает двоичные нули. Возможно, мне придется принудить mimetype с overrideMimeType() чтобы это произошло. В IE, однако, responseText не работает, потому что он, похоже, завершается с первым нулем. Если Вы читаете 100 000 байт, а байт 7 является двоичным нулем, вы сможете получить доступ только к 7 байтам. IE XMLHttpRequest предоставляет responseBody свойство для доступа к байт. Я видел несколько сообщений, предполагающих, что невозможно получить доступ к этому свойству каким-либо значимым образом непосредственно из Javascript. Это звучит безумно для меня.

xhr.responseBody и доступно из VBScript, поэтому очевидным обходным путем является определение метода в VBScript на веб-странице, а затем вызов этого метода из Javascript. См.jsdap для примера. EDIT: НЕ ИСПОЛЬЗУЙТЕ ЭТО По 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 in
         ReDim byteArray(LenB(Binary))n
         For i = 1 To LenB(Binary)n
             byteArray(i-1) = AscB(MidB(Binary, i, 1))n
         Nextn
         BinaryToArray = byteArrayn
     End Functionn
</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.Поток, который является COM-эквивалентом MemoryStream. посмотреть здесь для примера. Он не требует 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.Поток отключен на большинстве машин в эти дни.


в инструментах разработчика IE8 - эквиваленте IE Firebug - я вижу, что responseBody-это массив байтов, и я даже могу видеть сами байты. Данные тут. Не понимаю, почему я не могу до него добраться.

возможно ли для меня прочитать его с помощью responseText?

намеки? (кроме определения метода VBScript)

7 ответов


Да, Ответ, который я придумал для чтения двоичных данных через XHR в IE, заключается в использовании инъекции VBScript. Сначала это было неприятно мне, но я смотрю на это как на еще один зависящий от браузера бит кода. (Обычный XHR и responseText отлично работает в других браузерах; возможно, вам придется принудить тип mime с XMLHttpRequest.overrideMimeType(). Это недоступно в IE).

Это, как я получил вещь, которая работает как 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);
}

класс 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);
}

на преобразование кода был предоставлен Miskun.

очень быстро, работает отлично.

я использовал этот метод для чтения и извлечения zip-файлов из Javascript, а также в классе, который читает и отображает файлы EPUB в Javascript. Очень разумная производительность. Около половины секунды для файла 500kb.


XMLHttpRequest.responseBody это VBArray объект, содержащий необработанные байты. Вы можете преобразовать эти объекты в стандартные массивы, используя :

var data = xhr.responseBody.toArray();

Я бы предложил два других (быстрый) параметры:

  1. во-первых, вы можете использовать объект adodb.Recordset для преобразования массива байтов в строку. Я бы предположил, что этот объект более распространен, чем ADODB.Поток, который часто отключается по соображениям безопасности. Эта опция очень быстрая, меньше чем 30ms для файла 500kB.

  2. во-вторых, если компонент набора записей недоступен, существует трюк для доступа к байту массив данных из Javascript. Отправьте свой xhr.responseBody к VBScript, передайте его через любую строковую функцию VBScript, такую как CStr (не занимает много времени), и верните его в JS. Вы получите странную строку с байтами, объединенными в 16-битный Юникод (в обратном порядке). Затем вы можете быстро преобразовать эту строку в полезный bytestring через регулярные выражения С заменой на основе словаря. Занимает около 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]-->

большое спасибо за это решение. функция BinaryToArray () в VbScript отлично работает для меня.

кстати, мне нужны двоичные данные для предоставления его в апплет. (Не спрашивайте меня, почему апплеты нельзя использовать для загрузки двоичных данных. Длинная короткая история.. странная аутентификация MS, которая не может пройти через апплеты (URLConn). Его особенно странно в случаях, когда пользователи находятся за прокси)

апплету нужен массив байтов из этих данных, поэтому вот что я делаю, чтобы получить это:

 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 для дальнейшей обработки.


Я пытался загрузить файл и подписать его с помощью 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);
}

Спасибо за этот пост.

Я нашел эту полезную ссылку:

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.


вы также можете просто сделать прокси-скрипт, который идет по адресу, который вы запрашиваете & base64. Затем вам просто нужно передать строку запроса прокси-скрипту, который сообщает ему адрес. В IE вы должны вручную сделать base64 в JS. Но это способ пойти, если вы не хотите использовать VBScript.

я использовал это для мой эмулятор цвета GameBoy.

вот 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) { }
}
?>