web-dev-qa-db-ja.com

IEのJavascriptからXHRresponseBody(バイナリデータ用)にアクセスするにはどうすればよいですか?

XMLHttpRequest を使用してバイナリリソースをダウンロードするWebページがあります。

FirefoxとGeckoでは、バイトストリームにバイナリゼロが含まれている場合でも、responseTextを使用してバイトを取得できます。それを実現するには、mimetypeをoverrideMimeType()で強制変換する必要があるかもしれません。ただし、IEでは、responseTextは最初のゼロで終了しているように見えるため、機能しません。 100,000バイトを読み取り、バイト7が2進ゼロの場合、アクセスできるのは7バイトのみです。 IEのXMLHttpRequestは、バイトにアクセスするためにresponseBodyプロパティを公開します。 Javascriptから直接このプロパティに意味のある方法でアクセスすることは不可能であることを示唆する投稿をいくつか見ました。これは私にはクレイジーに聞こえます。

xhr.responseBodyis VBScriptからアクセスできるため、明らかな回避策は、Webページの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('');

これは本当に本当ですか?一番いい方法?すべてのバイトをコピーしますか?あまり効率的ではない大きなバイナリストリームの場合。

また、MemoryStreamと同等のCOMであるADODB.Streamを使用するpossibleテクニックもあります。 ここを参照 例を示します。 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メソッドの定義以外)

25
Cheeso

はい、IEでXHRを介してバイナリデータを読み取るために私が思いついた答えは、VBScriptインジェクションを使用することです。これは最初は不快でしたが、ブラウザに依存するもう1つのコードとして見ています。 (通常のXHRとresponseTextは他のブラウザーでは正常に機能します。mimeタイプを XMLHttpRequest.overrideMimeType() で強制変換する必要がある場合があります。これはIEでは使用できません)。

これが、バイナリデータの場合でも、IEでresponseTextのように機能するものを取得した方法です。まず、次のように、VBScriptを1回限りのものとして挿入します。

_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クラスは、1つの興味深いメソッド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によって提供されました。

非常に高速で、うまく機能します。

このメソッドを使用して、JavascriptからZipファイルを読み取って抽出しました。また、JavascriptでEPUBファイルを読み取って表示するクラスでも使用しました。非常に合理的なパフォーマンス。 500kbファイルの場合は約0.5秒。

14
Cheeso

_XMLHttpRequest.responseBody_は VBArray 生のバイトを含むオブジェクトです。 toArray()関数を使用して、これらのオブジェクトを標準配列に変換できます。

_var data = xhr.responseBody.toArray();
_
11
timrice

私は他の2つの(速い)オプションを提案します:

  1. まず、ADODB.Recordsetを使用して、バイト配列を文字列に変換できます。このオブジェクトは、セキュリティ上の理由で無効になっていることが多いADODB.Streamよりも一般的だと思います。このオプションは非常に高速で、500kBファイルの場合は30ms未満です。

  2. 次に、Recordsetコンポーネントにアクセスできない場合は、Javascriptからバイト配列データにアクセスするためのトリックがあります。 xhr.responseBodyをVBScriptに送信し、CStrなどのVBScript文字列関数に渡して(時間はかかりません)、JSに返します。バイトが16ビットのUnicodeに連結された(逆の)奇妙な文字列が表示されます。次に、辞書ベースの置換を使用して正規表現を使用して、この文字列を使用可能なバイト文字列にすばやく変換できます。 500kBで約1sかかります。

比較のために、ループを介したバイトごとの変換には、この同じ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]-->
3
Louis LC

ファイルをダウンロードして、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
Renato Crivano

このソリューションをどうもありがとう。 VbScriptのBinaryToArray()関数は私にとって素晴らしい働きをします。

ちなみに、アプレットに提供するためのバイナリデータが必要です。 (なぜアプレットをバイナリデータのダウンロードに使用できないのか私に聞かないでください。簡単に言えば..アプ​​レット(URLConn)呼び出しを通過できない奇妙なMS認証。ユーザーがプロキシの背後にいる場合は特に奇妙です)

アプレットはこのデータからバイト配列を必要とするので、それを取得するために私が行うことは次のとおりです。

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

その後、バイト配列をバイト配列入力ストリームに変換して、さらに処理することができます。

1
rk2010

また、要求しているアドレスにアクセスするプロキシスクリプトを作成し、base64を作成することもできます。次に、アドレスを通知するクエリ文字列をプロキシスクリプトに渡す必要があります。 IEではJSでbase64を手動で実行する必要があります。ただし、VBScriptを使用したくない場合はこれが最適な方法です。

私はこれを 私のゲームボーイカラーエミュレーター に使用しました。

これが魔法を実行する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
Grant Galitz

この投稿をありがとうございます。

私はこのリンクが役に立ちました:

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
George G