javascript 如何修改另一个函数接收到的 XMLHttpRequest 响应文本?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26447335/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I modify the XMLHttpRequest responsetext received by another function?
提问by Shadow
I am trying to modify the responseText received by a function that I cannot modify. This function creates a XMLHttpRequest that I can attach to, but I have been unable to "wrap" the responseText in a way that allows me to modify the content before the original function receives it.
我正在尝试修改我无法修改的函数接收到的 responseText。此函数创建了一个我可以附加到的 XMLHttpRequest,但是我无法以一种允许我在原始函数接收内容之前修改内容的方式“包装” responseText。
Here's the full original function:
这是完整的原始函数:
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
}
I have also tried to manipulate the reiceiving function k(); in an attempt to reach my goal, but since it doesn't depend on any data passing to the function (for example k(a.responseText);) I had no success.
我也试图操纵接收函数 k(); 试图达到我的目标,但由于它不依赖于传递给函数的任何数据(例如 k(a.responseText);),我没有成功。
Is there any way that I can achieve this? I do not wish to use js libraries (such as jQuery);
有什么办法可以实现这一目标吗?我不想使用 js 库(例如 jQuery);
EDIT: I understand that I cannot change .responseText directly since it is read-only, but I am trying to find a way to change the content between the response and receiving function.
编辑:我知道我不能直接更改 .responseText 因为它是只读的,但我试图找到一种方法来更改响应和接收函数之间的内容。
EDIT2: Added below one of the methods I have tried to intercept and change .responseText which has been addapted from here: Monkey patch XMLHTTPRequest.onreadystatechange
EDIT2:在我尝试拦截和更改从这里添加的 .responseText 的方法之一下面添加: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: I forgot to include that the functions Mj and ff are not globally available, they are both contained inside an anonymous function (function(){functions are here})();
EDIT3:我忘了包括函数 Mj 和 ff 不是全局可用的,它们都包含在匿名函数中(function(){functions are here})();
EDIT4: I have changed the accepted answer because AmmarCSE's does not have any of the problems and complexity linked to jfriend00's answer.
EDIT4:我已经更改了接受的答案,因为 AmmarCSE 没有任何与 jfriend00 的答案相关的问题和复杂性。
The best answer explained in short is as follows:
简而言之,最佳答案如下:
Listen to whichever request you want to modify (make sure your listener will intercept it before the original function destination does, otherwise there is no point in modifying it after the response has already been used).
侦听您要修改的任何请求(确保您的侦听器会在原始函数目标执行之前拦截它,否则在响应已被使用后修改它是没有意义的)。
Save the original response (if you want to modify it) in a temporary variable
将原始响应(如果要修改)保存在临时变量中
Change the property you want to modify to "writable: true", it will erase whichever value it had. In my case I use
将您要修改的属性更改为“writable: true”,它将擦除它所具有的任何值。在我的情况下,我使用
Object.defineProperty(event, 'responseText', {
writable: true
});
Where event
is the object returned by listening to the load
or readystatechange
event of the xhr request
event
监听xhr请求的load
orreadystatechange
事件返回的对象在哪里
Now you can set anything you want for your response, if all you wanted was to modify the original response then you can use that data from your temporary variable and then save the modifications in the response.
现在你可以为你的响应设置任何你想要的,如果你只想修改原始响应,那么你可以使用临时变量中的数据,然后将修改保存在响应中。
采纳答案by AmmarCSE
One very simple workaround is to change the property descriptor for responseText
itself
一种非常简单的解决方法是更改responseText
自身的属性描述符
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
So, you can extend XMLHttpRequest
like
所以,你可以XMLHttpRequest
像这样扩展
(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);
回答by jfriend00
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):
由于您无法修改任何这些函数,因此您似乎必须遵循 XMLHttpRequest 原型。这是一个想法(未经测试,但您可以看到方向):
(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.
这会为 XMLHttpRequestopen()
方法做一个猴子补丁,然后当它被异步请求调用时,它会为 onReadyStateChange 处理程序做一个猴子补丁,因为应该已经设置了。然后,在调用原始 onReadyStateChange 处理程序之前,该修补函数可以查看 responseText,以便为其分配不同的值。
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.
并且,最后因为它.responseText
是仅就绪的,所以在调用onreadystatechange
处理程序之前替换了一个虚拟的 XMLHttpResponse 对象。这不会在所有情况下都有效,但如果 onreadystatechange 处理程序用于this.responseText
获取响应,则会起作用。
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.
并且,这里尝试将 XMLHttpRequest 对象重新定义为我们自己的代理对象。因为它是我们自己的代理对象,所以我们可以将responseText
属性设置为我们想要的任何值。对于除 之外的所有其他属性onreadystatechange
,该对象只是将 get、set 或函数调用转发到真正的 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);}
});
});
}
})();
Working demo: http://jsfiddle.net/jfriend00/jws6g691/
工作演示: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.
我在最新版本的 IE、Firefox 和 Chrome 中尝试过它,它可以处理一个简单的 ajax 请求。
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).
注意:我还没有研究 Ajax 的所有高级方法(如二进制数据、上传等...),以查看该代理是否足够彻底以完成所有这些工作(我猜它可能还没有没有进一步的工作,但它适用于基本请求,所以看起来这个概念是有能力的)。
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.
尝试从 XMLHttpRequest 对象派生,然后用我自己的构造函数替换构造函数,但这不起作用,因为真正的 XMLHttpRequest 函数不会让您将其作为函数调用来初始化我的派生对象。
尝试覆盖
onreadystatechange
处理程序并更改.responseText
,但该字段是只读的,因此您无法更改它。尝试创建一个
this
在调用时作为对象发送的虚拟对象onreadystatechange
,但很多代码没有引用this
,而是将实际对象保存在闭包中的局部变量中 - 从而击败了虚拟对象。
回答by Jake
I needed to intercept and modify a request response so I came up with a little bit of code. I also found that some websites like to use response as well as the responseText which is why my code modifies both.
我需要拦截和修改请求响应,所以我想出了一些代码。我还发现一些网站喜欢使用 response 和 responseText 这就是为什么我的代码修改这两者。
The 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);
};
};
the first param of the intercept_response function is a regular expression to match the request url and the second param is the function to be used on the response to modify it.
intercept_response 函数的第一个参数是匹配请求 url 的正则表达式,第二个参数是用于修改响应的函数。
Example Of Usage
使用示例
intercept_response(/fruit\.json/i, function(response) {
var new_response = response.replace('banana', 'apple');
return new_response;
});
回答by MT0
By request I include below an example snippet showing how to modify the response of a XMLHttpRequest before the original function can receive it.
根据请求,我在下面包含了一个示例片段,展示了如何在原始函数接收之前修改 XMLHttpRequest 的响应。
// 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();
回答by MT0
You can wrap the getter for responseText
in the prototype with a new function and make the changes to the output there.
您可以responseText
使用新函数将 getter for 包装在原型中,并对那里的输出进行更改。
Here is a simple example that appends the html comment <!-- TEST -->
to the response text:
这是一个将 html 注释附加<!-- TEST -->
到响应文本的简单示例:
(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);
The above function will change the response text for all requests.
上述函数将更改所有请求的响应文本。
If you want to make the change to just one request then do not use the function above but just define the getter on the individual request instead:
如果您只想对一个请求进行更改,则不要使用上面的函数,而只需在单个请求上定义 getter:
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();
回答by user3717718
I ran into the same problem when I was making a Chrome extension to allow cross origin API calls. This worked in Chrome. (Update: It doesn't work in the newest Chrome version).
我在制作 Chrome 扩展以允许跨源 API 调用时遇到了同样的问题。这在 Chrome 中有效。(更新:它在最新的 Chrome 版本中不起作用)。
delete _this.responseText;
_this.responseText = "Anything you want";
The snippet runs inside a monkeypatched XMLHttpRequest.prototype.send who is redirecting the requests to the extensions background script and replace all the properties on response. Like this:
该代码段在一个monkeypatched XMLHttpRequest.prototype.send 中运行,它正在将请求重定向到扩展后台脚本并替换响应中的所有属性。像这样:
// 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;
That didn't work in Firefox, but I found a solution that worked:
这在 Firefox 中不起作用,但我找到了一个有效的解决方案:
var test = new XMLHttpRequest();
Object.defineProperty(test, 'responseText', {
configurable: true,
writable: true,
});
test.responseText = "Hey";
That doesn't work in Chrome, but this work in both Chrome and Firefox:
这在 Chrome 中不起作用,但在 Chrome 和 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";
The last was copy past from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
最后一个是从https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty复制过去的
None of the solutions works in Safari. I tried to make a new XMLHttpRequest with writable properties, but it was not allowed to call open or send from it. I also tried this solution: https://stackoverflow.com/a/28513219/3717718. Unfortunately it produced the same error in Safari:
所有解决方案都不适用于 Safari。我试图创建一个具有可写属性的新 XMLHttpRequest,但不允许调用 open 或从中发送。我也试过这个解决方案:https: //stackoverflow.com/a/28513219/3717718。不幸的是,它在 Safari 中产生了同样的错误:
TypeError: Attempting to configurable attribute of unconfigurable property.
类型错误:正在尝试使用不可配置属性的可配置属性。
回答by CBHacking
First-class function variables are wonderful things! function f() {a; b; c; }
is exactly the same thing as var f = function () {a; b; c; }
This means you can redefine functions as needed. You want to wrap the function Mj
to return a modified object? No problem. The fact that the responseText
field is read-only is a pain, but if that's the only field you need...
一流的函数变量真是好东西!function f() {a; b; c; }
与var f = function () {a; b; c; }
这完全相同这意味着您可以根据需要重新定义函数。您想包装函数Mj
以返回修改后的对象吗?没问题。该responseText
字段是只读的这一事实很痛苦,但如果这是您唯一需要的字段......
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;
}
Now, when your page code calls Mj(), it will invoke your wrapper instead (which will still call the original Mj internally, of course).
现在,当您的页面代码调用 Mj() 时,它将改为调用您的包装器(当然,它仍会在内部调用原始 Mj)。