XMLHttpRequest如何拦截请求发送和结果获取?

cute_girl
  • 1.4k

我想在发送body参数前捕获到请求并更改body参数,并在得到返回结果的时候拦截做一层处理,请问如何实现?

回复
阅读 914
6 个回答
✓ 已被采纳

很巧,我今年恰好解决了同样的问题。 我的场景是在不污染现有前端库的前提下,记录所有异步请求的耗时。 列出代码,仅供参考,完全能解决你的问题。不要被这么长的代码吓到,最核心的部分只有几行,大部分是我用来计算耗时用的。

(function(open, send) {
   window.timing = new Map();
   var xhrOpenRequestUrl;  // capture method and url
   var xhrSendResponseUrl; // capture request body
   var responseData;       // captured response

   XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
      xhrOpenRequestUrl = new URL(url, document.location.href).href;
      window.timing[xhrOpenRequestUrl] = {
        begin: new Date().valueOf(),
        method: method
      }

      open.apply(this, arguments); 
   };

   XMLHttpRequest.prototype.send = function(data) {
       this.addEventListener('readystatechange', function() {
          var gaPageUrl=document.location.href;
          var gaTitle=document.title;
          if (window.timing[this.responseURL] && this.readyState === 4) {
            xhrSendResponseUrl = this.responseURL;
            var duration = new Date().valueOf() - window.timing[xhrSendResponseUrl].begin;
            console.log({
                  hitType: 'timing',
                  timingCategory: gaTitle + "-" + document.location.href, 
                  timingVar: window.timing[xhrSendResponseUrl].method + ':' + xhrSendResponseUrl ,
                  timingValue: duration,
                  timingLabel: JSON.stringify(data)
                })
          }
        }, false);
      
      send.apply(this, arguments); 
   }

})(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send)

无法拦截。

JS 里内置的 API 除了 Proxy,压根也没提供 AOP(面向切面编程)的能力。

除非你复写它的原型链方法,但极不推荐此做法。因为如果你要做前端埋点或性能监控,你引入的那些库本身就会这么做(比如听云、OneAPM 之类的),你再做了二者就非常容易冲突,况且你复写的玩意儿大概率也不怎么健壮。

但你可以自己封装一层啊,封装层里你想怎么处理就怎么处理呗。需要 AJAX 的调用你自己封装的方法,而不是直接操作 XMLHttpRequest 对象。


P.S. 如果是开发 Chrome 插件那另有办法,但我估计你问的也不是怎么开发插件。

chrome中大部分api都是可以修改的,就比如你可以修改ajax的send方法,

let oldXHR = window.XMLHttpRequest;

//hook xhr
function newXHR() {
    let realXHR = new oldXHR();
    let oldSendFun = realXHR.send;
    //修改ajax的send方法
    realXHR.send = function (body) {
        console.log("拦截到消息提",body)
        oldSendFun.call(realXHR,body)
    }
    return realXHR;
}

window.XMLHttpRequest = newXHR;

//这是一个测试用例
let ajax = new XMLHttpRequest();
ajax.open('post', 'inser.php', true);
ajax.send('name=2');

你也可以使用Proxy也可以做拦截,不过我还没试过。

既然说到拦截网络请求了,你也可以了解一下这个Service_Worker_API,虽然这个上手成本高,但是功能应该是最强到的,ajax,fetch,script脚本,css文件中的一些图片请求啥的都能拦截到也能做修改。

Service Worker 处理这种问题

'use strict';

;(function () {
  if (typeof window.XhrEvent === 'function') return false;
  function XhrEvent (event, params) {
    params = params || { bubbles: false, cancelable: false, detail: undefined };
    var evt = document.createEvent('CustomEvent');
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
    return evt;
  }

  XhrEvent.prototype = window.Event.prototype;

  window.XhrEvent = XhrEvent;
})();
;(function () {
  function xhrEventTrigger (event) {
    // eslint-disable-next-line no-undef
    var xhrEvent = new XhrEvent(event, { detail: this });
    window.dispatchEvent(xhrEvent);
  }

  var oldXHR = window.XMLHttpRequest;

  function newXHR (args) {
    var realXHR = new oldXHR(args);
    
    let oldSend = realXHR.send;
    realXHR.send = function(data){
        this.requestBody = data;
        xhrEventTrigger.call(this, 'xhrSendStart');
        oldSend.call(this, this.requestBody);

    };

    // 请求开始
    realXHR.addEventListener('loadstart', function () { xhrEventTrigger.call(this, 'xhrLoadStart'); }, false);
    // 请求进行中
    realXHR.addEventListener('progress', function () { xhrEventTrigger.call(this, 'xhrProgress'); }, false);

    realXHR.addEventListener('abort', function () { xhrEventTrigger.call(this, 'xhrAbort'); }, false);
    // 请求错误
    realXHR.addEventListener('error', function () { xhrEventTrigger.call(this, 'xhrError'); }, false);
    // 请求中
    realXHR.addEventListener('load', function () { xhrEventTrigger.call(this, 'xhrLoad'); }, false);
    // 超时
    realXHR.addEventListener('timeout', function () { xhrEventTrigger.call(this, 'xhrTimeout'); }, false);
    // 请求完毕
    realXHR.addEventListener('loadend', function () { xhrEventTrigger.call(this, 'xhrLoadEnd'); }, false);
    // 请求状态改变
    realXHR.addEventListener('readystatechange', function () { xhrEventTrigger.call(this, 'xhrReadyStateChange'); }, false);

    return realXHR;
  }

  window.XMLHttpRequest = newXHR;
})();
window.addEventListener('xhrSendStart', function(e){
  console.log(e.detail.requestBody);
  e.detail.requestBody = '123321';
});
你知道吗?

宣传栏