在JavaScript中发送之前拦截XHR并更改请求头和URL

我要截取所有正在发送的XHR请求,并在发送请求之前更改其URL和标头。
找到this类似问题,但没有答案。

我尝试挂钩XMLHttpRequest.prototype.open,但它只允许我访问响应:

(function () {
    var origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function () {
        console.log(arguments); // prints method ("GET"), URL
        console.log(this); // prints response, responseText, responseURL, status, statusText, and onXXX handlers
        origOpen.apply(this, arguments);
    };
})();

还尝试了挂钩XMLHttpRequest.prototype.setRequestHeader,但它只允许我逐个访问正在设置的每个标头值,无法将其与请求的URL相关联:

(function () {
    var origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
    XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
        console.log("header", header);
        console.log("value", value);
        origSetRequestHeader.apply(this, arguments);
    };
})();

我设法挂钩XMLHttpRequest.prototype.send来设置自定义标头,但是由于我想更改现有的标头键,所以它附加了我的新值,而不是替换现有的值。其他人也遇到了同样的问题:1,2:

(function () {
    var origSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function () {
        arguments[1] = myNewUrl; // arguments[1] holds the URL
        this.setRequestHeader('existingHeaderKey', newHeaderValue)
        origSend.apply(this, arguments);
    };
})();

如何完成此操作?


解决方案

XMLHttpRequest(Xhr)接口公开的东西很少。所以您可以截获的内容是有限制的。

但是,我们可以将xhr对象包装在Proxy中并收集数据,直到调用Send为止。在发送请求之前,我们会一次性修改数据。

const OriginalXHR = XMLHttpRequest;

// wrap the XMLHttpRequest
XMLHttpRequest = function() {
  return new Proxy(new OriginalXHR(), {

    open(method, url, async, username = null, password = null) {
      lg('open');
      // collect URL and HTTP method
      this.modMethod = method;
      this.modUrl = url;

      this.open(...arguments);
    },

    setRequestHeader(name, value) {
      lg('set header');
      if (!this.modReqHeaders) {
        this.modReqHeaders = {};
      }
      // collect headers
      this.modReqHeaders[name] = value;

      // do NOT set headers here. Hold back!
      // this.setRequestHeader(name, value);
    },

    send(body = null) {
      lg('processing request...');
      // do the final processing
      // ...
      // don't forget to set headers
      for (const [name, value] of Object.entries(this.modReqHeaders)) {
        this.setRequestHeader(name, value);
      }

      lg('sending request =>' +
        '
		method: 	' + this.modMethod +
        '
		url:		' + this.modUrl +
        '
		headers:	' + JSON.stringify(this.modReqHeaders));
      this.send(body);
    },

    get(xhr, key) {
      if (!key in xhr) return undefined;

      let value = xhr[key];
      if (typeof value === "function") {
        // if wrapped, use the function in proxy
        value = this[key] || value;
        return (...args) => value.apply(xhr, args);
      } else {
        //return properties
        return value;
      }
    },

    set(xhr, key, value) {
      if (key in xhr) {
        xhr[key] = value;
      }
      return value;
    }
  });
}
console.warn('XMLHttpRequest has been patched!
 XMLHttpRequest: ', XMLHttpRequest);

let url = 'https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=1';

function getData() {
  console.log('fetching lorem ipsum');
  let xhr = new XMLHttpRequest();
  xhr.responseType = 'json';

  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("demo").innerText = this.response[0];
    }
  };
  xhr.open("GET", url, true);
  xhr.setRequestHeader('Referer', 'www.google.com');
  xhr.setRequestHeader('Accept-Encoding', 'x-compress; x-zip')
  xhr.setRequestHeader('Accept-Language', 'de-US,en;q=0.5');
  xhr.send();
}

//fancy logging, looks good in dark mode
function lg(msg) {
  console.log('%c	 Proxy: ' + msg, 'background: #222; color: #bada55');
}
#demo {
  min-height: 100px;
  background-color: wheat;
}
<button onclick="getData()">Get data</button>
<div id="demo"></div>
<p>Note: look in the Developer Console for debug logs</p>

您可以根据需要将剩余的xhr方法或属性包装在代理处理程序中。
这可能比不上服务人员。但是服务人员有以下缺点:

服务工作器在工作器上下文中运行:因此它没有DOM访问权限,并且运行在与驱动您的应用程序的主JavaScript不同的线程上,因此它是非阻塞的。它被设计为完全异步;因此,诸如Synchronous XHR和Web Storage之类的API不能在服务工作者内部使用。

出于安全原因,服务工作人员仅通过HTTPS运行。修改了网络请求后,对中间人攻击完全开放将是非常糟糕的。在Firefox中,Service Worker API也是隐藏的,当用户处于私人浏览模式时无法使用。ref

相关文章