web-dev-qa-db-fra.com

Comment charger des données d'images binaires à l'aide de Javascript et XMLHttpRequest?

J'essayais de charger un côté client d'image et de coder en base64 les octets renvoyés par le serveur afin de le faire passer pour effectuer un traitement. IE a une propriété RequestBody de l'objet XMLHttpRequest, mais je n'arrive pas à l'utiliser et RequestText est tronqué. Dans Firefox, RequestText est là, mais semble corrompu.

25
Emil Lerch

Voici comment je l'ai fait.

Cette technique est fournie dans une réponse à une autre SO question, mais elle est également pertinente ici.

Je ne voulais rien encoder en base64. Je voulais télécharger et analyser des fichiers binaires dans le navigateur via Javascript, sans modifier le serveur pour les encoder spécialement. J'ai trouvé que dans Firefox, en forçant le mimetype de la réponse via overrideMimeType(), je pouvais utiliser XMLHttpRequest.responseText. Sur IE, c'est différent parce que:

  • responseText on IE tronque au premier zéro. Pour les flux binaires, c'est un gros problème.

  • il n'y a pas de XMLHttpRequest.overrideMimeType(), pour forcer IE pour traiter les flux binaires comme du texte.

  • alors qu'il existe un XMLHttpRequest.responseBody (IE uniquement!) spécifiquement conçu pour être utilisé avec des flux de données binaires, la propriété n'est pas exaspérante à partir de Javascript.

Par conséquent, le besoin est de convertir la propriété responseBody d'IE en une chose qui ressemble à responseText de FireFox, avec la contrainte de type mime. Cela est possible en utilisant VBScript injecté.

Pour le rendre multi-navigateur, vous devez simplement emballer la logique spécifique au navigateur dans un conditionnel. Voici ce que j'ai utilisé:

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

... puis appelez readByte(i) pour obtenir l'octet à la position ith dans le fichier binaire.

Bonne chance.

Crédit à Miskun pour la logique de conversion VBScript.

12
Cheeso

Si vous utilisez COTS, vous pouvez toujours configurer une passerelle intermédiaire dans laquelle la demande est effectuée et transformée (codée en base64 dans ce cas) en quelque chose de plus acceptable avant d'être renvoyée au client.

1
Justin Johnson

Vous pouvez demander au serveur de renvoyer le texte base64, plutôt que de faire cet encodage côté client.

Par exemple, (dans ASP.NET), une demande à /ImageAsBase64.ashx?file=/images/myimage.png pourrait être codée pour lire le fichier, le coder en base64 et le diffuser en tant que réponse.

Ce sera vraiment à peu près la même chose dans PHP ou autre).

0
Clyde