博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用 Chrome 扩展实现修改
阅读量:5273 次
发布时间:2019-06-14

本文共 5214 字,大约阅读时间需要 17 分钟。

 

 
用 Chrome 扩展实现修改 ajax 请求的响应

用 Chrome 扩展实现修改 ajax 请求的响应

4 个月前

背景

Fiddler 和 Charles 是常见的 HTTP 调试器,它们会在本地运行一个代理服务器,可以查看浏览器或其它客户端软件通过这个代理发起的请求和服务器的响应,也可以在请求提交到服务器之前和服务器返回响应之后设置断点,手工修改请求、响应的内容。另外,两个软件都可以以“中间人攻击”的形式,解密 HTTPS 通信。

某天,我在调试某个网站的过程中,希望把“修改 ajax 请求的响应”这一功能持久化下来方便使用。Fiddler 提供了自定义脚本的功能,但它的 macOS 版本和我用 Homebrew 安装的 Mono 不兼容,而且我也不希望我的所有网络流量都经过 Fiddler 代理。另外考虑到将来可能把这个功能小范围发布出来供其它人使用,那么 Fiddler 或者 Charles 这种“重量级”解决方案就被否定了,所以我很自然地想到了使用 Chrome 扩展来完成此任务。

目标

实现一个 Chrome 扩展,自动修改特定网站的 ajax 请求的响应。网站是一个重度使用 ajax 的网站,并且带有比较严格的“反作弊”限制,对 Cookie、HTTP Header(Referer等)甚至多个 ajax 请求的顺序都有要求,如果出错就无法继续。

方案1:WebRequest API 修改响应

从 Fiddler 这种方案自然而然地想到,如果 Chrome 也提供类似的“断点”机制,外加 JavaScript 脚本,上述问题就解决了。于是搜了一下 “chrome extension hook ajax”,找到了 第一个回答便是使用  。

于是开始学习 WebRequest API 的文档,它能监听的事件如上图所示,最有可能满足条件的就是 onResponseStarted 了,但仔细一看发现它是一个异步事件,并不能实现“断点”,也不能做任何改动(This event is informational and handled asynchronously. It does not allow modifying or cancelling the request)

换个关键词 “webrequest api modify response” 再次搜索,找到了和我的需求十分吻合的问题和答案: ,其中有三个有用的信息:

  1. Chrome 的 issue: 意味着上述想法并不能实现
  2. 可以替换页面上的 XMLHttpRequest
  3. 用 WebRequest API 将请求重定向到 data:-URL
初看上去,第三个方案似乎比较简单,所以先按此执行。

方案2:WebRequest API 重定向

我想的方案是:在中以 blocking 模式注册监听 onBeforeRequest 事件,在事件处理函数中,重新发起这个 ajax 请求。此处要注意两点:1.要在原页面的环境中请求,大致思路是在页面中,背景页使用机制和页面脚本通信,2.“重新发起”的 ajax 请求依然会被 onBeforeRequest 事件拦截,需要做额外的处理。“重新发起”的 ajax 请求拿到数据后,进行修改,最后编码为 data:-URL 作为  返回。

背景页的代码大致如下:

chrome.webRequest.onBeforeRequest.addListener( function(details) { // 如果请求带有特殊标记,则不进行修改 if (details.url.endsWith("#do_not_modify_this_request")) { return {} } // 使用消息机制将请求传递给页面再发起 ajax,而不是在背景页中发起 chrome.tabs.sendMessage(details.tabId, details, function(response) { // 此处可以修改response... redirectUrl = "data:application/json;charset=UTF-8;base64," + Base64.encode(newResponse) }); return { redirectUrl: ...}; }, { urls: [...]}, ["blocking", "requestBody"] );
页面脚本的代码大致如下:
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { // 重新发起的请求要做标记,避免无限循环 var settings = { url: request.url + "#do_not_modify_this_request", method: request.method, dataType: "text" }; if (request.requestBody && request.requestBody.formData) { settings.data = request.requestBody.formData; } $.ajax(settings).done(function(data) { sendResponse(data); }); // 由于 sendResponse 是异步调用的,需要返回 true return true; } );

前途是光明的,道路是曲折的。在这里我就遇到了“暂时的困难”,写完上面的代码,我竟然不知道怎么写下去了。前面说的方案,到这里似乎已经完成了 90% 了:背景页拦截了 ajax 请求,在页面注入脚本重新发起了请求,拿到了结果,进行了修改,编码成 data:-URL。但差就差在最后一步上,这个 data:-URL 是在回调函数里拼出来的,而执行回掉函数的时候,外层的事件处理函数早就应该返回啦。并没有任何机制可以在返回前“等一下” sendMessage。

继续上搜索引擎,我看到了  这又是一个 issue ,而且结论还是 WontFix 。

其中,用 storage API 可以解决,但下一条回复反驳了他,因为 storage API 本身也是异步的。于是这个想法又一次失败了。

方案2.5:不完美的尝试

我把背景页改了一下,页面脚本保持不变:

chrome.webRequest.onBeforeRequest.addListener( function(details) { // 发起 ajax 请求的部分不变,不再处理响应 if (details.url.endsWith("#do_not_modify_this_request")) { return {} } chrome.tabs.sendMessage(details.tabId, details, function(response) {}); // 直接生成新页面,进行重定向 content = "......" return { redirectUrl: "data:application/json;charset=UTF-8;base64," + Base64.encode(content)}; }, { urls: [...]}, ["blocking", "requestBody"] );

方法简单粗暴,ajax 请求照常发起(因为服务端的限制,如果不发起这个 ajax 请求的话,下一步其它 ajax 必然会返回错误,功能就无法使用了),但是结果直接忽略,用预先准备好的假页面直接返回。

这个方案实际执行时“时好时坏”,原因是,事件处理函数返回后,页面的下一个 ajax 请求就会发起,如果模拟 ajax 请求先于它发生,则结果正常。反之,如果下一个 ajax 请求发起时,我的模拟请求尚未发送,那服务端就直接拒绝执行,返回“服务器繁忙”的错误,其实这“服务器繁忙”就是“发现你在作弊”的意思。

方案3:替换 XMLHttpRequest

最后,只剩下这一个方案了,说得很复杂,但搜一下还是能找到“成品”方案。 中已经写好了代码,略做修改如下:

function modifyResponse(response) { var original_response, modified_response; if (this.readyState === 4) { // 使用在 openBypass 中保存的相关参数判断是否需要修改 if (this.requestUrl ... && this.requestMethod ...) { original_response = response.target.responseText; Object.defineProperty(this, "responseText", { writable: true}); modified_response = JSON.parse(original_response); // 根据 sendBypass 中保存的数据修改响应内容 this.responseText = JSON.stringify(modified_response); } } } function openBypass(original_function) { return function(method, url, async) { // 保存请求相关参数 this.requestMethod = method; this.requestURL = url; this.addEventListener("readystatechange", modifyResponse); return original_function.apply(this, arguments); }; } function sendBypass(original_function) { return function(data) { // 保存请求相关参数 this.requestData = data; return original_function.apply(this, arguments); }; } XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open); XMLHttpRequest.prototype.send = sendBypass(XMLHttpRequest.prototype.send);

这段代码会替换 XMLHttpRequest 中的 open 和 send 函数,在 open 中优先注册 readystatechange 的事件监听,以便在原页面代码执行前修改 responseText 的内容。

这段代码不能直接注入页面,因为 Chrome 扩展的 Content Script 会运行在中,直接注入的话,并不能影响到页面原有的 XMLHttpRequest。想要实现我们想要的功能,可以参考 的做法。再写一个文件:

var s = document.createElement("script"); s.src = chrome.extension.getURL("xmlhttp.js"); s.onload = function() { this.remove(); }; (document.head || document.documentElement).appendChild(s);

这个功能一看就知道,在页面上增加一个 <script> 标签,src 属性指向插件中的 xmlhttp.js。为了让这个文件能在页面内被引用,需要在 manifest.json 里加一行:

"web_accessible_resources": ["xmlhttp.js"]

需要注意的是:用这种方法插入的脚本,是无法控制在何时执行的(不像 Content Script,可以设置 document_start、document_end、document_idle),而在此文件执行前发起的 ajax 是无法被修改的。幸好在我的需求中,这些 ajax 请求都是由用户点击触发的,在那之前时间充足,足够我动手脚了。

综上所述,一个能修改 ajax 响应的 Chrome 扩展就写好了。

转载于:https://www.cnblogs.com/h2zZhou/p/9235152.html

你可能感兴趣的文章
leetcode 51 N-Queens & 52 N-Queens II ----- java
查看>>
图片上传
查看>>
死锁及oracle死锁--转载
查看>>
Android设置监听
查看>>
findByExample(Object exampleEntity)方法得到的List判断是否为空,不可用(lis != null)
查看>>
pip下载保存Python包,pip离线安装
查看>>
mysql建库建表
查看>>
实验五
查看>>
spark生成大宽表的parquet性能优化
查看>>
彩票,随机一注
查看>>
怎么总是这等凶卦
查看>>
浅谈logo在PPT设计中的运用
查看>>
网络那点事之socket队列
查看>>
编程练习:寻找发帖"水王"扩展问题二
查看>>
查询Oracle正在执行和执行过的SQL语句
查看>>
说明性弹性域段
查看>>
15_动态SQL
查看>>
项目协作工具调研
查看>>
理解HEAD请求以及HTTP/204和HTTP/206响应
查看>>
Android 网络通信框架Volley简介(Google IO 2013)
查看>>