一、什么是抓包
什么是抓包:抓包就是抓取网络请求,将请求一直挂在到我们写的中间件中,在中间件统一修改后完成本次请求,俗称请求托管
闲着没事本篇文章用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)
还想看啥啊?没啦,该吃吃该喝喝