Как получить доступ к XHR responseBody (для двоичных данных) из Javascript в IE? - PullRequest
24 голосов
/ 17 декабря 2009

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

В Firefox и Gecko я могу использовать responseText для получения байтов, даже если поток байтов содержит двоичные нули. Возможно, мне придется заставить mimetype с помощью overrideMimeType(), чтобы это произошло. В IE, однако, responseText не работает, потому что он, кажется, заканчивается в первом нуле. Если вы прочитаете 100 000 байтов, а байт 7 - это двоичный ноль, вы сможете получить доступ только к 7 байтам. IEH 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)

Ответы [ 7 ]

14 голосов
/ 18 декабря 2009

Да, ответ, который я придумал для чтения двоичных данных через 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);
}

Код преобразования предоставлен Мискуном.

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

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

11 голосов
/ 02 декабря 2010

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

var data = xhr.responseBody.toArray();
3 голосов
/ 16 июня 2010

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

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

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

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

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

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.

1 голос
/ 03 января 2014

Я пытался скачать файл и подписать его, используя 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);
}
1 голос
/ 29 сентября 2010

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

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

Вот скрипт 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) { }
}
?>
1 голос
/ 22 декабря 2009

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

Кстати, мне нужны двоичные данные для предоставления их апплету. (Не спрашивайте меня, почему Applets нельзя использовать для загрузки двоичных данных. Короче говоря ... странная аутентификация 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]);
    }

Затем байтовый массив может быть преобразован в байтовый входной поток для дальнейшей обработки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...