当前位置:   article > 正文

JS实现网页抓包-拦截、请求托管以及修改响应数据_js获取页面以响应的请求

js获取页面以响应的请求
一、什么是抓包

什么是抓包:抓包就是抓取网络请求,将请求一直挂在到我们写的中间件中,在中间件统一修改后完成本次请求,俗称请求托管

闲着没事本篇文章用js给大家手写一个抓包工具,配合油猴脚本嵌入各大网站,仅供学习参考、本地测试,切勿实施违法行为,现成脚本代码在最后,老规矩先看图脚本图一目了然,脚本也是我之前自己写的,大家拿去用即可

抓包的作用有哪些?

1、拦截请求,修改请求体,篡改一些我们无法手动更改请求信息

2、拦截响应,修改响应体,对无防御无加密式网站可以造成一定影响,比如解锁一些功能

3、看执行逻辑和说要做的事务(回调事件)

4、方便开发测试

二、思路

        值得一提的是,网页抓包方式比较局限,我们悉知前端向服务器发送请求只有几种方式,原生XMLHTTPRequest、Fetch、Axios(基于前两者混合封装),其中Axios是对请求的进一步封装,底层依旧和前两者挂钩,因此我们只需要了解原生Ajax和Fetch

想要在收发中间横插一脚,只能依靠中间件,今天我们可以手写一个中间件,我会以原生Ajax为例向大家讲述,至于Fetch,看完文章之后原理完全一样,甚至你可以亲自手写,我们知道在java中可以重写和继承各种类来修改代码,但在js中显然没有完整的重写概念,我们只能改变其指向

二、改变请求指向

        首先我们要拦截网站发出的请求,托管到我们编写的代码,这一步怎么做到呢?仔细想一想,使用原生Ajax发送请求的第一步是什么?

1、创建XMLHTTPRequest实例

2、调用open方法控制请求流程

3、调用send方法发送请求

所以我们的思路就来了,先改变其指向,发送请求时网站会自实例化XMLHTTPRequest进行交互,我们捷足先登

middle.js

  1. //开始编写我们的中间件类
  2. class XMLHTTPRequestMiddle {
  3. constructor() { }
  4. open(method, url) {
  5. console.log('请求地址:', url, '请求方式', method)
  6. }
  7. send(data) {
  8. console.log('我是请求体', data)
  9. }
  10. }
  11. //我们将XMLHTTPRequest指向到我们自己写的类
  12. window.XMLHttpRequest = XMLHTTPRequestMiddle

 index.html

  1. <script src="/middle.js"></script>
  2. <script>
  3. const request = new XMLHttpRequest()
  4. request.open('get', 'http://localhost:8060/test')
  5. request.send()
  6. </script>

 别急,我们先启动一下82年没打开过的SpringBoot项目,写一个测试接口,让后端返回一个对象

  1. import org.springframework.web.bind.annotation.GetMapping;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. @RestController
  7. @RequestMapping("/test")
  8. public class TestController {
  9. @GetMapping
  10. public Map<String, String> test() {
  11. HashMap<String, String> result = new HashMap<>();
  12. result.put("code", "200");
  13. result.put("message", "success");
  14. result.put("data", "no data");
  15. return result;
  16. }
  17. }

 结果在我们意料之中,成功拦截到关键信息,url,method,以及body对象(请求体),随之而来又有问题了,请求发送过程中被我们成功拦截,但是还少了关键的方法onreadystatechange,这才是我们发送请求之后最重要的方法,他将决定实现什么功能,如何拿数据,这个方法我们怎么写?不能直接像open和send那样直接写,因为onreadystatechange方法里面有个默认参数比较重要,这个参数是我们无法一开始传入的,发送请求后才能获取到的信息,那么我们怎么做?

一、onreadystatechange方法是什么?

        通俗的来讲,他就是发送请求之后我们要干的事,就写在里边,可以看作Promise的then方法,then方法里面执行你操作页面的逻辑,也是一个回调函数,一般从这里拿到后端传回来的数据供页面渲染,举个例子

  1. request.onreadystatechange = (event) => {
  2. //此方法有个默认参数 可以从参数拿到当前请求的字节流长度等 实现加载进度等
  3. //但是目前这个参数我们写的中间件是无法模拟给出的
  4. document.write(request.responseText)
  5. }

通过观察我们发现onreadystatechange是个setter,setter怎么拿,我们在中间件定义一个setter,setter的特性必须传入至少一个参数,先拿到setter后面肯定有大用场,在中间件加入onreadystatechange的setter方法,直接上代码

  1. class XMLHTTPRequestMiddle {
  2. constructor() { }
  3. open(method, url) {
  4. console.log('请求地址:', url, '请求方式', method)
  5. }
  6. send(data) {
  7. console.log('我是请求体', data)
  8. }
  9. //加入同名setter
  10. set onreadystatechange(callback) {
  11. //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
  12. console.log(callback)
  13. }
  14. }
'
运行

结果如出一辙,非常完美,但是onreadystatechange的参数我们仍然无法模拟,上面拿到的setter参数实际上是XMLHTTPRequest本体中onreadystatechange的映射,我们需要做的是,得到参数,然后传入刚才setter到的callback中执行,怎么做?想到了吗?还有构造啊,构造我们晾了半天,也该干点实事了,干他!

  1. //其余代码略
  2. constructor() {
  3. //我们定义一个callback属性 来映射真正的onreadystatechange 这样可以拿到两个参数并执行
  4. this.callback = null
  5. }
  6. //加入同名setter
  7. set onreadystatechange(callback) {
  8. //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
  9. //console.log(callback)
  10. if (typeof callback == 'function') {
  11. this.callback = callback
  12. } else {
  13. throw new Error('不是函数憋来沾边')
  14. }
  15. }

到这一步,还是没有实质性的动作,因为我们并没有真正发送请求!就连请求收集都没做好,慢慢来,下面看我操作

三、请求收集

        到这里需要收集请求了 ,不建议在构造内定义请求队列(requestQueue),我们提到全局

  1. //在全局定义请求队列 保存每次完整实例后的请求体
  2. const requestQueue = []
  3. //保留一份本体
  4. const xmlhttp = XMLHttpRequest
  5. //------------------------------------------------------------------------------------------
  6. //其余代码略 在构造加入
  7. constructor() {
  8. this.callback = null
  9. //加入一个载体 来收集每次实例的请求体 他与requestQueue 紧密联系 为什么写一个header空对象在内
  10. //对于空对象的顶层添加 如:this.polay.url = 'xxx'不会报错
  11. //但对于次要层添加就会报错 如:this.polay.header.ContentType = 'json' ==> error...
  12. //因为他会认为已经存在header对象,会直接从header对象添加属性 但我们知道请求体内的请求头是个属性很多的对象
  13. this.polay = { header: {} }
  14. //加入Ajax本体来控制请求的真正发送 很关键
  15. this.request = new xmlhttp()
  16. }

 开始写数据收集,碎片化的收集最终会组合成一个完整请求体(request),付上完整代码防止断片

  1. //在全局定义请求队列 保存每次完整实例后的请求体
  2. const requestQueue = []
  3. //保留一份本体
  4. const xmlhttp = XMLHttpRequest
  5. //开始编写我们的中间件类
  6. class XMLHTTPRequestMiddle {
  7. constructor() {
  8. this.callback = null
  9. //加入一个载体 来收集每次实例的请求体 他与requestQueue 紧密联系 为什么写一个header空对象在内
  10. //对于空对象的顶层添加 如:this.polay.url = 'xxx'不会报错
  11. //但对于次要层添加就会报错 如:this.polay.header.ContentType = 'json' ==> error...
  12. //因为他会认为已经存在header对象,会直接从header对象添加属性 但我们知道请求体内的请求头是个属性很多的对象
  13. this.polay = { header: {} }
  14. //加入Ajax本体来控制请求的真正发送 很关键
  15. this.request = new xmlhttp()
  16. //加入responseText的映射
  17. this.responseText = null
  18. //其他属性 原生Ajax有什么加什么 这里暂时用上面若干属性做演示
  19. }
  20. open(method, url) {
  21. this.polay.url = url
  22. this.polay.method = method
  23. }
  24. //send总是在最后执行的 因此在这里 请求体已经收集完了 可以加入队列了
  25. send(data) {
  26. this.polay.body = data
  27. //这里是修改请求request的关键位置 写个小例子 比如修改请求方式Method
  28. const method = prompt(`请输入要修改的method方法,目前是${ this.polay.method }`)
  29. //达到修改效果
  30. this.polay.method = method
  31. //加入队列 现在明白为什么写个载体了吗?
  32. requestQueue.push(this.polay)
  33. //打印队列看是否抓取成功
  34. console.log(`request is`)
  35. console.log(requestQueue)
  36. }
  37. //加入同名setter
  38. set onreadystatechange(callback) {
  39. //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
  40. //console.log(callback)
  41. if (typeof callback == 'function') {
  42. this.callback = callback
  43. } else {
  44. throw new Error('不是函数憋来沾边')
  45. }
  46. }
  47. //设置请求头的方法 原生的 我们重写
  48. setRequestHeader(key, value) {
  49. this.polay.header[key] = value
  50. }
  51. }
  52. //我们将XMLHTTPRequest指向到我们自己写的类
  53. window.XMLHttpRequest = XMLHTTPRequestMiddle

我们通过弹窗把原本get改为post看是否生效

 值得一提的是,上面我使用了弹窗简单模拟修改request操作,实际上你可以搭建界面,随心修改,通过按钮精准修改每一个值,篇幅有限,简陋教学,码不在多,有懂王则灵

厉不厉害?你坤哥,都干到这一步了,但我们页面仍然没有任何输出,我们还是没有真正发送数据,只是简单做了request的数据收集,来吧,真正发送请求,上代码

  1. //在全局定义请求队列 保存每次完整实例后的请求体
  2. const requestQueue = []
  3. //保留一份本体
  4. const xmlhttp = XMLHttpRequest
  5. //开始编写我们的中间件类
  6. class XMLHTTPRequestMiddle {
  7. constructor() {
  8. this.callback = null
  9. //加入一个载体 来收集每次实例的请求体 他与requestQueue 紧密联系 为什么写一个header空对象在内
  10. //对于空对象的顶层添加 如:this.polay.url = 'xxx'不会报错
  11. //但对于次要层添加就会报错 如:this.polay.header.ContentType = 'json' ==> error...
  12. //因为他会认为已经存在header对象,会直接从header对象添加属性 但我们知道请求体内的请求头是个属性很多的对象
  13. this.polay = { header: {} }
  14. //加入Ajax本体来控制请求的真正发送 很关键
  15. this.request = new xmlhttp()
  16. //加入responseText的映射
  17. this.responseText = null
  18. //其他属性 原生Ajax有什么加什么 这里暂时用上面若干属性做演示
  19. }
  20. open(method, url) {
  21. this.polay.url = url
  22. this.polay.method = method
  23. }
  24. //send总是在最后执行的 因此在这里 请求体已经收集完了 可以加入队列了
  25. send(data) {
  26. this.polay.body = data
  27. //这里是修改请求request的关键位置 写个小例子 比如修改请求方式Method
  28. const method = prompt(`请输入要修改的method方法,目前是${ this.polay.method }`)
  29. //达到修改效果
  30. this.polay.method = method
  31. //加入队列 现在明白为什么写个载体了吗?
  32. requestQueue.push(this.polay)
  33. //打印队列看是否抓取成功
  34. console.log(`request is`)
  35. console.log(requestQueue)
  36. //从这里开始发送请求 已经是通过上述修改过的request 我们已经实现拦截并修改request了
  37. this.request.open(this.polay.method, this.polay.url)
  38. this.request.send()
  39. //这里更关键 终于到我们一开始讲的那个参数那里了 在这一步可以解决了 这里决定是否执行回调
  40. this.request.onreadystatechange = (event) => {
  41. //暂时映射一种返回结果类型
  42. this.responseText = this.request.responseText
  43. this.callback(event) //参数问题完美传入
  44. }
  45. }
  46. //加入同名setter
  47. set onreadystatechange(callback) {
  48. //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
  49. //console.log(callback)
  50. if (typeof callback == 'function') {
  51. this.callback = callback
  52. } else {
  53. throw new Error('不是函数憋来沾边')
  54. }
  55. }
  56. //设置请求头的方法 原生的 我们重写
  57. setRequestHeader(key, value) {
  58. this.polay.header[key] = value
  59. }
  60. }
  61. //我们将XMLHTTPRequest指向到我们自己写的类
  62. window.XMLHttpRequest = XMLHTTPRequestMiddle
  1. <script src="/middle.js"></script>
  2. <script>
  3. const request = new XMLHttpRequest()
  4. request.open('get', 'http://localhost:8060/test')
  5. request.send()
  6. request.onreadystatechange = (http, event) => {
  7. document.write(request.responseText)
  8. console.log(event)
  9. }
  10. </script>

 完美解决问题,服务端参数也输出了,event参数也拿到了,那响应拦截如何做呢?

  1. //全局加入响应队列
  2. const responstQueue = []
  3. //其余代码略
  4. send(data) {
  5. this.polay.body = data
  6. //这里是修改请求request的关键位置 写个小例子 比如修改请求方式Method
  7. const method = prompt(`请输入要修改的method方法,目前是${ this.polay.method }`)
  8. //达到修改效果
  9. this.polay.method = method
  10. //加入队列 现在明白为什么写个载体了吗?
  11. requestQueue.push(this.polay)
  12. //打印队列看是否抓取成功
  13. console.log(`request is`)
  14. console.log(requestQueue)
  15. //从这里开始发送请求 已经是通过上述修改过的request 我们已经实现拦截并修改request了
  16. this.request.open(this.polay.method, this.polay.url)
  17. this.request.send()
  18. //这里更关键 终于到我们一开始讲的那个参数那里了 在这一步可以解决了 这里决定是否执行回调
  19. this.request.onreadystatechange = (event) => {
  20. //暂时映射一种返回结果类型
  21. this.responseText = this.request.responseText
  22. //这里决定拦截响应体
  23. const responst = prompt('请输入新的data')
  24. //简陋模拟修改
  25. this.request.responseText = '新的数据'
  26. //最终输出到页面的是你已经修改过得数据
  27. this.callback(event) //参数问题完美传入
  28. }
  29. }

 so?是不是很简单,什么?还不是很理解?!哎,怎么说你好呢,给你准备了现成代码啦,快点拿去!

 四、现成代码(脚本)

请直接粘贴到油猴脚本内!没有油猴也没事,随便打开一个网页打开控制台把代码复制进去即可

  1. // ==UserScript==
  2. // @name New Userscript
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://*/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant none
  10. // ==/UserScript==
  11. /**
  12. * XMLHTTPRequest 拦截工具
  13. *
  14. * 大致思路 手写虚拟XMLHTTPRequest类 让真正的XMLHTTPRequest指向我们自己写的类
  15. * 再把一系列方法 请求地址 请求头 response预先收集起来集中做处理 最后再触发由各大网站自定义的回调函数实现他们的业务
  16. * 但是数据已经经过我们的加工处理了 Understand?
  17. *
  18. * 因为是全自动脚本 我们不得不把Html也集成到了js代码中混合开发 核心代码不多 Html他么的多啊
  19. */
  20. const request = XMLHttpRequest
  21. //请求拦截队列
  22. const RequestQueue = []
  23. //响应拦截队列
  24. const ResponseQueue = []
  25. class XMLHttpRequestPlus {
  26. constructor() {
  27. this.request = new request()
  28. //承载体 修改数据关键
  29. this.paloy = { header: {} }
  30. //核心方法 决定可不可以实现响应拦截
  31. this.requestCallback = null
  32. this.status = 0
  33. this.readyState = 0
  34. this.response = null
  35. this.responseText = null
  36. this.responseURL = null
  37. this.responseXML = null
  38. this.responseType = ''
  39. this.timeout = 1*60*60*24 //超时时间设为一天 强制解除各大网站超时不再有响应的安全策略
  40. }
  41. open(method, url) {
  42. this.paloy.method = method
  43. this.paloy.url = url
  44. this.paloy.params = url.indexOf('?') != -1 ? url.substring(url.indexOf('?') + 1).split('&').reduce(
  45. (obj, item) => {
  46. const keyVal = item.split('=')
  47. if (keyVal.length == 1) { obj[keyVal[0]] = '' } else { obj[keyVal[0]] = keyVal[1] }
  48. return obj
  49. }, {}) : null
  50. }
  51. //请求拦截核心部分
  52. send(body) {
  53. if(this.timeout < 1*60*60*24) this.timeout = 1*60*60*24 //防止对超时时间的更改
  54. this.paloy.data = body
  55. this.paloy.state = this.request.status
  56. this.paloy.ready = this.request.readyState
  57. this.paloy.index = RequestQueue.length
  58. this.paloy.invoke = this.requestCallback
  59. //加入受理队列
  60. RequestQueue.push(this.paloy)
  61. //渲染请求拦截表格
  62. ResolveRequestRow(this.paloy)
  63. //自动滚动到底部
  64. autoScroll()
  65. //在放行之前还有一件事要做 确认用户修改我们的请求体
  66. const xmlConfrimBtn = [...document.querySelectorAll('.xml-http-request-more-message-footer')]
  67. xmlConfrimBtn.forEach((element, key) => {
  68. element.querySelector('button').onclick = () => {
  69. const { parentNode } = element.parentNode
  70. const url = parentNode.querySelector('.request-url').querySelector('textarea').value
  71. const method = parentNode.querySelector('.data-list-select').value
  72. const header = parentNode.querySelector('.request-header').querySelector('textarea').value
  73. const params = parentNode.querySelector('.request-params').querySelector('textarea').value
  74. const invoke = parentNode.querySelector('.request-invoke').querySelector('textarea').value
  75. try {
  76. const paramsed = JSON.parse(params)
  77. RequestQueue[key].url = url.indexOf('?') != -1 ? `${ url }&${ Object.entries(paramsed).map(([key, value]) => `${ key }=${ value }`).join('&') }`
  78. : `${url}?${Object.entries(paramsed).map(([key, value]) => `${ key }=${ value }`).join('&') }`
  79. } catch (error) {
  80. RequestQueue[key].url = url
  81. }
  82. console.log(url)
  83. RequestQueue[key].method = method
  84. RequestQueue[key].header = JSON.parse(header)
  85. RequestQueue[key].params = JSON.parse(params)
  86. //暂不开放修改回调功能 因为目前没有完美的修改方案
  87. //this.requestCallback = new Function(invoke)
  88. }
  89. })
  90. //放行事件 也是核心部分 决定了能不能拿到AJAX的状态以及返回值
  91. const nextRun = [...document.querySelectorAll('.request-next-run')]
  92. nextRun.forEach((element, key) => {
  93. element.onclick = () => {
  94. this.request.open(RequestQueue[key].method, RequestQueue[key].url)
  95. //这里放行请求头
  96. //this.request.setRequestHeader('Content-Type', 'application/json')
  97. Object.entries(RequestQueue[key].header).forEach(([key, value]) => { this.request.setRequestHeader(key, value) })
  98. this.request.send(body)
  99. this.request.onreadystatechange = (event) => {
  100. //先将response托管到响应队列保管 以便劫持修改
  101. if (this.request.status >= 200 && this.request.readyState == 4) {
  102. ResponseQueue.push({
  103. method : this.paloy.method,
  104. url : this.paloy.url,
  105. status : this.request.status,
  106. readyState : this.request.readyState,
  107. response : this.request.response,
  108. responseText : this.request.responseText,
  109. responseURL : this.request.responseURL,
  110. responseXML : this.request.responseXML
  111. })
  112. //紧接着执行响应拦截表格
  113. ResolveResponseRow(ResponseQueue[ResponseQueue.length - 1])
  114. }
  115. //响应数据修改
  116. const setResponseDataBtn = [...document.querySelectorAll('.xml-http-response-more-message-footer')]
  117. setResponseDataBtn.forEach((element, key) => {
  118. element.querySelector('button').onclick = () => {
  119. const { parentNode } = element.parentNode
  120. const status = parentNode.querySelector('.response-status').querySelector('input').value
  121. const data = parentNode.querySelector('.response-data').querySelector('textarea').value
  122. const text = parentNode.querySelector('.response-text').querySelector('textarea').value
  123. ResponseQueue[key].status = status
  124. ResponseQueue[key].response = data
  125. ResponseQueue[key].responseText = text
  126. }
  127. })
  128. //响应拦截放行
  129. const nextRun = [...document.querySelectorAll('.response-next-run')]
  130. nextRun.forEach((element, key) => {
  131. element.onclick = () => {
  132. const { method, url, status, readyState, response, responseText, responseURL, responseXML, index } = ResponseQueue[key]
  133. this.status = status
  134. this.readyState = readyState
  135. this.response = this.responseType == 'json' ? JSON.parse(response) : response
  136. this.responseText = responseText
  137. this.responseURL = responseURL
  138. this.responseXML = responseXML
  139. //最终返回响XMLHTTPRequest接收处理
  140. this.requestCallback(event)
  141. //放行之后此处响应就没用了 不能再次响应给XMLHTTPRequest 所以要移除掉DOM
  142. removeRows(element.parentNode.parentNode, index)
  143. }
  144. })
  145. }
  146. }
  147. })
  148. }
  149. setRequestHeader(name, value) { this.paloy.header[name] = value }
  150. getResponseHeader(name) { return this.request.getResponseHeader(name) }
  151. getAllResponseHeaders() { return this.request.getAllResponseHeaders() }
  152. overrideMimeType(mime) { this.request.overrideMimeType(mime) }
  153. async abort() {
  154. //alert(`检测到${ (await RequestQueue)[RequestQueue.length - 1]['url'] }试图终止请求来保护网站,已被分析大师驳回`)
  155. }
  156. set ontimeout(func) { return }
  157. set onreadystatechange(func) { if (typeof func == 'function') this.requestCallback = func }
  158. }
  159. //切换为手动抓包
  160. window.handMovementPacketCapture = function handMovementPacketCapture() { window.XMLHttpRequest = XMLHttpRequestPlus }
  161. /*
  162. * !这里请注意 如果想嵌入脚本即抓取 请打开下面的注释
  163. */
  164. // window.XMLHttpRequest = XMLHttpRequestPlus
  165. const app = document.querySelector('body'), header = document.querySelector('head')
  166. /**
  167. * 控制面板UI
  168. */
  169. function ControllerTemplate() {
  170. return `
  171. <div class="xml-http-intercepter-controller-template">
  172. <div class="intercepter-controller-button">${ Button() }</div>
  173. <div class="intercepter-controller-content">${ Header() }
  174. <div class="intercepter-controller-main">
  175. <div class="main-header">${ MinHeader() }</div>
  176. ${ DataView() }
  177. </div>
  178. </div>
  179. </div>
  180. `
  181. }
  182. /**
  183. * 按钮
  184. */
  185. function Button() { return `<button onclick="activeWindow()">START</button>` }
  186. //打开关闭调试窗口
  187. window.activeWindow = function activeWindow() { const wins = document.querySelector('.intercepter-controller-content'); wins.classList.toggle('content-show') }
  188. /**
  189. *
  190. * 头部
  191. */
  192. function Header() {
  193. return `
  194. <div class="controller-content-header">
  195. <div class="content-header-left">
  196. <div class="header-left-title">
  197. <h2>WEB - 请求分析大师</h2>
  198. </div>
  199. </div>
  200. <div class="content-header-right">
  201. <div class="header-right-options">
  202. <div class="option-item" onclick="activeWindow()">最小化</div>
  203. <div class="option-item">关于脚本</div>
  204. <div class="option-item">主题</div>
  205. <div class="option-item">设置</div>
  206. </div>
  207. </div>
  208. </div>
  209. `
  210. }
  211. /**
  212. * Main头部
  213. */
  214. function MinHeader() {
  215. return `
  216. <div class="main-header-left">
  217. <button class="main-tab-item main-tab-item-active" onclick="checkDataView(0)">请求拦截-Request</button>
  218. <button class="main-tab-item" onclick="checkDataView(1)" title="使用此功能前请确保进行了请求拦截">响应拦截-Response</button>
  219. </div>
  220. <div class="main-header-right">
  221. <button class="main-tab-item main-tab-item-active" title="默认不进行分析" onclick="checkAction(0)">关闭</button>
  222. <button class="main-tab-item" title="请手动触发网页请求以继续分析" onclick="checkAction(1)">手动分析</button>
  223. <button class="main-tab-item" onclick="checkAction(2)">全盘分析</button>
  224. </div>
  225. `
  226. }
  227. //切换左侧tab Method
  228. window.checkDataView = function checkDataView(n) {
  229. const leftItem = document.querySelector('.main-header-left').querySelectorAll('.main-tab-item')
  230. const dataItem = document.querySelectorAll('.intercepter-item')
  231. dataItem.forEach((element, index) => { leftItem[index].classList.remove('main-tab-item-active'); element.classList.add('item-close') })
  232. leftItem[n].classList.add('main-tab-item-active'); dataItem[n].classList.remove('item-close')
  233. }
  234. //切换右侧tab Method
  235. window.checkAction = function checkAction(n) {
  236. const leftItem = document.querySelector('.main-header-right').querySelectorAll('.main-tab-item')
  237. leftItem.forEach((element, key) => { element.classList.remove('main-tab-item-active') })
  238. leftItem[n].classList.add('main-tab-item-active')
  239. if (n == 0) window.XMLHttpRequest = request; else if (n == 1) handMovementPacketCapture(); else overallPacketCapture()
  240. }
  241. /**
  242. * 信息区域
  243. */
  244. function DataView() {
  245. return `
  246. <div class="intercepter-item">
  247. ${ ResolveTableHeader(
  248. [
  249. 'Method-请求方式', 'Origin-源地址', 'URL-请求地址', 'Params-参数', 'Body-请求体', 'Header-请求头', 'State-当前状态', 'Ready-就绪状态', 'Action-操作'
  250. ]
  251. ) }
  252. </div>
  253. <div class="intercepter-item">
  254. ${ ResolveTableHeader(
  255. [
  256. 'Method-请求方式', 'URL-请求地址', 'Status-状态码', 'Data-响应数据', 'Text-响应文本', 'URL-响应地址', 'XML-响应文档', 'Action-操作'
  257. ]
  258. ) }
  259. </div>
  260. `
  261. }
  262. /**
  263. * 信息表格
  264. */
  265. function ResolveTableHeader(data) {
  266. return `
  267. <table class="intercepter-table">
  268. <tbody>
  269. <tr class="table-header">
  270. <td><input type="checkbox"></td>${ data.map(title => `<td>${ title }</td>`).join('') }
  271. </tr>
  272. </tbody>
  273. </table>
  274. `
  275. }
  276. /**
  277. * 请求拦截行渲染
  278. */
  279. function ResolveRequestRow(option) {
  280. const table = document.querySelector('.intercepter-table')
  281. const { method, url, params, data, header, index, state, ready, invoke } = option
  282. table.insertAdjacentHTML('beforeend', `
  283. <tbody class="info-message">
  284. <tr>
  285. <td><input type="checkbox"></td>
  286. <td><span>${ method }</span></td>
  287. <td>${ url }</td>
  288. <td>${ url }</td>
  289. <td>${ params }</td>
  290. <td>${ data }</td>
  291. <td>${ header }</td>
  292. <td>${ state == 0 ? '劫持中' : '已放行' }</td>
  293. <td>${ ready == 0 ? '劫持中' : '已就绪' }</td>
  294. <td class="actions-button-group">
  295. <button class="error" onclick="removeRows(this.parentNode.parentNode, ${ index }, 'request')">忽略</button>
  296. <button class="info" onclick="openMoreMessage(this.parentNode.parentNode.parentNode)">详情</button>
  297. <button class="info request-next-run">放行</button>
  298. </td>
  299. </tr>
  300. </tbody>
  301. <tbody class="more-about-message">
  302. <tr>
  303. <td colspan="10">
  304. <div class="request-message-container">
  305. <div class="request-url">
  306. <div class="request-message-title"><h5>URL - 请求地址详情</h5></div>
  307. <div class="request-message-content">
  308. <form class="request-message-form">
  309. <li>
  310. <textarea placeholder="请输入请求地址">${ url }</textarea>
  311. </li>
  312. </form>
  313. </div>
  314. </div>
  315. <div class="request-methods">
  316. <div class="request-message-title"><h5>Method - 请求方式详情</h5></div>
  317. <div class="request-message-content">
  318. <input
  319. class="data-list-select"
  320. list="data-list-${ index }"
  321. value="${ method }"
  322. placeholder="请选择方式"
  323. >
  324. <datalist id="data-list-${ index }">
  325. <option>Get</option>
  326. <option>Post</option>
  327. <option>Put</option>
  328. <option>Delete</option>
  329. <option>Path</option>
  330. </datalist>
  331. </div>
  332. </div>
  333. <div class="request-params">
  334. <div class="request-message-title"><h5>Params - 参数详情</h5></div>
  335. <div class="request-message-content">
  336. <form class="request-message-form">
  337. <li>
  338. <textarea autoHeight="true" placeholder="请编形式参数">${ JSON.stringify(params) }</textarea>
  339. </li>
  340. </form>
  341. </div>
  342. </div>
  343. <div class="request-header">
  344. <div class="request-message-title"><h5>Header - 请求头详情</h5></div>
  345. <div class="request-message-content">
  346. <form class="request-message-form">
  347. <li>
  348. <textarea autoHeight="true" placeholder="请编辑请求头">${ JSON.stringify(header) }</textarea>
  349. </li>
  350. </form>
  351. </div>
  352. </div>
  353. <div class="request-data">
  354. <div class="request-message-title"><h5>Body - 请求体详情</h5></div>
  355. <div class="request-message-content">
  356. <form class="request-message-form">
  357. <li>
  358. <textarea autoHeight="true" placeholder="请输入请求体参数">${ data }</textarea>
  359. </li>
  360. </form>
  361. </div>
  362. </div>
  363. <div class="request-invoke">
  364. <div class="request-message-title"><h5>Invoke - 功能详情</h5></div>
  365. <div class="request-message-content">
  366. <form class="request-message-form">
  367. <li>
  368. <textarea autoHeight="true" placeholder="请输入具体实现代码">${ invoke }</textarea>
  369. </li>
  370. </form>
  371. </div>
  372. </div>
  373. <div class="xml-http-request-more-message-footer">
  374. <button>确认修改</button>
  375. </div>
  376. </div>
  377. </td>
  378. </tr>
  379. </tbody>
  380. `)
  381. }
  382. /**
  383. * 响应拦截行渲染
  384. */
  385. function ResolveResponseRow(option) {
  386. const table = document.querySelectorAll('.intercepter-table')[1]
  387. const { method, status, url, response, responseText, responseXML, responseURL, index } = option
  388. table.insertAdjacentHTML('beforeend', `
  389. <tbody class="info-message">
  390. <tr>
  391. <td><input type="checkbox"></td>
  392. <td><span>${ method }</span></td>
  393. <td>${ url }</td>
  394. <td>${ status }</td>
  395. <td>${ response }</td>
  396. <td>${ responseText }</td>
  397. <td>${ responseURL }</td>
  398. <td>${ responseXML }</td>
  399. <td class="actions-button-group">
  400. <button class="error" onclick="removeRows(this.parentNode.parentNode, ${ index }, 'response')">忽略</button>
  401. <button class="info" onclick="openMoreMessage(this.parentNode.parentNode.parentNode)">详情</button>
  402. <button class="info response-next-run">放行</button>
  403. </td>
  404. </tr>
  405. </tbody>
  406. <tbody class="more-about-message">
  407. <tr>
  408. <td colspan="9">
  409. <div class="request-message-container">
  410. <div class="response-url">
  411. <div class="request-message-title"><h5>URL - 响应地址</h5></div>
  412. <div class="request-message-content">
  413. <form class="request-message-form">
  414. <li>
  415. <textarea placeholder="请输入请求地址">${ url }</textarea>
  416. </li>
  417. </form>
  418. </div>
  419. </div>
  420. <div class="response-status">
  421. <div class="request-message-title"><h5>Status - 响应码</h5></div>
  422. <div class="request-message-content">
  423. <input class="data-list-select" value="${ status }" placeholder="请输入状态码">
  424. </div>
  425. </div>
  426. <div class="response-data">
  427. <div class="request-message-title"><h5>Response - 响应数据</h5></div>
  428. <div class="request-message-content">
  429. <form class="request-message-form">
  430. <li>
  431. <textarea autoHeight="true" placeholder="请输入响应参数">${ response }</textarea>
  432. </li>
  433. </form>
  434. </div>
  435. </div>
  436. <div class="response-text">
  437. <div class="request-message-title"><h5>Text - 响应文本</h5></div>
  438. <div class="request-message-content">
  439. <form class="request-message-form">
  440. <li>
  441. <textarea autoHeight="true" placeholder="请输入响应文本">${ responseText }</textarea>
  442. </li>
  443. </form>
  444. </div>
  445. </div>
  446. <div class="response-url">
  447. <div class="request-message-title"><h5>URL - 响应地址</h5></div>
  448. <div class="request-message-content">
  449. <form class="request-message-form">
  450. <li>
  451. <textarea autoHeight="true" placeholder="请输入响应地址参数">${ url }</textarea>
  452. </li>
  453. </form>
  454. </div>
  455. </div>
  456. <div class="response-xml">
  457. <div class="request-message-title"><h5>XML - 响应文档</h5></div>
  458. <div class="request-message-content">
  459. <form class="request-message-form">
  460. <li>
  461. <textarea autoHeight="true" placeholder="请写入响应文档">${ responseXML }</textarea>
  462. </li>
  463. </form>
  464. </div>
  465. </div>
  466. <div class="xml-http-response-more-message-footer">
  467. <button>确认修改</button>
  468. </div>
  469. </div>
  470. </td>
  471. </tr>
  472. </tbody>
  473. `)
  474. }
  475. //展开详细信息
  476. window.openMoreMessage = function openMoreMessage(parent) { const nextElement = parent.nextElementSibling; nextElement.classList.toggle('more-message-show') }
  477. app.insertAdjacentHTML('beforeend', ControllerTemplate())
  478. // Methods
  479. // 自动滚动
  480. function autoScroll() {
  481. const contariner = document.querySelectorAll('.intercepter-item'), isActive = [...contariner].findIndex((em => !em.classList.contains('item-close')))
  482. contariner[isActive].scrollBy({ top: contariner[0].scrollHeight, left: 0, behavior: 'smooth' })
  483. }
  484. //删除一行
  485. window.removeRows = function removeRows(node, index, how) {
  486. const root = node, nextNode = node.parentNode.nextElementSibling, parentRoot = nextNode.parentNode
  487. root.classList.add('row-item-remove')
  488. how === 'request' ? delete RequestQueue[index] : delete ResponseQueue[index]
  489. setTimeout(() => { parentRoot.removeChild(nextNode); parentRoot.removeChild(root.parentNode) }, 380)
  490. }
  491. //改变请求方式
  492. window.changeMethods = function changeMethods(element) {
  493. alert(element.value)
  494. }
  495. //一些初始化启动
  496. checkDataView(0)
  497. /**
  498. * 样式
  499. */
  500. const STYLE = `
  501. <style>
  502. .xml-http-intercepter-controller-template {
  503. --bgcolor: rgb(157, 0, 255)/*rgb(255, 76, 76)*/
  504. }
  505. .xml-http-intercepter-controller-template * {
  506. margin: 0;
  507. padding: 0
  508. }
  509. .intercepter-controller-button button {
  510. position: fixed;
  511. right: 2vw;
  512. top: 50%;
  513. width: 3.8vw;
  514. height: 3.8vw;
  515. background: rgb(157, 0, 255);
  516. border-top: solid .3vw rgb(105, 105, 105);
  517. border-bottom: solid .3vw rgb(105, 105, 105);
  518. border-left: dashed .3vw rgb(105, 105, 105);
  519. border-right: dashed .3vw rgb(105, 105, 105);
  520. border-radius: 50%;
  521. outline: none;
  522. color: rgb(84, 84, 84);
  523. font-size: .8vw;
  524. transform: translateY(-50%);
  525. transition: .5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  526. cursor: pointer;
  527. z-index: 9999
  528. }
  529. .controller-content-header {
  530. padding: 1vw;
  531. background: var(--bgcolor);
  532. display: flex;
  533. justify-content: space-between;
  534. align-items: center;
  535. color: white;
  536. box-shadow: 0 0 1vw #ccc
  537. }
  538. .header-right-options {
  539. display: flex;
  540. justify-content: space-between;
  541. align-items: center;
  542. gap: 1vw
  543. }
  544. .option-item {
  545. cursor: pointer
  546. }
  547. .intercepter-controller-button:hover button {
  548. border-top: solid .3vw rgb(157, 0, 255);
  549. border-bottom: solid .3vw rgb(157, 0, 255);
  550. border-left: dashed .3vw rgb(157, 0, 255);
  551. border-right: dashed .3vw rgb(157, 0, 255);
  552. box-shadow: 0 0 .8vw rgb(157, 0, 255)
  553. }
  554. .intercepter-controller-content {
  555. position: fixed;
  556. top: 50%;
  557. left: 50%;
  558. width: 90vw;
  559. background: white;
  560. transition: .3s;
  561. transform: translate(-50%, -50%) scale(0);
  562. box-shadow: 0 0 5vw #ccc;
  563. border-radius: .3vw;
  564. overflow: hidden;
  565. z-index: 9999
  566. }
  567. .content-show {
  568. transform: translate(-50%, -50%) scale(1)
  569. }
  570. .intercepter-controller-main {
  571. padding: .5vw 1vw;
  572. height: 75vh
  573. }
  574. .main-header {
  575. display: flex;
  576. justify-content: space-between;
  577. align-items: center
  578. }
  579. .main-header-left .main-tab-item, .main-header-right .main-tab-item {
  580. padding: .5vw;
  581. background: white;
  582. border: solid .05vw var(--bgcolor);
  583. outline: none;
  584. cursor: pointer;
  585. font-size: .8vw;
  586. border-radius: .1vw;
  587. color: var(--bgcolor);
  588. transition: .5s
  589. }
  590. .main-header-right .main-tab-item:hover,
  591. .main-header-right .main-tab-item-active,
  592. .main-header-left .main-tab-item:hover,
  593. .main-header-left .main-tab-item-active {
  594. background: var(--bgcolor);
  595. color: white
  596. }
  597. .intercepter-item {
  598. padding-bottom: 1.5vw;
  599. height: calc(73.5vh - 1.5vw);
  600. overflow-y: auto
  601. }
  602. .intercepter-item::-webkit-scrollbar{
  603. appearance: none ; width: .3vw ; background: rgba(255, 255, 255, 0.6)
  604. }
  605. .intercepter-item::-webkit-scrollbar-thumb {
  606. appearance: none ; width: 100% ; background: rgba(161, 161, 161, 0.6) ; border-radius: .15rem
  607. }
  608. .intercepter-table {
  609. margin: 1vw 0;
  610. width: 100%;
  611. border-collapse: collapse;
  612. border-spacing: 0;
  613. ovflow: hidden
  614. }
  615. .intercepter-table .table-header {
  616. position: sticky;
  617. top: 0;
  618. background: rgb(246, 246, 246)
  619. }
  620. .intercepter-table .info-message {
  621. }
  622. .intercepter-table .info-message tr {
  623. position: sticky;
  624. top: 3vw;
  625. left: 0;
  626. background: white;
  627. transition: .4s
  628. }
  629. .row-item-remove {
  630. opacity: 0;
  631. height: .5vh;
  632. transform: translateX(-70%)
  633. }
  634. .intercepter-table .info-message tr:hover {
  635. background: linear-gradient(to right, white, var(--bgcolor), white)
  636. }
  637. .intercepter-table td {
  638. padding: 1vw;
  639. max-width: 6vw;
  640. white-space: nowrap;
  641. text-overflow: ellipsis;
  642. overflow: hidden;
  643. font-size: .8vw;
  644. transition: .3s
  645. }
  646. .actions-button-group button {
  647. margin: 0 .2vw;
  648. background: transparent;
  649. border: none;
  650. outline: none;
  651. cursor: pointer
  652. }
  653. .actions-button-group .error {
  654. color: red
  655. }
  656. .actions-button-group .info {
  657. color: var(--bgcolor)
  658. }
  659. .more-about-message {
  660. background: rgb(246, 246, 246)
  661. }
  662. .intercepter-table .more-about-message {
  663. border: none
  664. }
  665. .more-about-message td {
  666. padding: 0
  667. }
  668. .request-message-container {
  669. max-height: 0vh;
  670. overflow: hidden;
  671. display: flex;
  672. flex-flow: column nowrap;
  673. justify-content: space-between;
  674. gap: 1vw
  675. }
  676. .more-message-show td {
  677. padding: 1vw
  678. }
  679. .more-message-show .request-message-container {
  680. max-height: 1000vh
  681. }
  682. .request-message-content {
  683. margin: 1vw 0
  684. }
  685. .request-message-form {
  686. list-style: none;
  687. display: flex;
  688. flex-flow: row wrap;
  689. gap: 2vw
  690. }
  691. .request-message-form li {
  692. margin: .5vw 0;
  693. flex: auto;
  694. display: flex;
  695. justify-content: space-between;
  696. align-items: center;
  697. gap: .5vw
  698. }
  699. .request-message-form li textarea {
  700. padding: .5vw;
  701. width: 100%;
  702. height: auto;
  703. border-radius: .2vw;
  704. border: solid .05vw #ccc;
  705. outline: none
  706. }
  707. .data-list-select, .request-message-form li input {
  708. padding: .5vw;
  709. border-radius: .2vw;
  710. min-width: 3.5vw;
  711. height: 1.5vh;
  712. border: solid .05vw #ccc;
  713. outline: none
  714. }
  715. .item-show {
  716. display: block
  717. }
  718. .item-close {
  719. display: none
  720. }
  721. .xml-http-request-more-message-footer button, .xml-http-response-more-message-footer button {
  722. padding: .5vw 1vw;
  723. background: var(--bgcolor);
  724. border: none;
  725. border-radius: .2vw;
  726. outline: none;
  727. color: white;
  728. cursor: pointer
  729. }
  730. </style>
  731. `
  732. header.insertAdjacentHTML('beforeend', STYLE)

还想看啥啊?没啦,该吃吃该喝喝

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/969351
推荐阅读
相关标签
  

闽ICP备14008679号