Mitmproxy 拦截、mock移动设备网络请求

本文介绍了mitmproxy的安装、启动及抓包方法,推荐使用7.0以下安卓版本或iOS系统抓包。还讲解了编写自定义脚本处理拦截数据的两种套路,推荐第二种。详细介绍了不同生命周期的事件,最后通过示例展示如何利用mitmproxy工作,如修改搜索词、页面内容等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.  安装

pip install mitmproxy

2.  启动

mitmweb

若端口8080被占用,可通过运行mitmweb --port “任一可用端口”更改,更改为你想用的端口

3. 抓取 app 请求的数据

注意:如果 Android 的版本在 7.0 以上,抓取数据包可能会遇到一些问题,所以我们推荐你使用7.0 一下的Android 版本,或者使用 iOS 系统进行抓包测试,Android 7.0 的版本也是可以抓到app 数据的,但是需要开发包才行

在手机里面设置手机的代理模式为「手动」,代理的地址设置到自己的电脑的IP 端口设置为自己运行 mitm 时指定的端口即可,在手上下载并安装证书 (访问地址 mitm.it ) 按照提示就可以安装完成

接下来既可以看到拦截到的请求信息了

 

4. 写个脚本对拦截的数据做处理

脚本

完成了上述工作,我们已经具备了操作 mitmproxy 的基本能力 了。接下来开始开发自定义脚本,这才是 mitmproxy 真正强大的地方。

脚本的编写需要遵循 mitmproxy 规定的套路,这样的套路有两个。

第一个是,编写一个 py 文件供 mitmproxy 加载,文件中定义了若干函数,这些函数实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的函数,形如:

import mitmproxy.http
from mitmproxy import ctx

num = 0


def request(flow: mitmproxy.http.HTTPFlow):
    global num
    num = num + 1
    ctx.log.info("We've seen %d flows" % num)

第二个是,编写一个 py 文件供 mitmproxy 加载,文件定义了变量 addons,addons 是个数组,每个元素是一个类实例,这些类有若干方法,这些方法实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的方法。这些类,称为一个个 addon,比如一个叫 Counter 的 addon:

import mitmproxy.http
from mitmproxy import ctx


class Counter:
    def __init__(self):
        self.num = 0

    def request(self, flow: mitmproxy.http.HTTPFlow):
        self.num = self.num + 1
        ctx.log.info("We've seen %d flows" % self.num)


addons = [
    Counter()
]

这里强烈建议使用第二种套路,直觉上就会感觉第二种套路更为先进,使用会更方便也更容易管理和拓展。况且这也是官方内置的一些 addon 的实现方式。

我们将上面第二种套路的示例代码存为 addons.py,再重新启动 mitmproxy:

mitmweb -s addons.py

当浏览器使用代理进行访问时,就应该能看到控制台里有类似这样的日志:

Web server listening at http://127.0.0.1:8081/
Loading script addons.py
Proxy server listening at http://*:8080
We've seen 1 flows
……
……
We've seen 2 flows
……
We've seen 3 flows
……
We've seen 4 flows
……
……
We've seen 5 flows
……

这就说明自定义脚本生效了。

事件

上述的脚本估计不用我解释相信大家也看明白了,就是当 request 发生时,计数器加一,并打印日志。这里对应的是 request 事件,那拢共有哪些事件呢?不多,也不少,这里详细介绍一下。

事件针对不同生命周期分为 5 类。“生命周期”这里指在哪一个层面看待事件,举例来说,同样是一次 web 请求,我可以理解为“HTTP 请求 -> HTTP 响应”的过程,也可以理解为“TCP 连接 -> TCP 通信 -> TCP 断开”的过程。那么,如果我想拒绝来个某个 IP 的客户端请求,应当注册函数到针对 TCP 生命周期 的 tcp_start 事件,又或者,我想阻断对某个特定域名的请求时,则应当注册函数到针对 HTTP 声明周期的 http_connect 事件。其他情况同理。

下面一段估计会又臭又长,如果你没有耐心看完,那至少看掉针对 HTTP 生命周期的事件,然后跳到示例

1. 针对 HTTP 生命周期

def http_connect(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 收到了来自客户端的 HTTP CONNECT 请求。在 flow 上设置非 2xx 响应将返回该响应并断开连接。CONNECT 不是常用的 HTTP 请求方法,目的是与服务器建立代理连接,仅是 client 与 proxy 的之间的交流,所以 CONNECT 请求不会触发 request、response 等其他常规的 HTTP 事件。

def requestheaders(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自客户端的 HTTP 请求的头部被成功读取。此时 flow 中的 request 的 body 是空的。

def request(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自客户端的 HTTP 请求被成功完整读取。

def responseheaders(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自服务端的 HTTP 响应的头部被成功读取。此时 flow 中的 response 的 body 是空的。

def response(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自服务端端的 HTTP 响应被成功完整读取。

def error(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 发生了一个 HTTP 错误。比如无效的服务端响应、连接断开等。注意与“有效的 HTTP 错误返回”不是一回事,后者是一个正确的服务端响应,只是 HTTP code 表示错误而已。

2. 针对 TCP 生命周期

def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) 建立了一个 TCP 连接。

def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) TCP 连接收到了一条消息,最近一条消息存于 flow.messages[-1]。消息是可修改的。

def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) 发生了 TCP 错误。

def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) TCP 连接关闭。

3. 针对 Websocket 生命周期

def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 客户端试图建立一个 websocket 连接。可以通过控制 HTTP 头部中针对 websocket 的条目来改变握手行为。flow 的 request 属性保证是非空的的。

def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 建立了一个 websocket 连接。

def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 收到一条来自客户端或服务端的 websocket 消息。最近一条消息存于 flow.messages[-1]。消息是可修改的。目前有两种消息类型,对应 BINARY 类型的 frame 或 TEXT 类型的 frame。

def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 发生了 websocket 错误。

def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) websocket 连接关闭。

4. 针对网络连接生命周期

def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求。

def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 客户端断开了和 mitmproxy 的连接。

def serverconnect(self, conn: mitmproxy.connections.ServerConnection):

(Called when) mitmproxy 连接到了服务端。注意一条连接可能对应多个 HTTP 请求。

def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):

(Called when) mitmproxy 断开了和服务端的连接。

def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 网络 layer 发生切换。你可以通过返回一个新的 layer 对象来改变将被使用的 layer。详见 layer 的定义

5. 通用生命周期

def configure(self, updated: typing.Set[str]):

(Called when) 配置发生变化。updated 参数是一个类似集合的对象,包含了所有变化了的选项。在 mitmproxy 启动时,该事件也会触发,且 updated 包含所有选项。

def done(self):

(Called when) addon 关闭或被移除,又或者 mitmproxy 本身关闭。由于会先等事件循环终止后再触发该事件,所以这是一个 addon 可以看见的最后一个事件。由于此时 log 也已经关闭,所以此时调用 log 函数没有任何输出。

def load(self, entry: mitmproxy.addonmanager.Loader):

(Called when) addon 第一次加载时。entry 参数是一个 Loader 对象,包含有添加选项、命令的方法。这里是 addon 配置它自己的地方。

def log(self, entry: mitmproxy.log.LogEntry):

(Called when) 通过 mitmproxy.ctx.log 产生了一条新日志。小心不要在这个事件内打日志,否则会造成死循环。

def running(self):

(Called when) mitmproxy 完全启动并开始运行。此时,mitmproxy 已经绑定了端口,所有的 addon 都被加载了。

def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):

(Called when) 一个或多个 flow 对象被修改了,通常是来自一个不同的 addon。

示例

估计看了那么多的事件你已经晕了,正常,鬼才会记得那么多事件。事实上考虑到 mitmproxy 的实际使用场景,大多数情况下我们只会用到针对 HTTP 生命周期的几个事件。再精简一点,甚至只需要用到 http_connectrequestresponse 三个事件就能完成大多数需求了。

这里以一个稍微有点黑色幽默的例子,覆盖这三个事件,展示如果利用 mitmproxy 工作。

需求是这样的:

  1. 因为百度搜索是不靠谱的,所有当客户端发起百度搜索时,记录下用户的搜索词,再修改请求,将搜索词改为“360 搜索”;
  2. 因为 360 搜索还是不靠谱的,所有当客户端访问 360 搜索时,将页面中所有“搜索”字样改为“请使用谷歌”。
  3. 因为谷歌是个不存在的网站,所有就不要浪费时间去尝试连接服务端了,所有当发现客户端试图访问谷歌时,直接断开连接。
  4. 将上述功能组装成名为 Joker 的 addon,并保留之前展示名为 Counter 的 addon,都加载进 mitmproxy。

第一个需求需要篡改客户端请求,所以实现一个 request 事件:

def request(self, flow: mitmproxy.http.HTTPFlow):
    # 忽略非百度搜索地址
    if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
        return

    # 确认请求参数中有搜索词
    if "wd" not in flow.request.query.keys():
        ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
        return

    # 输出原始的搜索词
    ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
    # 替换搜索词为“360搜索”
    flow.request.query.set_all("wd", ["360搜索"])

第二个需求需要篡改服务端响应,所以实现一个 response 事件:

def response(self, flow: mitmproxy.http.HTTPFlow):
    # 忽略非 360 搜索地址
    if flow.request.host != "www.so.com":
        return

    # 将响应中所有“搜索”替换为“请使用谷歌”
    text = flow.response.get_text()
    text = text.replace("搜索", "请使用谷歌")
    flow.response.set_text(text)

第三个需求需要拒绝客户端请求,所以实现一个 http_connect 事件:

def http_connect(self, flow: mitmproxy.http.HTTPFlow):
    # 确认客户端是想访问 www.google.com
    if flow.request.host == "www.google.com":
        # 返回一个非 2xx 响应断开连接
        flow.response = http.HTTPResponse.make(404)

为了实现第四个需求,我们需要将代码整理一下,即易于管理也易于查看。

创建一个 joker.py 文件,内容为:

import mitmproxy.http
from mitmproxy import ctx, http


class Joker:
    def request(self, flow: mitmproxy.http.HTTPFlow):
        if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
            return

        if "wd" not in flow.request.query.keys():
            ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
            return

        ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
        flow.request.query.set_all("wd", ["360搜索"])

    def response(self, flow: mitmproxy.http.HTTPFlow):
        if flow.request.host != "www.so.com":
            return

        text = flow.response.get_text()
        text = text.replace("搜索", "请使用谷歌")
        flow.response.set_text(text)

    def http_connect(self, flow: mitmproxy.http.HTTPFlow):
        if flow.request.host == "www.google.com":
            flow.response = http.HTTPResponse.make(404)

创建一个 counter.py 文件,内容为:

import mitmproxy.http
from mitmproxy import ctx


class Counter:
    def __init__(self):
        self.num = 0

    def request(self, flow: mitmproxy.http.HTTPFlow):
        self.num = self.num + 1
        ctx.log.info("We've seen %d flows" % self.num)

创建一个 addons.py 文件,内容为:

import counter
import joker

addons = [
    counter.Counter(),
    joker.Joker(),
]

将三个文件放在相同的文件夹,在该文件夹内启动命令行,运行:

mitmweb -s addons.py

 

<think>嗯,用户问的是如何在Vite中通过Mock拦截并封装Axios请求返回虚拟数据。首先,我需要回忆一下Vite和Axios的基本知识,以及Mock数据的方法。Vite是一个前端构建工具,支持快速的开发环境,而Axios是常用的HTTP客户端。Mock数据通常用于前端开发中,当后端API还没完成时,可以模拟数据来进行测试和开发。 用户提到要拦截Axios请求,可能需要使用Axios的拦截器功能。不过,Vite本身可能有一些Mock插件,比如vite-plugin-mock,这个插件可以在开发服务器中拦截请求,返回模拟数据。需要确认这个插件的使用方法和配置步骤。 接下来,应该分步骤讲解。首先是如何安装必要的依赖,比如vite-plugin-mockmockjs。然后是如何在Vite配置文件中配置这个插件,确保它只在开发环境中启用。接着,创建Mock数据的文件,比如在mock目录下建立index.ts,定义各种接口的模拟响应。 然后是Axios的封装部分,用户可能需要一个统一的请求工具,封装GET、POST等方法,并处理错误。这里需要展示如何创建axios实例,设置拦截器,以及在拦截器中处理请求和响应。同时,要确保在开发环境下,请求会被Mock拦截,而在生产环境下则发送真实请求。 可能还需要考虑不同的请求方法,比如GET和POST,以及如何根据不同的URL返回对应的模拟数据。比如,使用正则表达式匹配URL,或者根据请求参数返回不同的数据。 另外,用户可能关心的是如何动态生成模拟数据,这时候可以引入mockjs库,它提供了丰富的随机数据生成功能。在Mock文件中,使用mockjs生成更逼真的数据,比如随机名称、邮箱、地址等。 还要注意,在封装Axios的时候,如何处理错误,比如网络错误或响应错误,在拦截器中统一捕获并处理,或者抛出给调用者处理。 可能遇到的疑问点包括:配置是否正确,Mock数据是否生效,如何区分环境,以及Axios拦截器和Mock插件的优先级。需要确保Mock插件在开发环境下正确拦截请求,而Axios的拦截器不会影响到这一过程。 最后,可能需要一个示例页面,比如在Vue或React组件中调用封装的Axios方法,展示如何获取Mock数据,并在页面上渲染出来。这样用户可以看到实际效果,验证配置是否正确。 总结一下,整个流程包括安装依赖、配置Vite插件、编写Mock数据、封装Axios实例,并在组件中使用。同时要注意环境区分和错误处理,确保Mock只在开发环境生效,生产环境使用真实API。</think>在Vite项目中通过Mock拦截Axios请求并返回虚拟数据,可以通过以下步骤实现: --- ### 一、安装必要依赖 ```bash npm install mockjs vite-plugin-mock axios -D ``` --- ### 二、配置Vite插件 1. 在`vite.config.ts`中添加配置: ```typescript import { defineConfig } from 'vite' import { viteMockServe } from 'vite-plugin-mock' export default defineConfig({ plugins: [ viteMockServe({ mockPath: './src/mock', // mock文件存放目录 localEnabled: true, // 开发环境启用 prodEnabled: false, // 生产环境禁用 logger: true // 控制台显示请求日志 }) ] }) ``` --- ### 三、创建Mock文件 1. 新建`src/mock/index.ts`: ```typescript import { MockMethod } from 'vite-plugin-mock' import Mock from 'mockjs' export default [ // 用户列表接口 { url: '/api/users', method: 'get', response: () => { return Mock.mock({ 'data|10': [{ 'id|+1': 1, name: '@cname', email: '@email' }] }) } }, // 登录接口 { url: '/api/login', method: 'post', timeout: 1000, // 模拟延迟 response: ({ body }) => { if (body.username === 'admin') { return { code: 200, token: 'mock-token' } } return { code: 401, message: 'Invalid credentials' } } } ] as MockMethod[] ``` --- ### 四、封装Axios实例 1. 创建`src/utils/request.ts`: ```typescript import axios from 'axios' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { if (response.status === 200) { return response.data } return Promise.reject(response) }, error => { console.error('Request Error:', error) return Promise.reject(error) } ) export default service ``` --- ### 五、使用示例 ```typescript import request from '@/utils/request' // 获取用户列表 export const getUsers = () => request.get('/api/users') // 登录请求 export const login = (data: { username: string; password: string }) => request.post('/api/login', data) ``` --- ### 六、注意事项 1. **环境控制**:通过`import.meta.env.DEV`判断开发环境 2. **类型提示**:建议为Mock数据定义TypeScript类型 3. **动态参数**:在response函数中可以通过`params`获取请求参数: ```typescript response: ({ query }) => { const page = query.page || 1 // 返回分页数据... } ``` --- ### 七、调试技巧 1. 在浏览器Network面板查看`mock`开头的请求 2. 使用`console.log`在mock文件的response函数中调试 3. 通过修改`timeout`参数模拟网络延迟 通过这种方式,可以在前后端并行开发时保持高效,且切换真实接口时只需修改`baseURL`即可实现无缝切换。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值