it-swarm.com.de

Wie kann ich den von einer anderen Funktion empfangenen XMLHttpRequest-Antworttext ändern?

Ich versuche, den von einer Funktion empfangenen responseText zu ändern, den ich nicht ändern kann. Diese Funktion erstellt eine XMLHttpRequest, die ich anhängen kann, aber ich konnte den responseText nicht so umbrechen, dass ich den Inhalt ändern kann, bevor die ursprüngliche Funktion ihn empfängt.

Hier ist die volle ursprüngliche Funktion:

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
}

Ich habe auch versucht, die Reiceiving-Funktion k () zu manipulieren; Bei dem Versuch, mein Ziel zu erreichen, aber da es nicht darauf ankommt, dass Daten an die Funktion übergeben werden (z. B. k (a.responseText);), hatte ich keinen Erfolg.

Kann ich das auf irgendeine Weise erreichen? Ich möchte keine js-Bibliotheken (wie jQuery) verwenden.


EDIT: Ich verstehe, dass ich .responseText nicht direkt ändern kann, da es schreibgeschützt ist, aber ich versuche, eine Möglichkeit zu finden, den Inhalt zwischen der Antwort- und der Empfangsfunktion zu ändern.


EDIT2 : Hinzugefügt unter einer der Methoden, die ich abzufangen und zu ändern versucht habe .responseText, die von hier angepasst wurde: 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 : Ich habe vergessen einzuschließen, dass die Funktionen Mj und ff nicht global verfügbar sind, sondern beide in einer anonymen Funktion enthalten sind (function () {functions are here}) ();


EDIT4 : Ich habe die akzeptierte Antwort geändert, da AmmarCSE keine Probleme und Komplexität aufweist, die mit der Antwort von jfriend00 zusammenhängen.

Die beste Antwort lautet wie folgt:

Hören Sie sich an, welche Anforderung Sie ändern möchten (stellen Sie sicher, dass Ihr Listener sie abfängt, bevor das ursprüngliche Funktionsziel verwendet wird, da es sonst keinen Sinn macht, sie zu ändern, nachdem die Antwort bereits verwendet wurde).

Speichern Sie die ursprüngliche Antwort (falls Sie sie ändern möchten) in einer temporären Variablen

Ändern Sie die Eigenschaft, die Sie ändern möchten, in "writable: true". Dadurch wird der jeweils vorhandene Wert gelöscht. In meinem Fall benutze ich

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

Wobei event das Objekt ist, das durch Abhören des Ereignisses load oder readystatechange der xhr-Anforderung zurückgegeben wird

Jetzt können Sie alles für Ihre Antwort festlegen. Wenn Sie lediglich die ursprüngliche Antwort ändern möchten, können Sie diese Daten aus Ihrer temporären Variablen verwenden und die Änderungen in der Antwort speichern.

15
Shadow

Eine sehr einfache Problemumgehung besteht darin, die Eigenschaftsbeschreibung für responseText selbst zu ändern

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

Sie können also XMLHttpRequest gerne erweitern

(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

Bearbeiten: Siehe die zweite Code-Option unten (sie wurde getestet und funktioniert). Der erste hat einige Einschränkungen.


Da Sie keine dieser Funktionen ändern können, müssen Sie dem XMLHttpRequest-Prototyp folgen. Hier ist eine Idee (ungetestet, aber Sie können die Richtung sehen):

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

})();

Dies führt zu einem Affen-Patch für die XMLHttpRequest open()-Methode. Wenn dies für eine async-Anforderung aufgerufen wird, führt es einen Affen-Patch für den Handler onReadyStateChange aus, da dieser bereits festgelegt sein sollte. Diese gepatchte Funktion sieht dann den responseText, bevor der ursprüngliche onReadyStateChange-Handler aufgerufen wird, sodass er ihm einen anderen Wert zuweisen kann.

Und schließlich, da .responseText bereit ist, ersetzt dies ein Dummy-Objekt XMLHttpResponse, bevor der onreadystatechange-Handler aufgerufen wird. Dies würde nicht in allen Fällen funktionieren, funktioniert jedoch, wenn der Handler onreadystatechange this.responseText verwendet, um die Antwort zu erhalten.


Und hier ist ein Versuch, der das XMLHttpRequest-Objekt als unser eigenes Proxy-Objekt definiert. Da es sich um unser eigenes Proxy-Objekt handelt, können wir die responseText-Eigenschaft auf das setzen, was wir möchten. Bei allen anderen Eigenschaften mit Ausnahme von onreadystatechange leitet das Objekt den Aufruf "get", "set" oder "function" einfach an das echte XMLHttpRequest-Objekt weiter.

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

Arbeitsdemo: http://jsfiddle.net/jfriend00/jws6g691/

Ich habe es in den neuesten Versionen von IE, Firefox und Chrome ausprobiert und es funktionierte mit einer einfachen Ajax-Anfrage.

Hinweis: Ich habe nicht all die fortgeschrittenen Möglichkeiten untersucht, die Ajax (wie Binärdaten, Uploads usw.) verwenden kann, um zu sehen, dass dieser Proxy gründlich genug ist, um alle diese Funktionen auszuführen (ich denke, dass dies noch nicht der Fall ist.) ohne einige weitere Arbeit, aber es funktioniert für grundlegende Anforderungen, so dass es aussieht, als wäre das Konzept fähig).


Andere Versuche, die fehlgeschlagen sind:

  1. Es wurde versucht, vom XMLHttpRequest-Objekt abzuleiten und den Konstruktor dann durch meinen eigenen zu ersetzen. Dies funktionierte jedoch nicht, da die eigentliche XMLHttpRequest-Funktion es nicht zulässt, dass Sie es als eine Funktion zum Initialisieren meines abgeleiteten Objekts aufrufen.

  2. Es wurde versucht, den Handler onreadystatechange zu überschreiben und .responseText zu ändern. Dieses Feld ist jedoch schreibgeschützt, sodass Sie es nicht ändern können.

  3. Es wurde versucht, ein Dummy-Objekt zu erstellen, das beim Aufruf von this als onreadystatechange-Objekt gesendet wird. Viel Code verweist jedoch nicht auf this, sondern das eigentliche Objekt wird in einer lokalen Variablen in einer Schließung gespeichert. Dadurch wird das Dummy-Objekt vereitelt.

15
jfriend00

Ich musste eine Anforderungsantwort abfangen und modifizieren, so dass ich etwas Code bekam. Ich habe auch festgestellt, dass einige Websites gerne sowohl Response als auch ResponseText verwenden, weshalb mein Code beide modifiziert.

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

der erste Parameter der intercept_response-Funktion ist ein regulärer Ausdruck, der mit der Anforderungs-URL übereinstimmt, und der zweite Parameter ist die Funktion, die in der Antwort verwendet wird, um sie zu ändern.

Verwendungsbeispiel

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

Sie können den Getter für responseText im Prototyp mit einer neuen Funktion umschließen und dort die Änderungen an der Ausgabe vornehmen.

Hier ist ein einfaches Beispiel, das den HTML-Kommentar <!-- TEST --> an den Antworttext anfügt:

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

Die obige Funktion ändert den Antworttext für alle Anforderungen.

Wenn Sie die Änderung an nur einer Anfrage vornehmen möchten, verwenden Sie nicht die obige Funktion, sondern definieren Sie stattdessen den Getter für die einzelne Anfrage:

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

Ich hatte das gleiche Problem, als ich eine Chrome-Erweiterung erstellte, um Cross-Origin-API-Aufrufe zuzulassen. Das hat in Chrome funktioniert. (Update: Es funktioniert nicht in der neuesten Chrome-Version).

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

Das Snippet wird in einem monkeypatched XMLHttpRequest.prototype.send ausgeführt, der die Anforderungen an das Hintergrundskript der Erweiterungen umleitet und alle Eigenschaften für die Antwort ersetzt. So was:

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

Das hat in Firefox nicht funktioniert, aber ich habe eine Lösung gefunden, die funktioniert:

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

test.responseText = "Hey";

Das funktioniert nicht in Chrome, aber dies funktioniert sowohl in Chrome als auch in 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";

Das letzte war eine Kopie von https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Keine der Lösungen funktioniert in Safari. Ich habe versucht, ein neues XMLHttpRequest mit beschreibbaren Eigenschaften zu erstellen, aber es war nicht erlaubt, es aufzurufen oder zu senden. Ich habe auch diese Lösung ausprobiert: https://stackoverflow.com/a/28513219/3717718 . Leider hat es in Safari den gleichen Fehler gegeben:

TypeError: Versuch eines konfigurierbaren Attributs einer nicht konfigurierbaren Eigenschaft.

3
user3717718

Auf Anfrage füge ich unten ein Beispiel-Snippet hinzu, das zeigt, wie die Antwort eines XMLHttpRequest geändert werden kann, bevor die ursprüngliche Funktion sie empfangen kann.

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

Erstklassige Funktionsvariablen sind wunderbare Dinge! function f() {a; b; c; } ist genau dasselbe wie var f = function () {a; b; c; } Dies bedeutet, dass Sie Funktionen nach Bedarf neu definieren können. Sie möchten die Funktion Mj umschließen, um ein modifiziertes Objekt zurückzugeben? Kein Problem. Die Tatsache, dass das Feld responseText schreibgeschützt ist, ist ein Schmerz, aber wenn dies das einzige Feld ist, das Sie brauchen ...

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

Wenn Ihr Seitencode jetzt Mj () aufruft, ruft er stattdessen Ihren Wrapper auf (der den ursprünglichen Mj natürlich weiterhin intern aufruft).

1
CBHacking