Chrome扩展 - 如何获取HTTP响应体?

65

这似乎是一个困难的问题(或者是不可能的?)。 我想在观看Chrome扩展后台脚本时获取并阅读由浏览器引起的HTTP响应,我们可以通过以下方式获取HTTP请求正文:

chrome.webRequest.onBeforeRequest.addListener(function(data){
    // data contains request_body
},{'urls':[]},['requestBody']);

我也查阅了以下这些stackoverflow:

是否有巧妙的方法在Chrome扩展程序中获取HTTP响应正文?


1
没有通用的方法。只有针对特定用例才可能实现。 - Rob W
5个回答

37
我找不到比这个答案更好的方法了。 Chrome扩展程序读取HTTP响应 答案告诉了如何获取响应头并在另一页中显示。但是响应对象中没有主体信息(请参见event-responseReceived)。如果你想获取响应正文而不需要切换到另一页,试试以下方法。
var currentTab;
var version = "1.0";

chrome.tabs.query( //get current Tab
    {
        currentWindow: true,
        active: true
    },
    function(tabArray) {
        currentTab = tabArray[0];
        chrome.debugger.attach({ //debug at current tab
            tabId: currentTab.id
        }, version, onAttach.bind(null, currentTab.id));
    }
)


function onAttach(tabId) {

    chrome.debugger.sendCommand({ //first enable the Network
        tabId: tabId
    }, "Network.enable");

    chrome.debugger.onEvent.addListener(allEventHandler);

}


function allEventHandler(debuggeeId, message, params) {

    if (currentTab.id != debuggeeId.tabId) {
        return;
    }

    if (message == "Network.responseReceived") { //response return 
        chrome.debugger.sendCommand({
            tabId: debuggeeId.tabId
        }, "Network.getResponseBody", {
            "requestId": params.requestId
        }, function(response) {
            // you get the response body here!
            // you can close the debugger tips by:
            chrome.debugger.detach(debuggeeId);
        });
    }

}

我认为这对我有足够的用处,你可以使用chrome.debugger.detach(debuggeeId)来关闭这个不好看的提示。

抱歉,可能没有帮到你... ^ ^


3
我从上面的代码中制作了一个内容脚本,并在响应体部分添加了console.log和alert,但是没有任何反应。代码应该放在哪里? - user5155835
5
我收到了错误信息:“无法‘附加’到chrome://url”。有没有什么方法只附加到特定的URL? - evanjmg
4
这是从后台脚本还是内容脚本运行的? - spy
4
如果在 manifest.json 的权限数组中添加 "tabs",那么您可以查询 URL。在 chrome.tabs.query 中使用以下函数:function (tabArray) { currentTab = tabArray[0]; if(!currentTab.url.startsWith("chrome:")){ chrome.debugger.attach({ //debug at current tab tabId: currentTab.id }, version, onAttach.bind(null, currentTab.id)); } } - Paul
2
嗨,如果有人需要更多命令来获取网络信息,请访问此链接https://chromedevtools.github.io/devtools-protocol/tot/Network。 - Ernesto Alfonso
显示剩余5条评论

12

现在有一种方法可以使用Chrome开发者工具扩展来实现,示例代码可在此处查看:博客文章

简而言之,以下是他示例代码的改编:

chrome.devtools.network.onRequestFinished.addListener(request => {
  request.getContent((body) => {
    if (request.request && request.request.url) {
      if (request.request.url.includes('facebook.com')) {

         //continue with custom code
         var bodyObj = JSON.parse(body);//etc.
      }
    }
  });
});

26
这种方法的缺点是我们必须一直保持 Chrome 开发者工具处于打开状态,因为只有在打开开发者工具时扩展程序才会被激活。 - mikey

10
这绝对不是Chrome扩展生态系统提供的开箱即用的功能。但是,我找到了几种解决方法,但它们都有各自的缺点。
第一种方法是:
1.使用内容脚本注入我们自己的自定义脚本。 2.使用自定义脚本扩展XHR的本机方法以读取响应。 3.将响应添加到网页的DOM中的隐藏元素中(而不是display: none)。 4.使用内容脚本读取隐藏的响应。
第二种方法是创建一个DevTools扩展,这是唯一提供API以读取每个请求的扩展。
我已经在博客文章这里中详细介绍了这两种方法。
如果您遇到任何问题,请告诉我! :)

你的文章没有说明如何向后台页面发送响应,因为注入 'chrome.runtime.sendMessage' 是无效的。 - Andrew
@Andrew 我会检查一下,这篇文章只是用来读取响应体的。我可以问一下你在哪里使用了 'chrome.runtime.sendMessage' 吗? - Tarun Dugar
我试图在注入的内容脚本中使用它,但显然它不起作用。文章提到“你可以传递给BG”,所以知道如何做这件事会很好。 - Andrew
您不能像文章注入自运行函数那样在注入的脚本中使用Chrome消息API。该脚本与扩展之间没有消息传递。 - Andrew
@Andrew 是的,因为注入的脚本不是内容脚本。你可以通过将注入脚本的数据附加到DOM元素上,然后使用内容脚本通过读取相应的DOM元素来访问该数据来实现这一点。我希望这样能够澄清问题? - Tarun Dugar
显示剩余4条评论

6
为了获得XHR响应体,您可以按照此答案中的说明操作。
要获取FETCH响应体,您可以查看本文中的“解决方案3”以及此答案。两者都可以在不使用chrome.debugger的情况下获得响应体。
简而言之,您需要使用与XHR请求相同的方法从内容脚本向页面注入以下函数。
const constantMock = window.fetch;
 window.fetch = function() {
    return new Promise((resolve, reject) => {
        constantMock.apply(this, arguments)
            .then((response) => {
                if (response) {
                    response.clone().json() //the response body is a readablestream, which can only be read once. That's why we make a clone here and work with the clone
                    .then( (json) => {
                        console.log(json);
                        //Do whatever you want with the json
                        resolve(response);
                    })
                    .catch((error) => {
                        console.log(error);
                        reject(response);
                    })
                }
                else {
                    console.log(arguments);
                    console.log('Undefined Response!');
                    reject(response);
                }
            })
            .catch((error) => {
                console.log(error);
                reject(response);
            })
    })
}

如果response.clone().json()无法正常工作,你可以尝试response.clone().text()

问题是关于截取带有扩展名的页面的所有请求。您的答案是关于截取自己的请求。例如,使用fetch发送的自己的请求可以轻松地用".then"函数处理。 - Sergey NN
1
@SergeyNN 我的回答是关于拦截所有请求的。请仔细阅读并尝试理解! - Kalo

1
我展示了我的完整代码,如果有帮助的话。我添加了下划线以获取请求URL,谢谢。
//background.js
import _, { map } from 'underscore';

var currentTab;
var version = "1.0";

chrome.tabs.onActivated.addListener(activeTab => {
    currentTab&&chrome.debugger.detach({tabId:currentTab.tabId});
    currentTab = activeTab;
    chrome.debugger.attach({ //debug at current tab
        tabId: currentTab.tabId
    }, version, onAttach.bind(null, currentTab.tabId));
});

function onAttach(tabId) {
    chrome.debugger.sendCommand({ //first enable the Network
        tabId: tabId
    }, "Network.enable");
    chrome.debugger.onEvent.addListener(allEventHandler);
}

function allEventHandler(debuggeeId, message, params) {
    if (currentTab.tabId !== debuggeeId.tabId) {
        return;
    }
    if (message === "Network.responseReceived") { //response return
        chrome.debugger.sendCommand({
            tabId: debuggeeId.tabId
        }, "Network.getResponseBody", {
            "requestId": params.requestId
            //use underscore to add callback a more argument, passing params down to callback
        }, _.partial(function(response,params) {
            // you get the response body here!
            console.log(response.body,params.response.url);
            // you can close the debugger tips by:
            // chrome.debugger.detach(debuggeeId);
        },_,params));
    }
}

我还发现chrome.debugger.sendCommand存在一个bug。如果我有两个请求的URI相同但参数不同,例如: 第二个请求将无法获得正确的responseBody,它会显示:
Chrome Extension: "Unchecked runtime.lastError: {"code":-32000,"message":"No resource with given identifier found"}

但是我直接在后台开发工具中调试时,它获取了第二个正确的正文。
chrome.debugger.sendCommand({tabId:2},"Network.getResponseBody",{requestId:"6932.574"},function(response){console.log(response.body)})

所以tabId和requestId没有问题。 然后我用setTimeout包装chrome.debugger.sendCommand,它将正确地获取第一个和第二个responseBody。
if (message === "Network.responseReceived") { //response return            
console.log(params.response.url,debuggeeId.tabId,params.requestId)
        setTimeout(()=>{
            chrome.debugger.sendCommand({
                tabId: debuggeeId.tabId
            }, "Network.getResponseBody", {
                "requestId": params.requestId
                //use underscore to add callback a more argument, passing params down to callback
            }, _.partial(function(response,params,debuggeeId) {
                // you get the response body here!
                console.log(response.body,params.response.url);
                // you can close the debugger tips by:
                // chrome.debugger.detach(debuggeeId);
            },_,params,debuggeeId));
        },800)
    
}

我认为setTimeout不是完美的解决方案,有人能提供帮助吗? 谢谢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接