How can I modify the XMLHttpRequest responsetext received by another function?
Edit: See the second code option below (it is tested and works). The first one has some limitations.
Since you can't modify any of those functions, it appears you have to go after the XMLHttpRequest prototype. Here's one idea (untested, but you can see the 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);
}
})();
This does a monkey patch for the XMLHttpRequest open()
method and then when that is called for an async request, it does a monkey patch for the onReadyStateChange handler since that should already be set. That patched function then gets to see the responseText before the original onReadyStateChange handler is called so it can assign a different value to it.
And, finally because .responseText
is ready-only, this substitutes a dummy XMLHttpResponse object before calling the onreadystatechange
handler. This would not work in all cases, but will work if the onreadystatechange handler uses this.responseText
to get the response.
And, here's an attempt that redefines the XMLHttpRequest object to be our own proxy object. Because it's our own proxy object, we can set the responseText
property to whatever we want. For all other properties except onreadystatechange
, this object just forwards the get, set or function call to the real XMLHttpRequest object.
(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);}
});
});
}
})();
Working demo: http://jsfiddle.net/jfriend00/jws6g691/
I tried it in the latest versions of IE, Firefox and Chrome and it worked with a simple ajax request.
Note: I have not looked into all the advanced ways that Ajax (like binary data, uploads, etc...) can be used to see that this proxy is thorough enough to make all those work (I would guess it might not be yet without some further work, but it is working for basic requests so it looks like the concept is capable).
Other attempts that failed:
Tried to derive from the XMLHttpRequest object and then replace the constructor with my own, but that didn't work because the real XMLHttpRequest function won't let you call it as a function to initialize my derived object.
Tried just overriding the
onreadystatechange
handler and changing.responseText
, but that field is read-only so you can't change it.Tried creating a dummy object that is sent as the
this
object when callingonreadystatechange
, but a lot of code doesn't referencethis
, but rather has the actual object saved in a local variable in a closure - thus defeating the dummy object.
One very simple workaround is to change the property descriptor for responseText
itself
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
So, you can extend XMLHttpRequest
like
(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