能否创建一个修改 HTTP 响应体的 Chrome 扩展程序?
我查看了 Chrome 扩展程序 API,但是没有找到可以实现这个功能的接口。
一般来说,您无法使用标准的Chrome扩展API更改HTTP请求的响应正文。
此功能正在104058:WebRequest API:允许扩展编辑响应正文中请求。点赞此问题以接收更新通知。
如果要编辑已知XMLHttpRequest
的响应主体,则可以通过内容脚本注入代码覆盖默认的XMLHttpRequest
构造函数,并使用自定义(全功能)构造函数重写响应,然后触发真正的事件。确保您的XMLHttpRequest对象完全符合Chrome内置的XMLHttpRequest
对象,否则会破坏AJAX-heavy网站。
在其他情况下,您可以使用 chrome.webRequest
或 chrome.declarativeWebRequest
API 将请求重定向到 data:
-URI。与XHR方法不同的是,您将无法获得请求的原始内容。实际上,请求永远不会到达服务器,因为重定向只能在实际请求发送之前完成。如果您重定向一个 main_frame
请求,则用户将看到 data:
-URI 而不是请求的URL。
data:text...
? - Pacerier如@Rob w所说,我已经重写了XMLHttpRequest
,这是修改任何网站上的XHR请求的结果(类似于透明修改代理):
像@Rob w说的那样,我已经覆盖了XMLHttpRequest
,这是修改任何网站上XHR请求的结果(就像透明修改代理一样):
var _open = XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, URL) {
var _onreadystatechange = this.onreadystatechange,
_this = this;
_this.onreadystatechange = function () {
// catch only completed 'api/search/universal' requests
if (_this.readyState === 4 && _this.status === 200 && ~URL.indexOf('api/search/universal')) {
try {
//////////////////////////////////////
// THIS IS ACTIONS FOR YOUR REQUEST //
// EXAMPLE: //
//////////////////////////////////////
var data = JSON.parse(_this.responseText); // {"fields": ["a","b"]}
if (data.fields) {
data.fields.push('c','d');
}
// rewrite responseText
Object.defineProperty(_this, 'responseText', {value: JSON.stringify(data)});
/////////////// END //////////////////
} catch (e) {}
console.log('Caught! :)', method, URL/*, _this.responseText*/);
}
// call original callback
if (_onreadystatechange) _onreadystatechange.apply(this, arguments);
};
// detect any onreadystatechange changing
Object.defineProperty(this, "onreadystatechange", {
get: function () {
return _onreadystatechange;
},
set: function (value) {
_onreadystatechange = value;
}
});
return _open.apply(_this, arguments);
};
例如,Tampermonkey可以成功使用此代码在任何网站上进行任何修改 :)
response
而不是responseText
,所以你只需要将Object.defineProperty更改为使用response
即可。 - jgawrychchrome.devtools.network.onRequestFinished
列出所有请求。getContent()
方法下载其响应,然后将该响应发送到保存在本地的 Python 脚本中。call
(对于OSX)或subprocess.Popen
(对于Windows)在编辑器中打开文件。chrome.proxy.settings.set()
)将PAC设置为代理设置。该PAC文件将所有通信重定向到Python脚本的代理。chrome.debugger
API实现,该API允许扩展程序访问Chrome DevTools Protocol,该协议通过其网络API支持HTTP拦截和修改。
这个解决方案是由Chrome 问题 487422 的一个评论提出的:
“对于任何想要一个可行替代方案的人,您可以在背景/事件页面中使用chrome.debugger
来附加到您想要监听的特定选项卡(或者如果可能的话,附加到所有选项卡,我个人没有测试所有选项卡),然后使用调试协议的网络API。”
“唯一的问题是,在选项卡视口的顶部会有通常的黄色条,除非用户在chrome://flags
中关闭它。”
首先,将调试器附加到目标。chrome.debugger.getTargets((targets) => {
let target = /* Find the target. */;
let debuggee = { targetId: target.id };
chrome.debugger.attach(debuggee, "1.2", () => {
// TODO
});
});
接下来,发送Network.setRequestInterceptionEnabled
命令,该命令将启用网络请求拦截:
chrome.debugger.getTargets((targets) => {
let target = /* Find the target. */;
let debuggee = { targetId: target.id };
chrome.debugger.attach(debuggee, "1.2", () => {
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
});
});
Chrome现在将开始发送Network.requestIntercepted
事件。请添加监听器:
chrome.debugger.getTargets((targets) => {
let target = /* Find the target. */;
let debuggee = { targetId: target.id };
chrome.debugger.attach(debuggee, "1.2", () => {
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
});
chrome.debugger.onEvent.addListener((source, method, params) => {
if(source.targetId === target.id && method === "Network.requestIntercepted") {
// TODO
}
});
});
在监听器中,params.request
将是相应的Request
对象。Network.continueInterceptedRequest
发送响应:
rawResponse
传递。params.interceptionId
作为interceptionId
传递。setRequestInterceptionEnabled
方法似乎没有包含在DevTools协议v1.2中,我找不到一种方法来代替最新的(tip-of-tree)版本。 - Aioroschrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
失败,显示 'Network.setRequestInterceptionEnabled' 未找到。 - PacerierNetwork.setRequestInterceptionEnabled
已经过时,现在应该使用这个新的API: https://chromedevtools.github.io/devtools-protocol/tot/Fetch - macabeus虽然Safari浏览器内置了此功能,但目前我发现在Chrome中最好的解决方法是使用Cypress的拦截(intercept)功能。它可以干净地让我在Chrome中对HTTP响应进行存根化(stub)。我调用cy.intercept
然后cy.visit(<URL>)
,它就会拦截并提供一个针对所访问页面特定请求的存根响应。这里有一个例子:
cy.intercept('GET', '/myapiendpoint', {
statusCode: 200,
body: {
myexamplefield: 'Example value',
},
})
cy.visit('http://localhost:8080/mytestpage')
注意:您可能还需要配置Cypress以禁用一些Chrome特定的安全设置。
通过Chrome DevTools Protocol version 1.3更改响应的版本
当您点击扩展程序的操作按钮时,该扩展程序在选项卡上调用chrome.debugger.attach()来捕获网络事件。
service-worker.js
// start on click in extension action button
chrome.action.onClicked.addListener(function (tab) {
setupDebugger(tab)
});
function setupDebugger(tab) {
const debuggee = {tabId: tab.id};
// The extension calls chrome.debugger.attach() on a tab
// to capture network events when you click the extension's action button.
chrome.debugger.attach(debuggee, "1.0", () => {
chrome.debugger.sendCommand(debuggee, "Fetch.enable", {
patterns: [{
urlPattern: '*', requestStage: 'Response'
}]
});
});
chrome.debugger.onEvent.addListener((source, method, params) => processEvent(debuggee, tab, source, method, params))
}
async function processEvent(debuggee, tab, source, method, params) {
let continueParams = {
requestId: params.requestId,
};
if (source.tabId === debuggee.tabId) {
if (method === "Fetch.requestPaused") {
let request_url = new URL(params.request.url, location)
let target_url = new URL(tab.url, location)
// we determine that this is the request we need, you can do this through the pattern
if (request_url.host === target_url.host && handlers.hasOwnProperty(request_url.pathname)) {
// an example of how to get the response body in order to partially change it
let body = await getResponseBodyJson(debuggee, continueParams)
let new_body = body
// create and send a new response
continueParams.responseCode = event_params.responseStatusCode
continueParams.responseHeaders = event_params.responseHeaders
continueParams.body = b64EncodeUnicode(JSON.stringify(body));
chrome.debugger.sendCommand(debuggee, 'Fetch.fulfillRequest', continueParams);
} else {
// if the request is not ours, let it go without changes
chrome.debugger.sendCommand(debuggee, 'Fetch.continueRequest', continueParams);
}
}
}
}
async function getResponseBodyJson(debuggee, continueParams) {
let res = await chrome.debugger.sendCommand(debuggee, 'Fetch.getResponseBody', continueParams);
let body = res.body
if (res.base64Encoded) {
body = b64DecodeUnicode(body)
}
body = JSON.parse(decodeURIComponent(body))
return body
}
function b64DecodeUnicode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
function b64EncodeUnicode(str) {
// first we use encodeURIComponent to get percent-encoded Unicode,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
manifest.json
{
"manifest_version": 3,
"name": "example",
"version": "1.0",
"description": "",
"background": {
"service_worker": "service-worker.js"
},
"action": {},
"permissions": [
"activeTab",
"tabs",
"debugger",
],
"host_permissions": [
"https://*/*"
]
}
originalContent
行上提到的警告 - 在某些情况下,fetch
的响应可能与原始响应不同。
npm install puppeteer node-fetch@2.6.7
main.js
文件:
const puppeteer = require("puppeteer");
const fetch = require("node-fetch");
(async function() {
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', async (request) => {
let url = request.url().replace(/\/$/g, ""); // remove trailing slash from urls
console.log("REQUEST:", url);
let originalContent = await fetch(url).then(r => r.text()); // TODO: Pass request headers here for more accurate response (still not perfect, but more likely to be the same as the "actual" response)
if(url === "https://example.com") {
request.respond({
status: 200,
contentType: 'text/html; charset=utf-8', // For JS files: 'application/javascript; charset=utf-8'
body: originalContent.replace(/example/gi, "TESTING123"),
});
} else {
request.continue();
}
});
await page.goto("https://example.com");
})();
运行它:
node main.js
安装 Deno:
curl -fsSL https://deno.land/install.sh | sh # linux, mac
irm https://deno.land/install.ps1 | iex # windows powershell
下载 Puppeteer 的 Chrome 浏览器:
PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
main.js
文件:import puppeteer from "https://deno.land/x/puppeteer@16.2.0/mod.ts";
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', async (request) => {
let url = request.url().replace(/\/$/g, ""); // remove trailing slash from urls
console.log("REQUEST:", url);
let originalContent = await fetch(url).then(r => r.text()); // TODO: Pass request headers here for more accurate response (still not perfect, but more likely to be the same as the "actual" response)
if(url === "https://example.com") {
request.respond({
status: 200,
contentType: 'text/html; charset=utf-8', // For JS files: 'application/javascript; charset=utf-8'
body: originalContent.replace(/example/gi, "TESTING123"),
});
} else {
request.continue();
}
});
await page.goto("https://example.com");
运行它:
deno run -A --unstable main.js
我目前遇到了一个TimeoutError
,希望很快能够解决:https://github.com/lucacasonato/deno-puppeteer/issues/65
全新的Chrome Devtools(我写这篇答案的时候是2023年5月)。 https://developer.chrome.com/docs/devtools/overrides/ 本地覆盖文件和HTTP响应头 它默认安装在Chrome版本113.0中...
我刚发现了这个扩展,它可以做很多其他的事情,但在浏览器中修改API响应非常有效:https://requestly.io/
按照以下步骤使其正常工作:
安装扩展程序
进入HttpRules
添加一个新规则并添加URL和响应
使用单选按钮启用规则
转到Chrome,您应该看到响应已被修改
您可以拥有多个具有不同响应的规则,并根据需要启用/禁用。不幸的是,如果URL相同,我还没有找到如何每个请求都有不同的响应。
webRequest.filterResponseData()
。不幸的是,这是一个仅适用于Firefox的解决方案。 - Franklin Yu