如何使用Javascript和XMLHttpRequest加载二进制图像数据?

时间:2022-09-04 09:00:02

I was trying to load an image client side and base64 encode the bytes returned by the server in order to pass it off to perform some processing. IE has a RequestBody property of the XMLHttpRequest object, but I can't seem to use it, and RequestText is truncated. In Firefox, RequestText is there, but seems corrupted.

我试图加载一个图像客户端,并对服务器返回的字节进行base64编码,以便传递它来执行一些处理。IE有XMLHttpRequest对象的RequestBody属性,但是我似乎不能使用它,并且RequestText被截断。在Firefox中,RequestText存在,但似乎已经损坏。

3 个解决方案

#1


12  

Here's how I did it.

我是这样做的。

This technique is provided in an answer to another SO question, but it's also relevant here.

这个技巧是在回答另一个SO问题时提供的,但是在这里它也是相关的。

I didn't want to base64 encode anything. I wanted to download and parse binary files in the browser via Javascript, without modifying the server to encode them specially. I found that in Firefox, by coercing the mimetype of the response via overrideMimeType(), I could use XMLHttpRequest.responseText. On IE, it's different because:

我不想对任何东西进行base64编码。我想通过Javascript下载并解析浏览器中的二进制文件,而不需要修改服务器来对它们进行特殊的编码。我发现在Firefox中,通过overrideMimeType()强制响应的mimetype,我可以使用XMLHttpRequest.responseText。在IE上,它是不同的,因为:

  • responseText on IE truncates at the first zero. For binary streams this is a big problem.

    IE上的responseText在第一个0处截断。对于二进制流来说,这是一个大问题。

  • there is no XMLHttpRequest.overrideMimeType(), to force IE to treat binary streams as text.

    没有XMLHttpRequest.overrideMimeType()命令IE将二进制流视为文本。

  • while there is a XMLHttpRequest.responseBody (IE only!) that is specifically designed to be used with binary data streams, maddeningly that property is not usable from Javascript.

    虽然有一个XMLHttpRequest。responseBody(仅限IE !)专门设计用于二进制数据流的,令人抓狂的是,该属性不能从Javascript中使用。

Therefore, the need is to convert IE's responseBody property into a thing that looks like responseText from FireFox, with the mime-type coercion. This is possible using injected VBScript.

因此,需要使用mime类型强制将IE的responseBody属性转换为类似于FireFox的responseText。这可以使用注入的VBScript。

To make it cross-browser, you need to just pack up the browser-specific logic in a conditional. This is what I used:

要使它跨浏览器,您只需将特定于浏览器的逻辑打包成条件。这是我用过的:

// one-time code
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
    var IEBinaryToArray_ByteStr_Script =
    "<!-- IEBinaryToArray_ByteStr -->\r\n"+
    "<script type='text/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);
}


// each time you make a request for a binary resource:
var req = (function() {
    if (window.XMLHttpRequest) {
        return new window.XMLHttpRequest();
    }
    else {
        try {
            return new ActiveXObject("MSXML2.XMLHTTP");
        }
        catch(ex) {
            return null;
        }
    }
})();

var fileContents = "";
var filesize = -1;
var readByteAt = function(i){
    return fileContents.charCodeAt(i) & 0xff;
};

req.open("GET", url, true);

if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
    // IE-specific logic here
    // 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);
            }
        }
        var rawBytes = IEBinaryToArray_ByteStr(binary);
        var lastChr = IEBinaryToArray_ByteStr_Last(binary);
        return rawBytes.replace(/[\s\S]/g,
                                function( match ) { return byteMapping[match]; }) + lastChr;
    };

    req.setRequestHeader("Accept-Charset", "x-user-defined");
    req.onreadystatechange = function(event){
        if (req.readyState == 4) {
            if (req.status == 200) {
                fileContents = convertResponseBodyToText(req.responseBody);
                fileSize = fileContents.length-1;
                // invoke a callback here, if you like...
            }
            else{
                alert("download failed, status " + req.status);
            }
        }
    };
    req.send();

} else {
    // ff/Gecko/Webkit specific stuff here
    req.onreadystatechange = function(aEvt) {
        if (req.readyState == 4) { // completed
            if(req.status == 200){ // status == OK
                fileContents = binStream.req.responseText;
                filesize = fileContents.length;
                // invoke a callback here, if you like...
            }
            else {
                alert("download failed, status " + req.status);
            }
        }
    };
    // coerce response type
    req.overrideMimeType('text/plain; charset=x-user-defined');
    req.send(null);
}

...then call readByte(i) to get the byte at the ith position in the binary file.

…然后调用readByte(i)获取二进制文件中第i个位置的字节。

Good luck.

祝你好运。

Credit to Miskun for the VBScript conversion logic.

VBScript转换逻辑归功于Miskun。

#2


1  

If you're using COTS, you could always set up an intermediate gateway wherein the request is made and transformed (base64 encoded in this case) into something more palatable before being returned to the client.

如果您正在使用COTS,那么您可以设置一个中间网关,在返回给客户端之前,在其中创建请求并将请求(在本例中编码为base64)转换为更容易接受的东西。

#3


0  

You could have the server return base64 text, rather than doing that encoding client side.

您可以让服务器返回base64文本,而不是在客户端进行编码。

For example, (in ASP.NET) a request to /ImageAsBase64.ashx?file=/images/myimage.png could be coded to read the file, base64encode it, and stream it as a response.

例如,(在ASP.NET中)对/ImageAsBase64.ashx?file=/images/myimage的请求。可以对png进行编码,以读取文件,对其进行base64encode,并将其作为响应进行流处理。

It's really going to be pretty much the same thing in PHP or whatever.

这在PHP中是一样的。

#1


12  

Here's how I did it.

我是这样做的。

This technique is provided in an answer to another SO question, but it's also relevant here.

这个技巧是在回答另一个SO问题时提供的,但是在这里它也是相关的。

I didn't want to base64 encode anything. I wanted to download and parse binary files in the browser via Javascript, without modifying the server to encode them specially. I found that in Firefox, by coercing the mimetype of the response via overrideMimeType(), I could use XMLHttpRequest.responseText. On IE, it's different because:

我不想对任何东西进行base64编码。我想通过Javascript下载并解析浏览器中的二进制文件,而不需要修改服务器来对它们进行特殊的编码。我发现在Firefox中,通过overrideMimeType()强制响应的mimetype,我可以使用XMLHttpRequest.responseText。在IE上,它是不同的,因为:

  • responseText on IE truncates at the first zero. For binary streams this is a big problem.

    IE上的responseText在第一个0处截断。对于二进制流来说,这是一个大问题。

  • there is no XMLHttpRequest.overrideMimeType(), to force IE to treat binary streams as text.

    没有XMLHttpRequest.overrideMimeType()命令IE将二进制流视为文本。

  • while there is a XMLHttpRequest.responseBody (IE only!) that is specifically designed to be used with binary data streams, maddeningly that property is not usable from Javascript.

    虽然有一个XMLHttpRequest。responseBody(仅限IE !)专门设计用于二进制数据流的,令人抓狂的是,该属性不能从Javascript中使用。

Therefore, the need is to convert IE's responseBody property into a thing that looks like responseText from FireFox, with the mime-type coercion. This is possible using injected VBScript.

因此,需要使用mime类型强制将IE的responseBody属性转换为类似于FireFox的responseText。这可以使用注入的VBScript。

To make it cross-browser, you need to just pack up the browser-specific logic in a conditional. This is what I used:

要使它跨浏览器,您只需将特定于浏览器的逻辑打包成条件。这是我用过的:

// one-time code
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
    var IEBinaryToArray_ByteStr_Script =
    "<!-- IEBinaryToArray_ByteStr -->\r\n"+
    "<script type='text/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);
}


// each time you make a request for a binary resource:
var req = (function() {
    if (window.XMLHttpRequest) {
        return new window.XMLHttpRequest();
    }
    else {
        try {
            return new ActiveXObject("MSXML2.XMLHTTP");
        }
        catch(ex) {
            return null;
        }
    }
})();

var fileContents = "";
var filesize = -1;
var readByteAt = function(i){
    return fileContents.charCodeAt(i) & 0xff;
};

req.open("GET", url, true);

if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
    // IE-specific logic here
    // 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);
            }
        }
        var rawBytes = IEBinaryToArray_ByteStr(binary);
        var lastChr = IEBinaryToArray_ByteStr_Last(binary);
        return rawBytes.replace(/[\s\S]/g,
                                function( match ) { return byteMapping[match]; }) + lastChr;
    };

    req.setRequestHeader("Accept-Charset", "x-user-defined");
    req.onreadystatechange = function(event){
        if (req.readyState == 4) {
            if (req.status == 200) {
                fileContents = convertResponseBodyToText(req.responseBody);
                fileSize = fileContents.length-1;
                // invoke a callback here, if you like...
            }
            else{
                alert("download failed, status " + req.status);
            }
        }
    };
    req.send();

} else {
    // ff/Gecko/Webkit specific stuff here
    req.onreadystatechange = function(aEvt) {
        if (req.readyState == 4) { // completed
            if(req.status == 200){ // status == OK
                fileContents = binStream.req.responseText;
                filesize = fileContents.length;
                // invoke a callback here, if you like...
            }
            else {
                alert("download failed, status " + req.status);
            }
        }
    };
    // coerce response type
    req.overrideMimeType('text/plain; charset=x-user-defined');
    req.send(null);
}

...then call readByte(i) to get the byte at the ith position in the binary file.

…然后调用readByte(i)获取二进制文件中第i个位置的字节。

Good luck.

祝你好运。

Credit to Miskun for the VBScript conversion logic.

VBScript转换逻辑归功于Miskun。

#2


1  

If you're using COTS, you could always set up an intermediate gateway wherein the request is made and transformed (base64 encoded in this case) into something more palatable before being returned to the client.

如果您正在使用COTS,那么您可以设置一个中间网关,在返回给客户端之前,在其中创建请求并将请求(在本例中编码为base64)转换为更容易接受的东西。

#3


0  

You could have the server return base64 text, rather than doing that encoding client side.

您可以让服务器返回base64文本,而不是在客户端进行编码。

For example, (in ASP.NET) a request to /ImageAsBase64.ashx?file=/images/myimage.png could be coded to read the file, base64encode it, and stream it as a response.

例如,(在ASP.NET中)对/ImageAsBase64.ashx?file=/images/myimage的请求。可以对png进行编码,以读取文件,对其进行base64encode,并将其作为响应进行流处理。

It's really going to be pretty much the same thing in PHP or whatever.

这在PHP中是一样的。