在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
相关文章