Подтвердить что ты не робот

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

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

В Firefox и Gecko я могу использовать responseText для получения байтов, даже если байтовый поток включает двоичные нули. Мне может потребоваться принудительно использовать mimetype с overrideMimeType(), чтобы это произошло. В 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. См. здесь для примера. Он не требует 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 - эквивалент IE Firebug - я могу видеть, что responseBody - это массив байтов, и я даже вижу сами байты. Данные прямо там. Я не понимаю, почему я не могу добраться до него.

Возможно ли, чтобы я прочитал его с помощью responseText?

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

4b9b3361

Ответ 1

Да, ответ, который я придумал для чтения двоичных данных через 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. Очень разумная производительность. Примерно полсекунды для файла 500 КБ.

Ответ 2

XMLHttpRequest.responseBody - VBArray объект, содержащий необработанные байты. Вы можете преобразовать эти объекты в стандартные массивы с помощью функции toArray():

var data = xhr.responseBody.toArray();

Ответ 3

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

  • Во-первых, вы можете использовать ADODB.Recordset, чтобы преобразовать массив байтов в строку. Я бы предположил, что этот объект более распространен, чем ADODB.Stream, который часто отключается по соображениям безопасности. Эта опция ОЧЕНЬ быстрая, менее 30 мс для файла 500 КБ.

  • Во-вторых, если компонент Recordset недоступен, существует трюк для доступа к данным байтового массива из Javascript. Отправьте свой xhr.responseBody в VBScript, передайте его через любую строковую строку VBScript, такую ​​как CStr (не требует времени), и верните ее в JS. Вы получите странную строку с байтами, объединенными в 16-разрядный Unicode (в обратном порядке). Затем вы можете быстро преобразовать эту строку в полезную байтов через регулярное выражение со словарной заменой. Принимает около 1 с для 500 КБ.

Для сравнения, побайтовое преобразование через петли занимает несколько минут для этого же файла 500 КБ, поэтому без проблем:) Ниже кода, который я использовал, можно вставить в свой заголовок. Затем вызовите функцию 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

Большое спасибо за это решение. функция 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 для дальнейшей обработки.

Ответ 5

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

Я использовал это для моего эмулятора GameBoy Color.

Вот PHP script, который делает магию:

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

Ответ 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.