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

//开始编写我们的中间件类
class XMLHTTPRequestMiddle { 
  constructor() { }

  open(method, url) { 
    console.log('请求地址:', url, '请求方式', method)
  }

  send(data) { 
    console.log('我是请求体', data)
  }
}

//我们将XMLHTTPRequest指向到我们自己写的类
window.XMLHttpRequest = XMLHTTPRequestMiddle

 index.html

<script src="/middle.js"></script>
<script>

  const request = new XMLHttpRequest()

  request.open('get', 'http://localhost:8060/test')

  request.send()

</script>

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

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping
    public Map<String, String> test() {
        HashMap<String, String> result = new HashMap<>();
        result.put("code", "200");
        result.put("message", "success");
        result.put("data", "no data");
        return result;
    }
}

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

一、onreadystatechange方法是什么?

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

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

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

class XMLHTTPRequestMiddle { 
  constructor() { }

  open(method, url) { 
    console.log('请求地址:', url, '请求方式', method)
  }

  send(data) { 
    console.log('我是请求体', data)
  }

  //加入同名setter
  set onreadystatechange(callback) {
    //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
    console.log(callback)
  }
}

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

//其余代码略
constructor() { 
    //我们定义一个callback属性 来映射真正的onreadystatechange 这样可以拿到两个参数并执行
    this.callback = null
}

//加入同名setter
set onreadystatechange(callback) {
    //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
    //console.log(callback)
    if (typeof callback == 'function') {
        this.callback = callback
    } else {
        throw new Error('不是函数憋来沾边')
    }
}

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

三、请求收集

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

//在全局定义请求队列 保存每次完整实例后的请求体
const requestQueue = []

//保留一份本体
const xmlhttp = XMLHttpRequest

//------------------------------------------------------------------------------------------

//其余代码略 在构造加入
constructor() { 
    this.callback = null

    //加入一个载体 来收集每次实例的请求体 他与requestQueue 紧密联系 为什么写一个header空对象在内
    //对于空对象的顶层添加 如:this.polay.url = 'xxx'不会报错
    //但对于次要层添加就会报错 如:this.polay.header.ContentType = 'json' ==> error...
    //因为他会认为已经存在header对象,会直接从header对象添加属性 但我们知道请求体内的请求头是个属性很多的对象
    this.polay = { header: {} }

    //加入Ajax本体来控制请求的真正发送 很关键
    this.request = new xmlhttp()
}

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

//在全局定义请求队列 保存每次完整实例后的请求体
const requestQueue = []

//保留一份本体
const xmlhttp = XMLHttpRequest

//开始编写我们的中间件类
class XMLHTTPRequestMiddle { 
 constructor() { 
  this.callback = null

  //加入一个载体 来收集每次实例的请求体 他与requestQueue 紧密联系 为什么写一个header空对象在内
  //对于空对象的顶层添加 如:this.polay.url = 'xxx'不会报错
  //但对于次要层添加就会报错 如:this.polay.header.ContentType = 'json' ==> error...
  //因为他会认为已经存在header对象,会直接从header对象添加属性 但我们知道请求体内的请求头是个属性很多的对象
  this.polay = { header: {} }

  //加入Ajax本体来控制请求的真正发送 很关键
  this.request = new xmlhttp()

  //加入responseText的映射
  this.responseText = null

  //其他属性 原生Ajax有什么加什么 这里暂时用上面若干属性做演示
}

  open(method, url) { 
    this.polay.url = url
    this.polay.method = method 
  }
  
  //send总是在最后执行的 因此在这里 请求体已经收集完了 可以加入队列了
  send(data) {  
    this.polay.body = data

    //这里是修改请求request的关键位置 写个小例子 比如修改请求方式Method
    const method = prompt(`请输入要修改的method方法,目前是${ this.polay.method }`)
    //达到修改效果
    this.polay.method = method

    //加入队列 现在明白为什么写个载体了吗?
    requestQueue.push(this.polay)

    //打印队列看是否抓取成功
    console.log(`request is`)
    console.log(requestQueue)
  }

  //加入同名setter
  set onreadystatechange(callback) {
    //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
    //console.log(callback)
    if (typeof callback == 'function') {
        this.callback = callback
    } else {
       throw new Error('不是函数憋来沾边')
    }
  }

  //设置请求头的方法 原生的 我们重写
  setRequestHeader(key, value) {
    this.polay.header[key] = value
  }
}

//我们将XMLHTTPRequest指向到我们自己写的类
window.XMLHttpRequest = XMLHTTPRequestMiddle

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

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

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

//在全局定义请求队列 保存每次完整实例后的请求体
const requestQueue = []

//保留一份本体
const xmlhttp = XMLHttpRequest

//开始编写我们的中间件类
class XMLHTTPRequestMiddle { 
 constructor() { 
  this.callback = null

  //加入一个载体 来收集每次实例的请求体 他与requestQueue 紧密联系 为什么写一个header空对象在内
  //对于空对象的顶层添加 如:this.polay.url = 'xxx'不会报错
  //但对于次要层添加就会报错 如:this.polay.header.ContentType = 'json' ==> error...
  //因为他会认为已经存在header对象,会直接从header对象添加属性 但我们知道请求体内的请求头是个属性很多的对象
  this.polay = { header: {} }

  //加入Ajax本体来控制请求的真正发送 很关键
  this.request = new xmlhttp()

  //加入responseText的映射
  this.responseText = null

  //其他属性 原生Ajax有什么加什么 这里暂时用上面若干属性做演示
}

  open(method, url) { 
    this.polay.url = url
    this.polay.method = method 
  }
  
  //send总是在最后执行的 因此在这里 请求体已经收集完了 可以加入队列了
  send(data) {  
    this.polay.body = data

    //这里是修改请求request的关键位置 写个小例子 比如修改请求方式Method
    const method = prompt(`请输入要修改的method方法,目前是${ this.polay.method }`)
    //达到修改效果
    this.polay.method = method

    //加入队列 现在明白为什么写个载体了吗?
    requestQueue.push(this.polay)

    //打印队列看是否抓取成功
    console.log(`request is`)
    console.log(requestQueue)

    //从这里开始发送请求 已经是通过上述修改过的request 我们已经实现拦截并修改request了
    this.request.open(this.polay.method, this.polay.url)
    this.request.send()

    //这里更关键 终于到我们一开始讲的那个参数那里了 在这一步可以解决了 这里决定是否执行回调
    this.request.onreadystatechange = (event) => {
        //暂时映射一种返回结果类型
        this.responseText = this.request.responseText
        this.callback(event) //参数问题完美传入
    }
  }


  //加入同名setter
  set onreadystatechange(callback) {
    //注意 setter内的参数 就是我们上图写的页面输出回调 打印就一目了然了
    //console.log(callback)
    if (typeof callback == 'function') {
        this.callback = callback
    } else {
       throw new Error('不是函数憋来沾边')
    }
  }

  //设置请求头的方法 原生的 我们重写
  setRequestHeader(key, value) {
    this.polay.header[key] = value
  }
}

//我们将XMLHTTPRequest指向到我们自己写的类
window.XMLHttpRequest = XMLHTTPRequestMiddle
  <script src="/middle.js"></script>
  <script>

    const request = new XMLHttpRequest()

    request.open('get', 'http://localhost:8060/test')

    request.send()

    request.onreadystatechange = (http, event) => {
      document.write(request.responseText)
      
      console.log(event)
    }

  </script>

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

//全局加入响应队列
const responstQueue = []

//其余代码略
send(data) {  
    this.polay.body = data

    //这里是修改请求request的关键位置 写个小例子 比如修改请求方式Method
    const method = prompt(`请输入要修改的method方法,目前是${ this.polay.method }`)
    //达到修改效果
    this.polay.method = method

    //加入队列 现在明白为什么写个载体了吗?
    requestQueue.push(this.polay)

    //打印队列看是否抓取成功
    console.log(`request is`)
    console.log(requestQueue)

    //从这里开始发送请求 已经是通过上述修改过的request 我们已经实现拦截并修改request了
    this.request.open(this.polay.method, this.polay.url)
    this.request.send()

    //这里更关键 终于到我们一开始讲的那个参数那里了 在这一步可以解决了 这里决定是否执行回调
    this.request.onreadystatechange = (event) => {
        //暂时映射一种返回结果类型
        this.responseText = this.request.responseText

        //这里决定拦截响应体
        const responst = prompt('请输入新的data')
        //简陋模拟修改
        this.request.responseText = '新的数据'

        //最终输出到页面的是你已经修改过得数据
        this.callback(event) //参数问题完美传入
    }
}

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

 四、现成代码(脚本)

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

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

/**
 * XMLHTTPRequest 拦截工具
 *
 * 大致思路 手写虚拟XMLHTTPRequest类 让真正的XMLHTTPRequest指向我们自己写的类
 * 再把一系列方法 请求地址 请求头 response预先收集起来集中做处理 最后再触发由各大网站自定义的回调函数实现他们的业务
 * 但是数据已经经过我们的加工处理了 Understand?
 *
 * 因为是全自动脚本 我们不得不把Html也集成到了js代码中混合开发 核心代码不多 Html他么的多啊
 */

const request = XMLHttpRequest

//请求拦截队列
const RequestQueue  = []

//响应拦截队列
const ResponseQueue = []

class XMLHttpRequestPlus {
  constructor() {
    this.request         = new request()
    //承载体 修改数据关键
    this.paloy           = { header: {} }
    //核心方法 决定可不可以实现响应拦截
    this.requestCallback = null
    this.status          = 0
    this.readyState      = 0
    this.response        = null
    this.responseText    = null
    this.responseURL     = null
    this.responseXML     = null
    this.responseType    = ''
    this.timeout         = 1*60*60*24 //超时时间设为一天 强制解除各大网站超时不再有响应的安全策略
  }

  open(method, url) {
    this.paloy.method = method
    this.paloy.url    = url
    this.paloy.params = url.indexOf('?') != -1 ? url.substring(url.indexOf('?') + 1).split('&').reduce(
      (obj, item) => {
        const keyVal = item.split('=')
        if (keyVal.length == 1) { obj[keyVal[0]] = '' } else { obj[keyVal[0]] = keyVal[1] }
        return obj
      }, {}) : null
  }

  //请求拦截核心部分
  send(body) {
    if(this.timeout < 1*60*60*24) this.timeout = 1*60*60*24 //防止对超时时间的更改
    this.paloy.data   = body
    this.paloy.state  = this.request.status
    this.paloy.ready  = this.request.readyState
    this.paloy.index  = RequestQueue.length
    this.paloy.invoke = this.requestCallback
    //加入受理队列
    RequestQueue.push(this.paloy)
    //渲染请求拦截表格
    ResolveRequestRow(this.paloy)
    //自动滚动到底部
    autoScroll()
    //在放行之前还有一件事要做 确认用户修改我们的请求体
    const xmlConfrimBtn = [...document.querySelectorAll('.xml-http-request-more-message-footer')]
    xmlConfrimBtn.forEach((element, key) => {
      element.querySelector('button').onclick = () => {
        const { parentNode } = element.parentNode
        const url    = parentNode.querySelector('.request-url').querySelector('textarea').value
        const method = parentNode.querySelector('.data-list-select').value
        const header = parentNode.querySelector('.request-header').querySelector('textarea').value
        const params = parentNode.querySelector('.request-params').querySelector('textarea').value
        const invoke = parentNode.querySelector('.request-invoke').querySelector('textarea').value
        try {
          const paramsed = JSON.parse(params)
          RequestQueue[key].url = url.indexOf('?') != -1 ? `${ url }&${ Object.entries(paramsed).map(([key, value]) => `${ key }=${ value }`).join('&') }`
            : `${url}?${Object.entries(paramsed).map(([key, value]) => `${ key }=${ value }`).join('&') }`
        } catch (error) {
          RequestQueue[key].url = url
        }
        console.log(url)
        RequestQueue[key].method = method
        RequestQueue[key].header = JSON.parse(header)
        RequestQueue[key].params = JSON.parse(params)
        //暂不开放修改回调功能 因为目前没有完美的修改方案
        //this.requestCallback = new Function(invoke)
      }
    })
    //放行事件 也是核心部分 决定了能不能拿到AJAX的状态以及返回值
    const nextRun = [...document.querySelectorAll('.request-next-run')]
    nextRun.forEach((element, key) => {
      element.onclick = () => {
        this.request.open(RequestQueue[key].method, RequestQueue[key].url)
        //这里放行请求头
        //this.request.setRequestHeader('Content-Type', 'application/json')
        Object.entries(RequestQueue[key].header).forEach(([key, value]) => { this.request.setRequestHeader(key, value) })
        this.request.send(body)
        this.request.onreadystatechange = (event) => {
          //先将response托管到响应队列保管 以便劫持修改
          if (this.request.status >= 200 && this.request.readyState == 4) {
            ResponseQueue.push({
              method       : this.paloy.method,
              url          : this.paloy.url,
              status       : this.request.status,
              readyState   : this.request.readyState,
              response     : this.request.response,
              responseText : this.request.responseText,
              responseURL  : this.request.responseURL,
              responseXML  : this.request.responseXML
            })
            //紧接着执行响应拦截表格
            ResolveResponseRow(ResponseQueue[ResponseQueue.length - 1])
          }
          //响应数据修改
          const setResponseDataBtn = [...document.querySelectorAll('.xml-http-response-more-message-footer')]
          setResponseDataBtn.forEach((element, key) => {
            element.querySelector('button').onclick = () => {
              const { parentNode } = element.parentNode
              const status = parentNode.querySelector('.response-status').querySelector('input').value
              const data   = parentNode.querySelector('.response-data').querySelector('textarea').value
              const text   = parentNode.querySelector('.response-text').querySelector('textarea').value
              ResponseQueue[key].status       = status
              ResponseQueue[key].response     = data
              ResponseQueue[key].responseText = text
            }
          })
          //响应拦截放行
          const nextRun = [...document.querySelectorAll('.response-next-run')]
          nextRun.forEach((element, key) => {
            element.onclick = () => {
              const { method, url, status, readyState, response, responseText, responseURL, responseXML, index } = ResponseQueue[key]
              this.status = status
              this.readyState = readyState
              this.response = this.responseType == 'json' ? JSON.parse(response) : response
              this.responseText = responseText
              this.responseURL = responseURL
              this.responseXML = responseXML
              //最终返回响XMLHTTPRequest接收处理
              this.requestCallback(event)
              //放行之后此处响应就没用了 不能再次响应给XMLHTTPRequest 所以要移除掉DOM
              removeRows(element.parentNode.parentNode, index)
            }
          })
        }
      }
    })
  }

  setRequestHeader(name, value) { this.paloy.header[name] = value }

  getResponseHeader(name) { return this.request.getResponseHeader(name) }

  getAllResponseHeaders() { return this.request.getAllResponseHeaders() }

  overrideMimeType(mime) { this.request.overrideMimeType(mime) }

  async abort() {
    //alert(`检测到${ (await RequestQueue)[RequestQueue.length - 1]['url'] }试图终止请求来保护网站,已被分析大师驳回`)
  }

  set ontimeout(func) { return }

  set onreadystatechange(func) { if (typeof func == 'function') this.requestCallback = func }
}


//切换为手动抓包
window.handMovementPacketCapture = function handMovementPacketCapture() { window.XMLHttpRequest = XMLHttpRequestPlus }

/*
* !这里请注意 如果想嵌入脚本即抓取 请打开下面的注释
*/
//  window.XMLHttpRequest = XMLHttpRequestPlus

const app = document.querySelector('body'), header = document.querySelector('head')


/**
 * 控制面板UI
 */
function ControllerTemplate() {
  return  `
    <div class="xml-http-intercepter-controller-template">
      <div class="intercepter-controller-button">${ Button() }</div>
      <div class="intercepter-controller-content">${ Header() }
        <div class="intercepter-controller-main">
          <div class="main-header">${ MinHeader() }</div>
          ${ DataView() }
        </div>
      </div>
    </div>
  `
}


/**
 * 按钮
 */
function Button() { return `<button onclick="activeWindow()">START</button>` }


//打开关闭调试窗口
window.activeWindow = function activeWindow() { const wins = document.querySelector('.intercepter-controller-content'); wins.classList.toggle('content-show') }


/**
 *
 * 头部
 */
function Header() {
  return `
    <div class="controller-content-header">
      <div class="content-header-left">
        <div class="header-left-title">
          <h2>WEB - 请求分析大师</h2>
        </div>
      </div>
      <div class="content-header-right">
        <div class="header-right-options">
          <div class="option-item" onclick="activeWindow()">最小化</div>
          <div class="option-item">关于脚本</div>
          <div class="option-item">主题</div>
          <div class="option-item">设置</div>
        </div>
      </div>
    </div>
  `
}

/**
 * Main头部
 */
function MinHeader() {
  return `
    <div class="main-header-left">
      <button class="main-tab-item main-tab-item-active" onclick="checkDataView(0)">请求拦截-Request</button>
      <button class="main-tab-item" onclick="checkDataView(1)" title="使用此功能前请确保进行了请求拦截">响应拦截-Response</button>
    </div>
    <div class="main-header-right">
      <button class="main-tab-item main-tab-item-active" title="默认不进行分析" onclick="checkAction(0)">关闭</button>
      <button class="main-tab-item" title="请手动触发网页请求以继续分析" onclick="checkAction(1)">手动分析</button>
      <button class="main-tab-item" onclick="checkAction(2)">全盘分析</button>
    </div>
  `
}


//切换左侧tab Method
window.checkDataView = function checkDataView(n) {
  const leftItem = document.querySelector('.main-header-left').querySelectorAll('.main-tab-item')
  const dataItem = document.querySelectorAll('.intercepter-item')

  dataItem.forEach((element, index) => { leftItem[index].classList.remove('main-tab-item-active'); element.classList.add('item-close') })

  leftItem[n].classList.add('main-tab-item-active'); dataItem[n].classList.remove('item-close')
}


//切换右侧tab Method
window.checkAction = function checkAction(n) {
  const leftItem = document.querySelector('.main-header-right').querySelectorAll('.main-tab-item')

  leftItem.forEach((element, key) => { element.classList.remove('main-tab-item-active') })

  leftItem[n].classList.add('main-tab-item-active')

  if (n == 0) window.XMLHttpRequest = request; else if (n == 1) handMovementPacketCapture(); else overallPacketCapture()
}


/**
 * 信息区域
 */
function DataView() {
  return `
    <div class="intercepter-item">
    ${ ResolveTableHeader(
      [
        'Method-请求方式', 'Origin-源地址', 'URL-请求地址', 'Params-参数', 'Body-请求体', 'Header-请求头', 'State-当前状态', 'Ready-就绪状态', 'Action-操作'
      ]
    ) }
    </div>

    <div class="intercepter-item">
    ${ ResolveTableHeader(
      [
        'Method-请求方式', 'URL-请求地址', 'Status-状态码', 'Data-响应数据', 'Text-响应文本', 'URL-响应地址', 'XML-响应文档', 'Action-操作'
      ]
    ) }
    </div>
  `
}


/**
 * 信息表格
 */
function ResolveTableHeader(data) {
  return `
    <table class="intercepter-table">
      <tbody>
        <tr class="table-header">
          <td><input type="checkbox"></td>${ data.map(title => `<td>${ title }</td>`).join('') }
        </tr>
      </tbody>
    </table>
  `
}


/**
 * 请求拦截行渲染
 */
function ResolveRequestRow(option) {
  const table = document.querySelector('.intercepter-table')
  const { method, url, params, data, header, index, state, ready, invoke } = option
  table.insertAdjacentHTML('beforeend', `
    <tbody class="info-message">
      <tr>
        <td><input type="checkbox"></td>
        <td><span>${ method }</span></td>
        <td>${ url }</td>
        <td>${ url }</td>
        <td>${ params }</td>
        <td>${ data }</td>
        <td>${ header }</td>
        <td>${ state == 0 ? '劫持中' : '已放行' }</td>
        <td>${ ready == 0 ? '劫持中' : '已就绪' }</td>
        <td class="actions-button-group">
          <button class="error" onclick="removeRows(this.parentNode.parentNode, ${ index }, 'request')">忽略</button>
          <button class="info" onclick="openMoreMessage(this.parentNode.parentNode.parentNode)">详情</button>
          <button class="info request-next-run">放行</button>
        </td>
      </tr>
    </tbody>
    <tbody class="more-about-message">
      <tr>
        <td colspan="10">
          <div class="request-message-container">
            <div class="request-url">
              <div class="request-message-title"><h5>URL - 请求地址详情</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea placeholder="请输入请求地址">${ url }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="request-methods">
              <div class="request-message-title"><h5>Method - 请求方式详情</h5></div>
              <div class="request-message-content">
                <input
                class="data-list-select"
                list="data-list-${ index }"
                value="${ method }"
                placeholder="请选择方式"
                >
                <datalist id="data-list-${ index }">
                  <option>Get</option>
                  <option>Post</option>
                  <option>Put</option>
                  <option>Delete</option>
                  <option>Path</option>
                </datalist>
              </div>
            </div>
            <div class="request-params">
              <div class="request-message-title"><h5>Params - 参数详情</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请编形式参数">${ JSON.stringify(params) }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="request-header">
              <div class="request-message-title"><h5>Header - 请求头详情</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请编辑请求头">${ JSON.stringify(header) }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="request-data">
              <div class="request-message-title"><h5>Body - 请求体详情</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请输入请求体参数">${ data }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="request-invoke">
              <div class="request-message-title"><h5>Invoke - 功能详情</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请输入具体实现代码">${ invoke }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="xml-http-request-more-message-footer">
              <button>确认修改</button>
            </div>
          </div>
        </td>
      </tr>
    </tbody>
  `)
}

/**
 * 响应拦截行渲染
 */
function ResolveResponseRow(option) {
  const table = document.querySelectorAll('.intercepter-table')[1]
  const { method, status, url, response, responseText, responseXML, responseURL, index } = option
  table.insertAdjacentHTML('beforeend', `
    <tbody class="info-message">
      <tr>
        <td><input type="checkbox"></td>
        <td><span>${ method }</span></td>
        <td>${ url }</td>
        <td>${ status }</td>
        <td>${ response }</td>
        <td>${ responseText }</td>
        <td>${ responseURL }</td>
        <td>${ responseXML }</td>
        <td class="actions-button-group">
          <button class="error" onclick="removeRows(this.parentNode.parentNode, ${ index }, 'response')">忽略</button>
          <button class="info" onclick="openMoreMessage(this.parentNode.parentNode.parentNode)">详情</button>
          <button class="info response-next-run">放行</button>
        </td>
      </tr>
    </tbody>
    <tbody class="more-about-message">
      <tr>
        <td colspan="9">
          <div class="request-message-container">
            <div class="response-url">
              <div class="request-message-title"><h5>URL - 响应地址</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea placeholder="请输入请求地址">${ url }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="response-status">
              <div class="request-message-title"><h5>Status - 响应码</h5></div>
              <div class="request-message-content">
                <input class="data-list-select" value="${ status }" placeholder="请输入状态码">
              </div>
            </div>
            <div class="response-data">
              <div class="request-message-title"><h5>Response - 响应数据</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请输入响应参数">${ response }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="response-text">
              <div class="request-message-title"><h5>Text - 响应文本</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请输入响应文本">${ responseText }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="response-url">
              <div class="request-message-title"><h5>URL - 响应地址</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请输入响应地址参数">${ url }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="response-xml">
              <div class="request-message-title"><h5>XML - 响应文档</h5></div>
              <div class="request-message-content">
                <form class="request-message-form">
                  <li>
                    <textarea autoHeight="true" placeholder="请写入响应文档">${ responseXML }</textarea>
                  </li>
                </form>
              </div>
            </div>
            <div class="xml-http-response-more-message-footer">
              <button>确认修改</button>
            </div>
          </div>
        </td>
      </tr>
    </tbody>
  `)
}


//展开详细信息
window.openMoreMessage = function openMoreMessage(parent) { const nextElement = parent.nextElementSibling; nextElement.classList.toggle('more-message-show') }


app.insertAdjacentHTML('beforeend', ControllerTemplate())

// Methods
// 自动滚动
function autoScroll() {
  const contariner = document.querySelectorAll('.intercepter-item'), isActive = [...contariner].findIndex((em => !em.classList.contains('item-close')))

  contariner[isActive].scrollBy({ top: contariner[0].scrollHeight, left: 0, behavior: 'smooth' })
}

//删除一行
window.removeRows = function removeRows(node, index, how) {
  const root = node, nextNode = node.parentNode.nextElementSibling, parentRoot = nextNode.parentNode
  root.classList.add('row-item-remove')

  how === 'request' ? delete RequestQueue[index] : delete ResponseQueue[index]

  setTimeout(() => { parentRoot.removeChild(nextNode); parentRoot.removeChild(root.parentNode) }, 380)
}

//改变请求方式
window.changeMethods = function changeMethods(element) {
  alert(element.value)
}

//一些初始化启动
checkDataView(0)


/**
 * 样式
 */
const STYLE = `
  <style>
    .xml-http-intercepter-controller-template {
      --bgcolor: rgb(157, 0, 255)/*rgb(255, 76, 76)*/
    }
    .xml-http-intercepter-controller-template * {
      margin: 0;
      padding: 0
    }
    .intercepter-controller-button button {
      position: fixed;
      right: 2vw;
      top: 50%;
      width: 3.8vw;
      height: 3.8vw;
      background: rgb(157, 0, 255);
      border-top: solid .3vw rgb(105, 105, 105);
      border-bottom: solid .3vw rgb(105, 105, 105);
      border-left: dashed .3vw rgb(105, 105, 105);
      border-right: dashed .3vw rgb(105, 105, 105);
      border-radius: 50%;
      outline: none;
      color: rgb(84, 84, 84);
      font-size: .8vw;
      transform: translateY(-50%);
      transition: .5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
      cursor: pointer;
      z-index: 9999
    }
    .controller-content-header {
      padding: 1vw;
      background: var(--bgcolor);
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: white;
      box-shadow: 0 0 1vw #ccc
    }
    .header-right-options {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 1vw
    }
    .option-item {
      cursor: pointer
    }
    .intercepter-controller-button:hover button {
      border-top: solid .3vw rgb(157, 0, 255);
      border-bottom: solid .3vw rgb(157, 0, 255);
      border-left: dashed .3vw rgb(157, 0, 255);
      border-right: dashed .3vw rgb(157, 0, 255);
      box-shadow: 0 0 .8vw rgb(157, 0, 255)
    }
    .intercepter-controller-content {
      position: fixed;
      top: 50%;
      left: 50%;
      width: 90vw;
      background: white;
      transition: .3s;
      transform: translate(-50%, -50%) scale(0);
      box-shadow: 0 0 5vw #ccc;
      border-radius: .3vw;
      overflow: hidden;
      z-index: 9999
    }
    .content-show {
      transform: translate(-50%, -50%) scale(1)
    }
    .intercepter-controller-main {
      padding: .5vw 1vw;
      height: 75vh
    }
    .main-header {
      display: flex;
      justify-content: space-between;
      align-items: center
    }
    .main-header-left .main-tab-item, .main-header-right .main-tab-item {
      padding: .5vw;
      background: white;
      border: solid .05vw var(--bgcolor);
      outline: none;
      cursor: pointer;
      font-size: .8vw;
      border-radius: .1vw;
      color: var(--bgcolor);
      transition: .5s
    }
    .main-header-right .main-tab-item:hover,
    .main-header-right .main-tab-item-active,
    .main-header-left .main-tab-item:hover,
    .main-header-left .main-tab-item-active {
      background: var(--bgcolor);
      color: white
    }
    .intercepter-item {
      padding-bottom: 1.5vw;
      height: calc(73.5vh - 1.5vw);
      overflow-y: auto
    }
    .intercepter-item::-webkit-scrollbar{
      appearance: none ; width: .3vw ; background: rgba(255, 255, 255, 0.6)
    }
    .intercepter-item::-webkit-scrollbar-thumb {
      appearance: none ; width: 100% ; background: rgba(161, 161, 161, 0.6) ; border-radius: .15rem
    }
    .intercepter-table {
      margin: 1vw 0;
      width: 100%;
      border-collapse: collapse;
      border-spacing: 0;
      ovflow: hidden
    }
    .intercepter-table .table-header {
      position: sticky;
      top: 0;
      background: rgb(246, 246, 246)
    }
    .intercepter-table .info-message {
    }
    .intercepter-table .info-message tr {
      position: sticky;
      top: 3vw;
      left: 0;
      background: white;
      transition: .4s
    }
    .row-item-remove {
      opacity: 0;
      height: .5vh;
      transform: translateX(-70%)
    }
    .intercepter-table .info-message tr:hover {
      background: linear-gradient(to right, white, var(--bgcolor), white)
    }
    .intercepter-table td {
      padding: 1vw;
      max-width: 6vw;
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
      font-size: .8vw;
      transition: .3s
    }
    .actions-button-group button {
      margin: 0 .2vw;
      background: transparent;
      border: none;
      outline: none;
      cursor: pointer
    }
    .actions-button-group .error {
      color: red
    }
    .actions-button-group .info {
      color: var(--bgcolor)
    }
    .more-about-message {
      background: rgb(246, 246, 246)
    }
    .intercepter-table .more-about-message {
      border: none
    }
    .more-about-message td {
      padding: 0
    }
    .request-message-container {
      max-height: 0vh;
      overflow: hidden;
      display: flex;
      flex-flow: column nowrap;
      justify-content: space-between;
      gap: 1vw
    }
    .more-message-show td {
      padding: 1vw
    }
    .more-message-show .request-message-container {
      max-height: 1000vh
    }
    .request-message-content {
      margin: 1vw 0
    }
    .request-message-form {
      list-style: none;
      display: flex;
      flex-flow: row wrap;
      gap: 2vw
    }
    .request-message-form li {
      margin: .5vw 0;
      flex: auto;
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: .5vw
    }
    .request-message-form li textarea {
      padding: .5vw;
      width: 100%;
      height: auto;
      border-radius: .2vw;
      border: solid .05vw #ccc;
      outline: none
    }
    .data-list-select, .request-message-form li input {
      padding: .5vw;
      border-radius: .2vw;
      min-width: 3.5vw;
      height: 1.5vh;
      border: solid .05vw #ccc;
      outline: none
    }
    .item-show {
      display: block
    }
    .item-close {
      display: none
    }
    .xml-http-request-more-message-footer button, .xml-http-response-more-message-footer button {
      padding: .5vw 1vw;
      background: var(--bgcolor);
      border: none;
      border-radius: .2vw;
      outline: none;
      color: white;
      cursor: pointer
    }
  </style>
`

header.insertAdjacentHTML('beforeend', STYLE)

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

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值