web-dev-qa-db-fra.com

Comment modifier le responsetext XMLHttpRequest reçu par une autre fonction?

J'essaie de modifier le responseText reçu par une fonction que je ne peux pas modifier. Cette fonction crée un XMLHttpRequest que je peux attacher, mais je n’ai pas pu "encapsuler" le responseText de façon à ce que je puisse modifier le contenu avant que la fonction d’origine ne le reçoive.

Voici la fonction originale complète:

function Mj(a, b, c, d, e) {
    function k() {
        4 == (m && 'readyState' in m ? m.readyState : 0) && b && ff(b) (m)
    }
    var m = new XMLHttpRequest;
    'onloadend' in m ? m.addEventListener('loadend', k, !1)  : m.onreadystatechange = k;
    c = ('GET').toUpperCase();
    d = d || '';
    m.open(c, a, !0);
    m.send(d);
    return m
}
function ff(a) {
    return a && window ? function () {
        try {
            return a.apply(this, arguments)
        } catch(b) {
            throw jf(b),
                b;
        }
    } : a
}

J'ai aussi essayé de manipuler la fonction de reiceiving k (); pour tenter d’atteindre mon objectif, mais comme cela ne dépend pas de la transmission de données à la fonction (par exemple, k (a.responseText);), j’ai échoué.

Y at-il un moyen que je puisse y parvenir? Je ne souhaite pas utiliser les bibliothèques js (telles que jQuery);


EDIT: Je comprends que je ne peux pas changer directement le fichier .responseText car il est en lecture seule, mais j’essaie de trouver un moyen de changer le contenu entre la réponse et la fonction de réception.


EDIT2 : Ajouté ci-dessous l’une des méthodes que j’ai essayé d’intercepter et de modifier .responseText et qui a été adaptée à partir de cet emplacement: Patch de singe XMLHTTPRequest.onreadystatechange

(function (open) {
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
    if(/results/.test(url)) {
      console.log(this.onreadystatechange);
        this.addEventListener("readystatechange", function () {
            console.log('readystate: ' + this.readyState);
            if(this.responseText !== '') {
                this.responseText = this.responseText.split('&')[0];
            }
        }, false);
    }
    open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest.prototype.open);

EDIT3 : J'ai oublié d'inclure que les fonctions Mj et ff ne sont pas globalement disponibles, elles sont toutes deux contenues dans une fonction anonyme (function () {les fonctions sont ici}) ();


EDIT4 : J'ai changé la réponse acceptée car AmmarCSE ne présente aucun des problèmes et de la complexité liés à la réponse de jfriend00.

La meilleure réponse expliquée en bref est la suivante:

Écoutez la demande que vous souhaitez modifier (assurez-vous que votre auditeur l'interceptera avant la destination de la fonction d'origine, sinon il ne sert à rien de la modifier après que la réponse a déjà été utilisée).

Enregistrez la réponse d'origine (si vous souhaitez la modifier) ​​dans une variable temporaire

Changez la propriété que vous voulez modifier en "accessible en écriture: true", cela effacera n'importe quelle valeur qu'il avait. Dans mon cas j'utilise

Object.defineProperty(event, 'responseText', {
    writable: true
});

event est l'objet renvoyé en écoutant l'événement load ou readystatechange de la demande xhr

Vous pouvez maintenant définir tout ce que vous voulez pour votre réponse. Si vous ne vouliez que modifier la réponse d'origine, vous pouvez utiliser les données de votre variable temporaire, puis enregistrer les modifications dans la réponse.

15
Shadow

Une solution très simple consiste à modifier le descripteur de propriété pour responseText lui-même.

Object.defineProperty(wrapped, 'responseText', {
     writable: true
});

Donc, vous pouvez prolonger XMLHttpRequest comme

(function(proxied) {
    XMLHttpRequest = function() {
        //cannot use apply directly since we want a 'new' version
        var wrapped = new(Function.prototype.bind.apply(proxied, arguments));

        Object.defineProperty(wrapped, 'responseText', {
            writable: true
        });

        return wrapped;
    };
})(XMLHttpRequest);

Demo

9
AmmarCSE

Edit: Voir la deuxième option de code ci-dessous (elle est testée et fonctionne). Le premier a des limites.


Puisque vous ne pouvez pas modifier l'une de ces fonctions, il semble que vous devez aller après le prototype XMLHttpRequest. Voici une idée (non testée, mais vous pouvez voir la direction):

(function() {
    var open = XMLHttpRequest.prototype.open;

    XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
        var oldReady;
        if (async) {   
            oldReady = this.onreadystatechange;
            // override onReadyStateChange
            this.onreadystatechange = function() {
                if (this.readyState == 4) {
                    // this.responseText is the ajax result
                    // create a dummay ajax object so we can modify responseText
                    var self = this;
                    var dummy = {};
                    ["statusText", "status", "readyState", "responseType"].forEach(function(item) {
                        dummy[item] = self[item];
                    });
                    dummy.responseText = '{"msg": "Hello"}';
                    return oldReady.call(dummy);
                } else {
                    // call original onreadystatechange handler
                    return oldReady.apply(this, arguments);
                }
            }
        } 
        // call original open method
        return open.apply(this, arguments);
    }

})();

Cela fait un patch singe pour la méthode open() XMLHttpRequest et lorsque cela est appelé à une demande async, il fait un patch de singe pour le gestionnaire onReadyStateChange depuis que devrait déjà. Cette fonction patché obtient alors pour voir le responseText avant que le gestionnaire de onReadyStateChange original est appelé donc il peut attribuer une valeur différente à elle.

Et, enfin parce que .responseText est prêt seulement, ce substitue un objet factice XMLHttpResponse avant d'appeler le gestionnaire de onreadystatechange. Cela ne fonctionnerait pas dans tous les cas, mais ne fonctionnera que si le gestionnaire onreadystatechange this.responseText utilise pour obtenir la réponse.


Et, voici une tentative qui redéfinit l'objet XMLHttpRequest pour notre propre objet proxy. Parce qu'il est notre propre objet proxy, nous pouvons définir la propriété responseText à ce que nous voulons. Pour toutes les autres propriétés, à l'exception onreadystatechange, cet objet transmet seulement l'obtenir, ou la fonction d'établissement d'appel à l'objet réel XMLHttpRequest.

(function() {
    // create XMLHttpRequest proxy object
    var oldXMLHttpRequest = XMLHttpRequest;

    // define constructor for my proxy object
    XMLHttpRequest = function() {
        var actual = new oldXMLHttpRequest();
        var self = this;

        this.onreadystatechange = null;

        // this is the actual handler on the real XMLHttpRequest object
        actual.onreadystatechange = function() {
            if (this.readyState == 4) {
                // actual.responseText is the ajax result

                // add your own code here to read the real ajax result
                // from actual.responseText and then put whatever result you want
                // the caller to see in self.responseText
                // this next line of code is a dummy line to be replaced
                self.responseText = '{"msg": "Hello"}';
            }
            if (self.onreadystatechange) {
                return self.onreadystatechange();
            }
        };

        // add all proxy getters
        ["status", "statusText", "responseType", "response",
         "readyState", "responseXML", "upload"].forEach(function(item) {
            Object.defineProperty(self, item, {
                get: function() {return actual[item];}
            });
        });

        // add all proxy getters/setters
        ["ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) {
            Object.defineProperty(self, item, {
                get: function() {return actual[item];},
                set: function(val) {actual[item] = val;}
            });
        });

        // add all pure proxy pass-through methods
        ["addEventListener", "send", "open", "abort", "getAllResponseHeaders",
         "getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) {
            Object.defineProperty(self, item, {
                value: function() {return actual[item].apply(actual, arguments);}
            });
        });
    }
})();

Démo de travail: http://jsfiddle.net/jfriend00/jws6g691/

Je l'ai essayé dans les dernières versions de IE, Firefox et Chrome et cela a fonctionné avec une simple demande ajax.

Remarque: je n’ai pas examiné toutes les méthodes avancées utilisées par Ajax (telles que les données binaires, les téléchargements, etc.) pour vérifier que ce proxy est suffisamment complet pour permettre son bon fonctionnement (je suppose que ce n’est peut-être pas encore le cas). sans un travail plus loin, mais il travaille pour les demandes de base il semble que le concept est capable).


Autres tentatives qui ont échoué:

  1. J'ai essayé de dériver de l'objet XMLHttpRequest, puis de remplacer le constructeur par le mien, mais cela n'a pas fonctionné car la fonction XMLHttpRequest réelle ne vous permet pas de l'appeler comme une fonction permettant d'initialiser mon objet dérivé.

  2. Juste Essayé remplaçant le gestionnaire de onreadystatechange et changer .responseText, mais ce champ est en lecture seule et vous ne pouvez pas le changer.

  3. J'ai essayé de créer un objet factice envoyé en tant qu'objet this lors de l'appel de onreadystatechange, mais une grande partie du code ne fait pas référence à this, mais a plutôt l'objet réel enregistré dans une variable locale dans une fermeture, supprimant ainsi l'objet factice.

15
jfriend00

J'avais besoin d'intercepter et de modifier une réponse à une requête, alors je suis arrivé avec un peu de code. J'ai également constaté que certains sites Web aiment utiliser la réponse ainsi que le responseText, raison pour laquelle mon code modifie les deux.

Le code

var open_prototype = XMLHttpRequest.prototype.open,
intercept_response = function(urlpattern, callback) {
   XMLHttpRequest.prototype.open = function() {
      arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function(event) {
         if ( this.readyState === 4 ) {
            var response = callback(event.target.responseText);
            Object.defineProperty(this, 'response',     {writable: true});
            Object.defineProperty(this, 'responseText', {writable: true});
            this.response = this.responseText = response;
         }
      });
      return open_prototype.apply(this, arguments);
   };
};

le premier paramètre de la fonction intercept_response est une expression régulière pour correspondre à l'URL de la demande et le deuxième paramètre est la fonction à utiliser dans la réponse pour la modifier.

Exemple d'utilisation

intercept_response(/fruit\.json/i, function(response) {
   var new_response = response.replace('banana', 'Apple');
   return new_response;
});
4
Jake

Vous pouvez encapsuler le getter pour responseText dans le prototype avec une nouvelle fonction et y apporter les modifications.

Voici un exemple simple qui ajoute le commentaire html <!-- TEST --> au texte de la réponse:

(function(http){
  var get = Object.getOwnPropertyDescriptor(
    http.prototype,
    'responseText'
  ).get;

  Object.defineProperty(
    http.prototype,
    "responseText",
    {
      get: function(){ return get.apply( this, arguments ) + "<!-- TEST -->"; }
    }
  );
})(self.XMLHttpRequest);

La fonction ci-dessus modifiera le texte de réponse pour toutes les demandes.

Si vous souhaitez modifier une seule demande, n'utilisez pas la fonction ci-dessus, mais définissez simplement le getter sur la demande individuelle:

var req = new XMLHttpRequest();
var get = Object.getOwnPropertyDescriptor(
  XMLHttpRequest.prototype,
  'responseText'
).get;
Object.defineProperty(
  req,
  "responseText", {
    get: function() {
      return get.apply(this, arguments) + "<!-- TEST -->";
    }
  }
);
var url = '/';
req.open('GET', url);
req.addEventListener(
  "load",
   function(){
     console.log(req.responseText);
   }
);
req.send();
3
MT0

Je rencontrais le même problème lorsque je créais une extension Chrome pour autoriser les appels inter-API croisés. Cela a fonctionné dans Chrome. (Mise à jour: cela ne fonctionne pas dans la dernière version de Chrome).

delete _this.responseText;
_this.responseText = "Anything you want";

Le fragment de code s'exécute dans un XMLHttpRequest.prototype.send monkeypatched qui redirige les demandes vers le script d'arrière-plan des extensions et remplace toutes les propriétés de la réponse. Comme ça:

// Delete removes the read only restriction
delete _this.response;
_this.response = event.data.response.xhr.response;
delete _this.responseText;
_this.responseText = event.data.response.xhr.responseText;
delete _this.status;
_this.status = event.data.response.xhr.status;
delete _this.statusText;
_this.statusText = event.data.response.xhr.statusText;
delete _this.readyState;
_this.readyState = event.data.response.xhr.readyState;

Cela ne fonctionnait pas dans Firefox, mais j'ai trouvé une solution qui fonctionnait:

var test = new XMLHttpRequest();
Object.defineProperty(test, 'responseText', {
  configurable: true,
  writable: true,
});

test.responseText = "Hey";

Cela ne fonctionne pas dans Chrome, mais cela fonctionne à la fois dans Chrome et Firefox:

var test = new XMLHttpRequest();
var aValue;
Object.defineProperty(test, 'responseText', {
  get: function() { return aValue; },
  set: function(newValue) { aValue = newValue; },
  enumerable: true,
  configurable: true
});

test.responseText = "Hey";

La dernière copie est passée de https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Aucune des solutions ne fonctionne dans Safari. J'ai essayé de créer un nouveau XMLHttpRequest avec des propriétés accessibles en écriture, mais il n'était pas autorisé à appeler open ou à en envoyer. J'ai aussi essayé cette solution: https://stackoverflow.com/a/28513219/3717718 . Malheureusement, la même erreur s'est produite dans Safari:

TypeError: tentative d'attribut configurable de propriété non configurable.

3
user3717718

Par requête, j'inclus ci-dessous un exemple de code indiquant comment modifier la réponse d'un XMLHttpRequest avant que la fonction d'origine ne puisse le recevoir.

// In this example the sample response should be
// {"data_sample":"data has not been modified"}
// and we will change it into
// {"data_sample":"woops! All data has gone!"}

/*---BEGIN HACK---------------------------------------------------------------*/

// here we will modify the response
function modifyResponse(response) {

    var original_response, modified_response;

    if (this.readyState === 4) {

        // we need to store the original response before any modifications
        // because the next step will erase everything it had
        original_response = response.target.responseText;

        // here we "kill" the response property of this request
        // and we set it to writable
        Object.defineProperty(this, "responseText", {writable: true});

        // now we can make our modifications and save them in our new property
        modified_response = JSON.parse(original_response);
        modified_response.data_sample = "woops! All data has gone!";
        this.responseText = JSON.stringify(modified_response);

    }
}

// here we listen to all requests being opened
function openBypass(original_function) {

    return function(method, url, async) {

        // here we listen to the same request the "original" code made
        // before it can listen to it, this guarantees that
        // any response it receives will pass through our modifier
        // function before reaching the "original" code
        this.addEventListener("readystatechange", modifyResponse);

        // here we return everything original_function might
        // return so nothing breaks
        return original_function.apply(this, arguments);

    };

}

// here we override the default .open method so that
// we can listen and modify the request before the original function get its
XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open);
// to see the original response just remove/comment the line above

/*---END HACK-----------------------------------------------------------------*/

// here we have the "original" code receiving the responses
// that we want to modify
function logResponse(response) {

    if (this.readyState === 4) {

        document.write(response.target.responseText);

    }

}

// here is a common request
var _request = new XMLHttpRequest();
_request.open("GET", "https://Gist.githubusercontent.com/anonymous/c655b533b340791c5d49f67c373f53d2/raw/cb6159a19dca9b55a6c97d3a35a32979ee298085/data.json", true);
_request.addEventListener("readystatechange", logResponse);
_request.send();

3
MT0

Les variables de fonction de première classe sont des choses merveilleuses! function f() {a; b; c; } est exactement la même chose que var f = function () {a; b; c; } Cela signifie que vous pouvez redéfinir les fonctions selon vos besoins. Vous voulez envelopper la fonction Mj pour renvoyer un objet modifié? Aucun problème. Le fait que le champ responseText soit en lecture seule est un problème, mais si c'est le seul champ dont vous avez besoin ...

var Mj_backup = Mj; // Keep a copy of it, unless you want to re-implement it (which you could do)
Mj = function (a, b, c, d, e) { // To wrap the old Mj function, we need its args
    var retval = Mj_backup(a,b,c,d,e); // Call the original function, and store its ret value
    var retmod; // This is the value you'll actually return. Not a true XHR, just mimics one
    retmod.responseText = retval.responseText; // Repeat for any other required properties
    return retmod;
}

Maintenant, lorsque votre code de page appelle Mj (), il appellera votre wrapper à la place (qui appellera toujours le Mj d'origine en interne, bien sûr).

1
CBHacking