Come posso modificare il responsext XMLHttpRequest ricevuto da un’altra funzione?

Sto cercando di modificare il responseTesto ricevuto da una funzione che non posso modificare. Questa funzione crea una XMLHttpRequest alla quale posso collegarmi, ma non sono stato in grado di “avvolgere” il responseText in un modo che mi permetta di modificare il contenuto prima che la funzione originale lo riceva.

Ecco la funzione originale completa:

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 } 

Ho anche provato a manipolare la funzione reiceiving k (); nel tentativo di raggiungere il mio objective, ma poiché non dipende da alcun dato che passa alla funzione (ad esempio k (a.responseText);) non ho avuto successo.

C’è un modo per raggiungerlo? Non voglio usare librerie js (come jQuery);


EDIT : Capisco che non posso cambiare .responseText direttamente dal momento che è di sola lettura, ma sto cercando di trovare un modo per modificare il contenuto tra la risposta e la funzione di ricezione.


EDIT2 : Aggiunto sotto uno dei metodi che ho provato ad intercettare e modificare .responseText che è stato addotto da qui: Monkey patch 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 : Ho dimenticato di includere che le funzioni Mj e ff non sono globalmente disponibili, sono entrambe contenute in una funzione anonima (function () {functions are here}) ();


EDIT4 : Ho cambiato la risposta accettata perché AmmarCSE non ha nessuno dei problemi e della complessità legati alla risposta di jfriend00.

La migliore risposta spiegata in breve è la seguente:

Ascolta la richiesta che desideri modificare (assicurati che l’ascoltatore la intercetti prima della destinazione della funzione originale, altrimenti non ha senso modificarla dopo che la risposta è già stata utilizzata).

Salva la risposta originale (se vuoi modificarla) in una variabile temporanea

Cambia la proprietà che vuoi modificare in “scrivibile: true”, cancellerà qualsiasi valore abbia. Nel mio caso io uso

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

Dove event è l’object restituito ascoltando l’evento load o readystatechange della richiesta xhr

Ora puoi impostare tutto ciò che vuoi per la tua risposta, se tutto ciò che volevi era modificare la risposta originale, allora puoi usare quei dati dalla tua variabile temporanea e poi salvare le modifiche nella risposta.

Una soluzione molto semplice consiste nel modificare il descrittore di proprietà per responseText stesso

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

Quindi, puoi estendere XMLHttpRequest come

 (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); 

dimostrazione

Modifica: vedi la seconda opzione di codice qui sotto (è testata e funziona). Il primo ha alcune limitazioni.


Dato che non è ansible modificare nessuna di queste funzioni, sembra che si debba andare dopo il prototipo XMLHttpRequest. Ecco un’idea (non testata, ma puoi vedere la direzione):

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

Questo fa una patch per scimmia per il metodo XMLHttpRequest open() e poi quando viene richiesta una richiesta asincrona, fa una patch scimmia per il gestore onReadyStateChange poiché dovrebbe essere già impostata. Quella funzione rattoppata viene quindi visualizzata per vedere il responseText prima che venga chiamato il gestore onReadyStateChange originale, in modo che possa assegnare ad esso un valore diverso.

Infine, poiché .responseText è solo pronto, questo sostituisce un object XMLHttpResponse fittizio prima di chiamare il gestore onreadystatechange . Ciò non funzionerebbe in tutti i casi, ma funzionerà se il gestore this.responseText utilizza this.responseText per ottenere la risposta.


E, ecco un tentativo che ridefinisce l’object XMLHttpRequest come il nostro object proxy. Poiché è il nostro object proxy, possiamo impostare la proprietà responseText su qualsiasi cosa vogliamo. Per tutte le altre proprietà tranne onreadystatechange , questo object inoltra semplicemente la chiamata get, set o function al vero object 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);} }); }); } })(); 

Demo funzionante: http://jsfiddle.net/jfriend00/jws6g691/

L’ho provato nelle ultime versioni di IE, Firefox e Chrome e ha funzionato con una semplice richiesta Ajax.

Nota: non ho esaminato tutti i metodi avanzati che Ajax (come dati binari, caricamenti, ecc.) Possono essere utilizzati per vedere che questo proxy è abbastanza approfondito da far funzionare tutti (suppongo che potrebbe non essere ancora senza ulteriore lavoro, ma sta lavorando per le richieste di base, quindi sembra che il concetto sia capace).


Altri tentativi falliti:

  1. Tentativo di derivare dall’object XMLHttpRequest e quindi sostituire il costruttore con il mio, ma questo non ha funzionato perché la funzione XMLHttpRequest non ti consente di chiamarla come una funzione per inizializzare il mio object derivato.

  2. onreadystatechange provato a onreadystatechange gestore onreadystatechange e a cambiare .responseText , ma quel campo è di sola lettura, quindi non puoi cambiarlo.

  3. Ho provato a creare un object fittizio che viene inviato come object quando si chiama onreadystatechange , ma un sacco di codice non fa riferimento a this , ma ha piuttosto l’object reale salvato in una variabile locale in una chiusura, quindi sconfigge l’object fittizio.

A richiesta includo sotto un frammento di esempio che mostra come modificare la risposta di un XMLHttpRequest prima che la funzione originale possa riceverlo.

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

Puoi racchiudere il getter per responseText nel prototipo con una nuova funzione e apportare le modifiche all’output.

Ecco un semplice esempio che aggiunge il commento html al testo della risposta:

 (function(http){ var get = Object.getOwnPropertyDescriptor( http.prototype, 'responseText' ).get; Object.defineProperty( http.prototype, "responseText", { get: function(){ return get.apply( this, arguments ) + ""; } } ); })(self.XMLHttpRequest); 

La funzione precedente cambierà il testo di risposta per tutte le richieste.

Se si desidera apportare la modifica a una sola richiesta, non utilizzare la funzione sopra, ma definire semplicemente il getter sulla singola richiesta:

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

Ho riscontrato lo stesso problema quando stavo creando un’estensione di Chrome per consentire chiamate API di origine incrociata. Questo ha funzionato in Chrome. (Aggiornamento: non funziona nella versione più recente di Chrome).

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

Lo snippet viene eseguito all’interno di un XMLHttpRequest.prototype.send monkeypatched che reindirizza le richieste allo script di background delle estensioni e sostituisce tutte le proprietà sulla risposta. Come questo:

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

Non funzionava con Firefox, ma ho trovato una soluzione che funzionava:

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

Questo non funziona in Chrome, ma funziona sia su Chrome che su 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"; 

L’ultima copia era passata da https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Nessuna delle soluzioni funziona in Safari. Ho provato a creare una nuova XMLHttpRequest con proprietà scrivibili, ma non mi è stato permesso di chiamare open o send da esso. Ho anche provato questa soluzione: https://stackoverflow.com/a/28513219/3717718 . Purtroppo ha prodotto lo stesso errore in Safari:

TypeError: Tentativo di attributo configurabile di proprietà non configurabile.

Avevo bisogno di intercettare e modificare la risposta di una richiesta, così ho trovato un po ‘di codice. Ho anche scoperto che alcuni siti web amano la risposta e il responseText, motivo per cui il mio codice modifica entrambi.

Il codice

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

il primo parametro della funzione intercept_response è un’espressione regolare che corrisponde all’URL della richiesta e il secondo param è la funzione da utilizzare nella risposta per modificarlo.

Esempio di utilizzo

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

Le variabili di funzione di prima class sono cose meravigliose! function f() {a; b; c; } function f() {a; b; c; } è esattamente la stessa cosa di var f = function () {a; b; c; } var f = function () {a; b; c; } var f = function () {a; b; c; } Ciò significa che è ansible ridefinire le funzioni secondo necessità. Vuoi avvolgere la funzione Mj per restituire un object modificato? Nessun problema. Il fatto che il campo responseText sia di sola lettura è un dolore, ma se questo è l’unico campo di cui hai bisogno …

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

Ora, quando il tuo codice di pagina chiama Mj (), invocherà invece il tuo wrapper (che chiamerà ancora internamente il Mj originale, ovviamente).