如何修改另一个函数接收的XMLHttpRequest响应文本?

Sha*_*dow 17 javascript ajax monkeypatching xmlhttprequest

我试图修改由我无法修改的函数收到的responseText.这个函数创建了一个我可以附加的XMLHttpRequest,但是我无法以允许我在原始函数接收内容之前修改内容的方式"包装"responseText.

这是完整的原始功能:

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
}
Run Code Online (Sandbox Code Playgroud)

我也试图操纵reiceiving函数k(); 试图达到我的目标,但因为它不依赖于传递给函数的任何数据(例如k(a.responseText);)我没有成功.

有什么方法可以实现这个目标吗?我不想使用js库(比如jQuery);


编辑:我知道我不能直接更改.responseText,因为它是只读的,但我试图找到一种方法来改变响应和接收函数之间的内容.


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);
Run Code Online (Sandbox Code Playgroud)

编辑3:我忘了包含函数Mj和ff不是全局可用的,它们都包含在一个匿名函数中(function(){functions are here})();


编辑4:我已经改变了接受的答案,因为AmmarCSE没有任何与jfriend00的答案相关的问题和复杂性.

简要解释的最佳答案如下:

聆听您想要修改的任何请求(确保您的侦听器将在原始函数目标之前拦截它,否则在已经使用响应之后修改它没有意义).

在临时变量中保存原始响应(如果要修改它)

将要修改的属性更改为"writable:true",它将删除它具有的任何值.在我的情况下,我使用

Object.defineProperty(event, 'responseText', {
    writable: true
});
Run Code Online (Sandbox Code Playgroud)

event通过监听xhr请求的事件loadreadystatechange事件返回的对象在哪里

现在,您可以为响应设置任何内容,如果您只想修改原始响应,则可以使用临时变量中的数据,然后将修改保存在响应中.

jfr*_*d00 15

编辑:请参阅下面的第二个代码选项(已经过测试并且可以使用).第一个有一些限制.


由于您无法修改任何这些函数,因此您必须先了解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);
    }

})();
Run Code Online (Sandbox Code Playgroud)

这为XMLHttpRequest open()方法做了一个猴子补丁,然后当为异步请求调用它时,它为onReadyStateChange处理程序做了一个猴子补丁,因为它应该已经设置好了.然后,该补丁函数在调用原始onReadyStateChange处理程序之前查看responseText,以便为其分配不同的值.

而且,最后因为.responseText只是ready-only,它在调用onreadystatechange处理程序之前替换了一个伪XMLHttpResponse对象.这在所有情况下都不起作用,但如果onreadystatechange处理程序用于this.responseText获取响应,则会起作用.


而且,这是尝试将XMLHttpRequest对象重新定义为我们自己的代理对象.因为它是我们自己的代理对象,所以我们可以将responseText属性设置为我们想要的任何内容.对于除此之外的所有其他属性onreadystatechange,此对象只是将get,set或function调用转发给真正的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);}
            });
        });
    }
})();
Run Code Online (Sandbox Code Playgroud)

工作演示:http://jsfiddle.net/jfriend00/jws6g691/

我在IE,Firefox和Chrome的最新版本中尝试了它,它使用了一个简单的ajax请求.

注意:我没有研究过Ajax(如二进制数据,上传等等)的所有高级方法,可以看到这个代理足够彻底,可以使所有这些工作(我猜它可能还没有)没有一些进一步的工作,但它正在为基本请求工作,所以看起来这个概念是有能力的).


其他失败的尝试:

  1. 试图从XMLHttpRequest对象派生,然后用我自己的构造函数替换构造函数,但这不起作用,因为真正的XMLHttpRequest函数不允许您将其称为初始化派生对象的函数.

  2. 试图覆盖onreadystatechange处理程序并更改.responseText,但该字段是只读的,因此您无法更改它.

  3. 尝试创建一个this在调用时作为对象发送的虚拟对象onreadystatechange,但是很多代码没有引用this,而是将实际对象保存在闭包中的局部变量中 - 从而击败了虚拟对象.


Amm*_*CSE 10

一个非常简单的解决方法是更改responseText自身的属性描述符

Object.defineProperty(wrapped, 'responseText', {
     writable: true
});
Run Code Online (Sandbox Code Playgroud)

所以,你可以扩展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);
Run Code Online (Sandbox Code Playgroud)

演示


MT0*_*MT0 6

根据请求,我在下面添加了一个示例片段,展示了如何在原始函数接收 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();
Run Code Online (Sandbox Code Playgroud)


MT0*_*MT0 6

您可以使用新函数将 getter for 包装responseText在原型中,并对其中的输出进行更改。

下面是一个将 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);
Run Code Online (Sandbox Code Playgroud)

上述函数将更改所有请求的响应文本。

如果您只想更改一个请求,则不要使用上面的函数,而只需在单个请求上定义 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();
Run Code Online (Sandbox Code Playgroud)


Jak*_*ake 6

我需要拦截和修改请求响应,所以我想出了一些代码。我还发现有些网站喜欢使用response以及responseText,这就是为什么我的代码同时修改了两者。

代码

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);
   };
};
Run Code Online (Sandbox Code Playgroud)

Intercept_response函数的第一个参数是匹配请求url的正则表达式,第二个参数是在响应上用于对其进行修改的函数。

使用示例

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