Python实现web接口响应性能分析工具

效果图

在这里插入图片描述

功能

动态抓包,并分析网络接口网络质量,超过限制响应时间,自动触发,推送飞书群

1、支持mac系统和window系统
2、支持飞书机器人通知
3、支持开启代理、关闭代理、安装证书
后续新增功能,待开发… …

实现逻辑

pyqt5界面 + mitmproxy网络抓包 + 分析

proxy_driver代码如下:

import time
import mitmproxy.http
from data.cachedriver import cache

class ProxyDriver:
    def __init__(self, urlList):
        self.urlList = urlList
        self.urlCounter = {}
    def request(self, flow: mitmproxy.http.HTTPFlow):
        pass
    def response(self, flow: mitmproxy.http.HTTPFlow):
        for filterUrl in self.urlList:
            if filterUrl in flow.request.url:
                method = flow.request.method
                url = flow.request.url.encode("utf-8").decode("utf-8")
                responseTime = (time.time() - flow.request.timestamp_start) * 1000
                if url in self.urlCounter.keys():
                    sumTime = self.urlCounter[url]["avageTime"] * self.urlCounter[url]["count"]
                    self.urlCounter[url]["count"] += 1
                    self.urlCounter[url]["avageTime"] = (sumTime + responseTime)/self.urlCounter[url]["count"]
                    self.urlCounter[url]["maxTime"] = responseTime if responseTime>self.urlCounter[url]["maxTime"] else self.urlCounter[url]["maxTime"]
                    self.urlCounter[url]["minTime"] = responseTime if responseTime<self.urlCounter[url]["minTime"] else self.urlCounter[url]["minTime"]
                else:
                    self.urlCounter[url] = {"count": 1, "avageTime": responseTime, "maxTime": responseTime, "minTime": responseTime}
                self.urlCounter[url]["method"] = method
                if self.urlCounter[url]["maxTime"]>=MAXTIME:
                    cache.add_cache(url, self.urlCounter[url])
            else:
                return 

界面代码

兼容了mac系统和window系统开启关闭代理的不同方式

import platform
import subprocess

from gui.widgetsmain import Ui_MainWindow
from PyQt5.QtWidgets import QMainWindow, QMessageBox, QAbstractItemView, QHeaderView, QMenu, QAction
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import QIcon
from applescript import tell
from data.cachedriver import Cache
from data.data_child_thread import DataChildTread
from model.model_response_data import ModelResponseData
from gui.my_qss import *

class WidgetsMain(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(WidgetsMain, self).__init__()
        self.setupUi(self)
        self.pushBotton_connectEvent()
        self.choose_system()
        self.table_view_config()
        self.sheet_style()
    def sheet_style(self):
        self.setStyleSheet(qss_mainWindow)


    def table_view_config(self):
        self.dataModel = ModelResponseData()
        self.proxymodel = QSortFilterProxyModel(self)
        self.proxymodel.setSourceModel(self.dataModel)
        self.tableView.setModel(self.proxymodel)
        self.tableView.setSortingEnabled(True)
        self.tableView.sortByColumn(2,Qt.SortOrder.DescendingOrder)
        self.tableView.horizontalHeader().setVisible(True)
        self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
        self.tableView.setWordWrap(True)

        # 列宽
        self.tableView.setColumnWidth(0,420)
        self.right_menu_config()
    def right_menu_config(self):
        self.rightMenu = QMenu(self)
        copyAction = QAction("复制选择内容", self)
        self.rightMenu.addAction(copyAction)

        self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableView.setSelectionMode(QAbstractItemView.ContiguousSelection)
        self.tableView.customContextMenuRequested.connect(self.slots_right_menu)
    def choose_system(self):
        if "macOS" in platform.platform():
            self.radioButtonMac.setChecked(True)
        else:
            self.radioButtonWindows.setChecked(True)
    def closeEvent(self, event) -> None:
        event.accept()
        os._exit(0)
    def pushBotton_connectEvent(self):
        self.pushButtonOpenProxy.clicked.connect(self.slots_open_proxy_pushBotton)
        self.pushButtonCloseProxy.clicked.connect(self.slots_close_proxy_pushBotton)
        self.pushButtonInstallCer.clicked.connect(self.slots_install_cer)
        self.pushButtonstart.clicked.connect(self.slots_start)
        self.pushButtonEnd.clicked.connect(self.slots_end)
        self.pushButtonClear.clicked.connect(self.slots_clear_model)
    def slots_right_menu(self, pos):
        self.rightMenu.exec_(self.tableView.mapToGlobal(pos))
    def slots_open_proxy_pushBotton(self):
        '''开启代理'''
        port = self.lineEditPort.text()
        if self.radioButtonMac.isChecked():
            openhttpcmd = f'''networksetup -setwebproxy "Wi-Fi" 127.0.0.1 {port}'''
            openhttpscmd = f'''networksetup -setsecurewebproxy "Wi-Fi" 127.0.0.1 {port}'''
            os.system(openhttpcmd)
            os.system(openhttpscmd)
        elif self.radioButtonWindows.isChecked():
            self.window_system_proxy_config(True, "127.0.0.1", int(port))
        messagebox = QMessageBox()
        messagebox.setWindowTitle("提示")
        messagebox.setWindowIcon(QIcon("gui/images/title.png"))
        messagebox.setText(f"{port}端口 代理开启成功!")
        messagebox.exec_()
    def slots_close_proxy_pushBotton(self):
        '''关闭代理'''
        port = self.lineEditPort.text()
        if self.radioButtonMac.isChecked():
            closehttpcmd = '''networksetup -setwebproxystate "Wi-Fi" off '''
            closehttscmd = '''networksetup -setsecurewebproxystate "Wi-Fi" off'''
            os.system(closehttpcmd)
            os.system(closehttscmd)
        elif self.radioButtonWindows.isChecked():
            self.window_system_proxy_config(False, "", 0)

        messagebox = QMessageBox()
        messagebox.setWindowTitle("提示")
        messagebox.setWindowIcon(QIcon("gui/images/title.png"))
        messagebox.setText(f"{port}端口 代理已关闭!")
        messagebox.exec_()
    def slots_install_cer(self):
        '''安装证书'''
        if self.radioButtonMac.isChecked():
            installCommand = '''sudo security add-trusted-cert -d -p ssl -p basic -k /Library/Keychains/System.keychain .mitmproxy/mitmproxy-ca-cert.pem'''
            tell.app("Terminal", 'do script"' + installCommand + '"')
        elif self.radioButtonWindows.isChecked():
            installCommand = 'start .mitmproxy\mitmproxy-ca-cert.p12'
            subprocess.run(installCommand, shell=True)
    def slots_start(self):
        '''开启监控'''
        self.thread = DataChildTread()
        self.thread.dataSignal.connect(self.dataModel.update_model)
        self.thread.start()
        port = self.lineEditPort.text()
        self.chanage_proxy_driver_filter_url()
        basePath = os.getcwd()
        activePath = 'venv/bin/activate'
        runType = 'mitmweb' if self.checkBoxOpenWeb.isChecked() else 'mitmproxy'
        if self.radioButtonMac.isChecked():
            startCommand = f'''cd {basePath} && source {activePath} && {runType} -s proxy_driver.py -p {port}'''
            tell.app('Terminal', 'do script"' + startCommand + '"')
        elif self.radioButtonWindows.isChecked():
            startCommand = f'start {runType} -s proxy_driver.py -p {port}'
            subprocess.run(startCommand, shell=True)
    def slots_end(self):
        '''关闭监控'''
        self.thread.isRun = False
        runType = 'mitmweb' if self.checkBoxOpenWeb.isChecked() else 'mitmproxy'
        if self.radioButtonMac.isChecked():
            # 关闭监控进程
            endCommandA = "ps aux | grep %s | grep -v grep | awk \'{print $2}\' | xargs kill -9" % runType
            # 关闭终端
            endCommandB = "ps aux | grep MacOS | grep Terminal |awk '{print $2}' | xargs kill -9"
        elif self.radioButtonWindows.isChecked():
            # 关闭监控进程
            endCommandA = f'taskkill /F /IM {runType}.exe'
            endCommandB = ''
            # 关闭终端
        os.system(endCommandA)
        os.system(endCommandB)
        messagebox = QMessageBox()
        messagebox.setWindowTitle("提示")
        messagebox.setWindowIcon(QIcon("gui/images/title.png"))
        messagebox.setText(f"已关闭监控进程: {runType}")
        messagebox.exec_()
    def slots_clear_model(self):
        '''清空缓存和model'''
        self.dataModel.update_model([])
        Cache.clean_all_cache()
        cache = Cache(".data")
    def chanage_proxy_driver_filter_url(self):
        '''
        修改驱动文件
        :return:
        '''
        fiterUrls = self.plainTextEdit.toPlainText()
        maxFileterTime = self.lineEditTime.text()
        fiterUrls = fiterUrls.split("\n")
        with open("proxy_driver.py", 'r', encoding='utf=8') as file:
            olddata = file.readlines()
        olddata[0] = f"MAXTIME = {int(maxFileterTime)}\n"
        olddata[-1] = f"addons = [ProxyDriver({fiterUrls})]"
        with open("proxy_driver.py", 'w', encoding='utf=8') as file:
            file.write(''.join(olddata))
    def window_system_proxy_config(self, enable: bool, server: str, port: int):
        '''
        window 系统开启/关闭网络代理
        :param enable: 是否开启
        :param server: ip
        :param port: 端口
        :return:
        '''
        import winreg
        # 打开注册表编辑器
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                             r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
                             0, winreg.KEY_WRITE)
        # 开启代理
        if enable:
            winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 1)
            winreg.SetValueEx(key, "ProxyServer", 0, winreg.REG_SZ, f"{server}:{port}")
            # 关闭代理
        else:
            winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 0)
            winreg.SetValueEx(key, "ProxyServer", 0, winreg.REG_SZ, "")
            # 关闭注册表编辑器
        winreg.CloseKey(key)

通过此工具,可解决测试中对项目所有接口的质量分析

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值