pytest-jequnauto/utils/recording/mitmproxy_control.py

222 lines
7.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from urllib.parse import parse_qs, urlparse
from typing import Any, Union, Text, List, Dict, Tuple
import ast
import os
import mitmproxy.http
from mitmproxy import ctx
from ruamel import yaml
class Counter:
"""
代理录制,基于 mitmproxy 库拦截获取网络请求
将接口请求数据转换成 yaml 测试用例
参考资料: https://blog.wolfogre.com/posts/usage-of-mitmproxy/
"""
def __init__(self, filter_url: List, filename: Text = './data/proxy_data.yaml'):
self.num = 0
self.file = filename
self.counter = 1
# 需要过滤的 url
self.url = filter_url
def response(self, flow: mitmproxy.http.HTTPFlow) -> None:
"""
mitmproxy抓包处理响应在这里汇总需要数据, 过滤 包含指定url并且响应格式是 json的
:param flow:
:return:
"""
# 存放需要过滤的接口
filter_url_type = ['.css', '.js', '.map', '.ico', '.png', '.woff', '.map3', '.jpeg', '.jpg']
url = flow.request.url
ctx.log.info("=" * 100)
# 判断过滤掉含 filter_url_type 中后缀的 url
if any(i in url for i in filter_url_type) is False:
# 存放测试用例
if self.filter_url(url):
data = self.data_handle(flow.request.text)
method = flow.request.method
header = self.token_handle(flow.request.headers)
response = flow.response.text
case_id = self.get_case_id(url) + str(self.counter)
cases = {
case_id: {
"host": self.host_handle(url),
"url": self.url_path_handle(url),
"method": method,
"detail": None,
"headers": header,
'requestType': self.request_type_handler(method),
"is_run": True,
"data": data,
"dependence_case": None,
"dependence_case_data": None,
"assert": self.response_code_handler(response),
"sql": None
}
}
# 判断如果请求参数时拼接在url中提取url中参数转换成字典
if "?" in url:
cases[case_id]['url'] = self.get_url_handler(url)[1]
cases[case_id]['data'] = self.get_url_handler(url)[0]
ctx.log.info("=" * 100)
ctx.log.info(cases)
# 判断文件不存在则创建文件
try:
self.yaml_cases(cases)
except FileNotFoundError:
os.makedirs(self.file)
self.counter += 1
@classmethod
def get_case_id(cls, url: Text) -> Text:
"""
通过url提取对应的user_id
:param url:
:return:
"""
_url_path = str(url).split('?')[0]
# 通过url中的接口地址最后一个参数作为case_id的名称
_url = _url_path.split('/')
return _url[-1]
def filter_url(self, url: Text) -> bool:
"""过滤url"""
for i in self.url:
# 判断当前拦截的url地址是否是addons中配置的host
if i in url:
# 如果是则返回True
return True
# 否则返回 False
return False
@classmethod
def response_code_handler(cls, response) -> Union[Dict, None]:
"""
处理接口响应默认断言数据为code码如果接口没有code码则返回None
@param response:
@return:
"""
try:
data = cls.data_handle(response)
return {"code": {"jsonpath": "$.code", "type": "==",
"value": data['code'], "AssertType": None}}
except KeyError:
return None
except NameError:
return None
@classmethod
def request_type_handler(cls, method: Text) -> Text:
""" 处理请求类型有params、json、file,需要根据公司的业务情况自己调整 """
if method == 'GET':
# 如我们公司只有get请求是prams其他都是json的
return 'params'
return 'json'
@classmethod
def data_handle(cls, dict_str) -> Any:
"""处理接口请求、响应的数据如null、true格式问题"""
try:
if dict_str != "":
if 'null' in dict_str:
dict_str = dict_str.replace('null', 'None')
if 'true' in dict_str:
dict_str = dict_str.replace('true', 'True')
if 'false' in dict_str:
dict_str = dict_str.replace('false', 'False')
dict_str = ast.literal_eval(dict_str)
if dict_str == "":
dict_str = None
return dict_str
except Exception as exc:
raise exc
@classmethod
def token_handle(cls, header) -> Dict:
"""
提取请求头参数
:param header:
:return:
"""
# 这里是将所有请求头的数据,全部都拦截出来了
# 如果公司只需要部分参数,可以在这里加判断过滤
headers = {}
for key, value in header.items():
headers[key] = value
return headers
def host_handle(self, url: Text) -> Tuple:
"""
解析 url
:param url: https://xxxx.test.xxxx.com/#/goods/listShop
:return: https://xxxx.test.xxxx.com/
"""
host = None
# 循环遍历需要过滤的hosts数据
for i in self.url:
# 这里主要是判断如果我们conf.py中有配置这个域名则用例中展示 ”${{host}}“动态获取用例host
# 大家可以在这里改成自己公司的host地址
if 'https://www.wanandroid.com' in url:
host = '${{host}}'
elif i in url:
host = i
return host
def url_path_handle(self, url: Text):
"""
解析 url_path
:param url: https://xxxx.test.xxxx.com/shopList/json
:return: /shopList/json
"""
url_path = None
# 循环需要拦截的域名
for path in self.url:
if path in url:
url_path = url.split(path)[-1]
return url_path
def yaml_cases(self, data: Dict) -> None:
"""
写入 yaml 数据
:param data: 测试用例数据
:return:
"""
with open(self.file, "a", encoding="utf-8") as file:
yaml.dump(data, file, Dumper=yaml.RoundTripDumper, allow_unicode=True)
file.write('\n')
def get_url_handler(self, url: Text) -> Tuple:
"""
将 url 中的参数 转换成字典
:param url: /trade?tradeNo=&outTradeId=11
:return: {“outTradeId”: 11}
"""
result = None
url_path = None
for i in self.url:
if i in url:
query = urlparse(url).query
# 将字符串转换为字典
params = parse_qs(query)
# 所得的字典的value都是以列表的形式存在如请求url中的参数值为空则字典中不会有该参数
result = {key: params[key][0] for key in params}
url = url[0:url.rfind('?')]
url_path = url.split(i)[-1]
return result, url_path
# 1、本机需要设置代理默认端口为: 8080
# 2、控制台输入 mitmweb -s .\utils\recording\mitmproxy_control.py - p 8888命令开启代理模式进行录制
addons = [
Counter(["https://www.wanandroid.com"])
]