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

wincss

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”,找到了Is there a hook for when an AJAX call returns? 第一个回答便是使用 WebRequest API 。

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

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

  1. Chrome 的 issue:allow extension to edit response body - chromium - Monorail 意味着上述想法并不能实现
  2. 可以替换页面上的 XMLHttpRequest
  3. 用 WebRequest API 将请求重定向到 data:-URL

初看上去,第三个方案似乎比较简单,所以先按此执行。

方案2:WebRequest API 重定向

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

背景页的代码大致如下:

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。

继续上搜索引擎,我看到了 54257 - The absence of synchronous message API make impossible to pass options to scripts that are loaded before the page to block content. - chromium - Monorail 这又是一个 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

最后,只剩下这一个方案了,上面那个答案说得很复杂,但搜一下还是能找到“成品”方案。How can I modify the XMLHttpRequest responsetext received by another function? 中已经写好了代码,略做修改如下:

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。想要实现我们想要的功能,可以参考Building a Chrome Extension - Inject code in a page using a Content script 的做法。再写一个文件:

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

用 Chrome 扩展实现修改相关推荐

  1. chrome扩展插件拦截修改请求头

    chrome扩展插件拦截修改请求头 常见问题 由于部分需求需要调用从三方抓包的来的接口取得一些数据,需要用谷歌扩展插件跨域请求三方接口,并携带部分头部信息,找了几个小时,终于找到了这个方法 在扩展程序 ...

  2. chrome 扩展 修改 html,制作并反映Chrome扩展程序中popup.html的更改

    我是Chrome扩展程序的新手.我正在尝试创建一个基本扩展,每隔几秒就会在弹出页面中将值更新为输入字段.这是我目前的代码. 的manifest.json { "manifest_versio ...

  3. 分享一些好用的 Chrome 扩展

    阅读本文大概需要 2.8 分钟. 前言 使用浏览器扩展程序可以使你的工作效率提高数倍不止,那么下面我就向大家分享一下我日常使用的扩展,可能大多数扩展大家都已经在使用了,不过也难免有一两个是你不知道的. ...

  4. SAP UI5 应用开发教程之四十一 - Chrome 扩展 UI5 Inspector 的离线安装和使用方法试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  5. 推荐一个比较好用的Chrome扩展应用,提供了桌面便签功能

    作为一个程序员,每天编程时脑子里不时会闪出一些灵感和火花.过去Jerry的习惯做法是用钢笔和笔马上记下来. 然而这样太不环保了. 后来我想到在电脑上新建一个记事本,把自己的灵感维护进去,但是这样的话, ...

  6. 推荐一个可以把网页背景色调成护眼色的Chrome扩展应用

    程序员一天有10几个小时要面对着电脑,老是这种白晃晃的屏幕,谁的眼睛受得了? 我在网上逛了一圈,找到一个比较实用的Chrome扩展应用,可以一键实现将Chrome打开网页的背景色修改成护眼的豆沙绿,这 ...

  7. 微软Edge扩展工具箱旨在将Chrome扩展带至Edge

    微软Windows 10新工具Edge扩展工具箱旨在让开发人员可以轻松地将Chrome扩展转换为微软Edge扩展. \\ 微软Edge扩展工具箱的目标是在少修改或不修改的情况下将Chrome扩展移植到 ...

  8. activex for chrome扩展程序 下载”_Chrome扩展程序一键生成网页骨架屏

    对于依赖接口渲染的页面,在拿到数据之前页面往往是空白的,为了提示用户当前正在加载中,往往会使用进度条.loading图标或骨架屏的方式.对于前两种方案而言,实现比较简单:本文主要研究骨架屏的应用及实现 ...

  9. edge浏览器扩展插件中心10月发布 可直接安装Chrome扩展

    edge浏览器扩展插件中心10月发布 可直接安装Chrome扩展 Windows 10的全新浏览器Edge收获了不少好评,我们也知道它将在今年秋天迎来扩展程序的支持. Mashable已经指出,微软将 ...

最新文章

  1. 在项目实践中用更优雅的方式处理数组问题
  2. Antd Vue range-picker 日期初始值设置 与 重置日期踩坑总结
  3. Jersey 入门与Javabean
  4. 关于oracle sql developer乱码的问题
  5. 302状态码_HTTP协议详解(基础概念 方法 状态码 首部 连接 Cookie 新特性 安全)
  6. gatsby_如何使用Gatsby和React Leaflet创建自己的圣诞老人追踪器
  7. 家里电脑是win10,但开机都要3分钟,请问怎么提快电脑速度?
  8. SQL Server索引的执行计划
  9. pythonflask接口开发处理多线程请求_flask是如何处理多个访问请求的?
  10. 机械电子工程专业和计算机科学,机械电子工程专业属于什么类别
  11. IP雷达4.0+网络检测
  12. 管家婆服务器支持win7,Windows7多种措施 打造无敌驱动管家婆
  13. 微信群二维码活码生成 微信活码
  14. 计算机桌面空白图标如何删除,桌面上有两个i空白文件的图标删不掉怎么办急急急...
  15. python excel写入日期变数字_RPA-使用Python读取Excel日期结果为数字时的转换处理方法...
  16. 解决两台路由器串联上网问题
  17. “Hacker_R_US”因炸弹威胁和DDoS勒索被判8年监禁
  18. android 4.0 原生短信,Android 4.0 短信发不出去解决办法
  19. android关闭背光
  20. 图像处理中 光场(Light Field)简介及理解

热门文章

  1. select case语句_图解Go select语句原理
  2. 5双机配置_CentOS 7 高可用双机热备实现
  3. gradle maven_Gradle vs Maven
  4. mockito模拟依赖注入_Mockito间谍–部分模拟
  5. Android ViewModel
  6. Java StringBuilder
  7. C# 窗体半透明,控件不透明
  8. Web开发过程中需要学习的知识有哪些?
  9. 开课吧:为什么指针被誉为C语言灵魂?
  10. 新书推荐:可爱的Python