修复bug: 修复定时任务多个项目共同执行时获取和更新LastResultPool、CookiePool、VariablePool对象存在冲突的问题

This commit is contained in:
azhengzz 2021-03-11 20:22:19 +08:00
parent 3c7667e125
commit a10d3c0cf4
3 changed files with 280 additions and 96 deletions

View File

@ -3,64 +3,158 @@
# 记录最近一次测试结果 # 记录最近一次测试结果
from copy import deepcopy from copy import deepcopy
from flask import session
from app.extensions import session_id_manager from app.extensions import session_id_manager
from app.cores.dictionaries import DISPATCHER_TRIGGER_TYPE
class LastResult: class LastResult:
"""
LastResultPool = {
session_id1: {
'result': None, # 上次执行结果
'request_headers': None, # 请求头
'request_body': None, # 请求包
'response_headers': None, # 应答头
'response_body': None, # 应答包
...
},
session_id2: {
'result': None, # 上次执行结果
'request_headers': None, # 请求头
'request_body': None, # 请求包
'response_headers': None, # 应答头
'response_body': None, # 应答包
...
},
...
}
ScheduleLastResultPool = {
project_id1: {
'result': None, # 上次执行结果
'request_headers': None, # 请求头
'request_body': None, # 请求包
'response_headers': None, # 应答头
'response_body': None, # 应答包
...
},
project_id2: {
'result': None, # 上次执行结果
'request_headers': None, # 请求头
'request_body': None, # 请求包
'response_headers': None, # 应答头
'response_body': None, # 应答包
...
},
...
}
"""
# 上次执行结果
LastResultPool = {} LastResultPool = {}
# 定时任务项目-上次执行结果
ScheduleLastResultPool = {}
@classmethod @classmethod
def get_last_result(cls) -> dict: def get_last_result(cls) -> dict:
session_id = session_id_manager.get_session_id() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
return cls.LastResultPool.get(session_id) project_id = session.get('project_id')
if project_id:
return cls.ScheduleLastResultPool.get(project_id)
else:
session_id = session_id_manager.get_session_id()
return cls.LastResultPool.get(session_id)
@classmethod @classmethod
def update_last_result(cls, result, request_headers, request_body, response_headers, response_body): def update_last_result(cls, result, request_headers, request_body, response_headers, response_body):
"""更新所有字段""" """更新所有字段"""
cls._check_current_session_id_last_result_exist() cls._check_current_session_id_last_result_exist()
session_id = session_id_manager.get_session_id() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
cls.LastResultPool.get(session_id).update({ project_id = session.get('project_id')
'result': deepcopy(result), if project_id:
'request_headers': deepcopy(request_headers), cls.ScheduleLastResultPool.get(project_id).update({
'request_body': deepcopy(request_body), 'result': deepcopy(result),
'response_headers': deepcopy(response_headers), 'request_headers': deepcopy(request_headers),
'response_body': deepcopy(response_body), 'request_body': deepcopy(request_body),
}) 'response_headers': deepcopy(response_headers),
'response_body': deepcopy(response_body),
})
else:
session_id = session_id_manager.get_session_id()
cls.LastResultPool.get(session_id).update({
'result': deepcopy(result),
'request_headers': deepcopy(request_headers),
'request_body': deepcopy(request_body),
'response_headers': deepcopy(response_headers),
'response_body': deepcopy(response_body),
})
@classmethod @classmethod
def update_request_and_response_to_last_result(cls, request_headers, request_body, response_headers, response_body): def update_request_and_response_to_last_result(cls, request_headers, request_body, response_headers, response_body):
"""更新请求和应答数据""" """更新请求和应答数据"""
cls._check_current_session_id_last_result_exist() cls._check_current_session_id_last_result_exist()
session_id = session_id_manager.get_session_id() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
cls.LastResultPool.get(session_id).update({ project_id = session.get('project_id')
'request_headers': deepcopy(request_headers), if project_id:
'request_body': deepcopy(request_body), cls.ScheduleLastResultPool.get(project_id).update({
'response_headers': deepcopy(response_headers), 'request_headers': deepcopy(request_headers),
'response_body': deepcopy(response_body), 'request_body': deepcopy(request_body),
}) 'response_headers': deepcopy(response_headers),
'response_body': deepcopy(response_body),
})
else:
session_id = session_id_manager.get_session_id()
cls.LastResultPool.get(session_id).update({
'request_headers': deepcopy(request_headers),
'request_body': deepcopy(request_body),
'response_headers': deepcopy(response_headers),
'response_body': deepcopy(response_body),
})
@classmethod @classmethod
def update_result_to_last_result(cls, result): def update_result_to_last_result(cls, result):
"""更新最近一次执行结果""" """更新最近一次执行结果"""
cls._check_current_session_id_last_result_exist() cls._check_current_session_id_last_result_exist()
session_id = session_id_manager.get_session_id() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
cls.LastResultPool.get(session_id).update({ project_id = session.get('project_id')
'result': deepcopy(result), if project_id:
}) cls.ScheduleLastResultPool.get(project_id).update({
'result': deepcopy(result),
})
else:
session_id = session_id_manager.get_session_id()
cls.LastResultPool.get(session_id).update({
'result': deepcopy(result),
})
@classmethod @classmethod
def _check_current_session_id_last_result_exist(cls): def _check_current_session_id_last_result_exist(cls):
"""检查当前session_id是否在LastResultPool中如果没有则新增一个空字典""" """检查当前session_id是否在LastResultPool中如果没有则新增一个空字典"""
session_id = session_id_manager.get_session_id() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
if session_id not in cls.LastResultPool: project_id = session.get('project_id')
cls.LastResultPool.update({ if project_id not in cls.ScheduleLastResultPool:
session_id: { cls.ScheduleLastResultPool.update({
'result': None, # 上次执行结果 project_id: {
'request_headers': None, # 请求头 'result': None, # 上次执行结果
'request_body': None, # 请求包 'request_headers': None, # 请求头
'response_headers': None, # 应答头 'request_body': None, # 请求包
'response_body': None, # 应答包 'response_headers': None, # 应答头
} 'response_body': None, # 应答包
}) }
})
else:
session_id = session_id_manager.get_session_id()
if session_id not in cls.LastResultPool:
cls.LastResultPool.update({
session_id: {
'result': None, # 上次执行结果
'request_headers': None, # 请求头
'request_body': None, # 请求包
'response_headers': None, # 应答头
'response_body': None, # 应答包
}
})

View File

@ -1,23 +1,46 @@
# coding=utf-8 # coding=utf-8
from flask import request from flask import request, session
from requests.cookies import RequestsCookieJar from requests.cookies import RequestsCookieJar
from datetime import timedelta, datetime from datetime import timedelta, datetime
import uuid import uuid
from typing import Optional, List, Mapping from typing import Optional, List, Mapping
from app.cores.dictionaries import DISPATCHER_TYPE from app.cores.dictionaries import DISPATCHER_TYPE, DISPATCHER_TRIGGER_TYPE
# requests库会在发起请求时根据请求服务器的domain选择合适的cookie作为请求头中的cookie数据发出 # requests库会在发起请求时根据请求服务器的domain选择合适的cookie作为请求头中的cookie数据发出
# 可参考代码 requests.models.prepare_cookies() # 可参考代码 requests.models.prepare_cookies()
class HTTPCookieManager: class HTTPCookieManager:
"""管理自动化测试中请求头Cookie数据""" """
管理自动化测试中请求头Cookie数据
CookiePool = {
http_cookie_manager_id1: {
DISPATCHER_TYPE.DEBUG: RequestsCookieJar(),
DISPATCHER_TYPE.BUILD: RequestsCookieJar(),
},
http_cookie_manager_id1: {
DISPATCHER_TYPE.DEBUG: RequestsCookieJar(),
DISPATCHER_TYPE.BUILD: RequestsCookieJar(),
},
...
}
ScheduleCookiePool = {
project_id1: RequestsCookieJar(),
project_id2: RequestsCookieJar(),
...
}
"""
# cookie池不同的会话放到不同的字典中key为区分会话唯一编号放入cookie中 # cookie池不同的会话放到不同的字典中key为区分会话唯一编号放入cookie中
CookiePool = {} CookiePool = {}
# 对于定时构建的项目使用另一个Cookie池保存key为项目id
ScheduleCookiePool = {}
def __init__(self, app=None): def __init__(self, app=None):
if app is not None: if app is not None:
self.init_app(app) self.init_app(app)
@ -26,65 +49,86 @@ class HTTPCookieManager:
app.http_cookie_manager = self app.http_cookie_manager = self
app.after_request(self._update_cookie) app.after_request(self._update_cookie)
def update_cookie_pool(self, http_cookie_manager_id: str = None, rcj: RequestsCookieJar = None, type: str = None): def update_cookie_pool(self, rcj: RequestsCookieJar = None, type: str = None):
""" """
:param http_cookie_manager_id: 标识当前会话cookie编号
:param rcj: RequestsCookieJar对象一般由应答头获得 response.cookies :param rcj: RequestsCookieJar对象一般由应答头获得 response.cookies
:param type: 需要更新到的cookie类型 DISPATCHER_TYPE.BUILD或DISPATCHER_TYPE.DEBUG :param type: 需要更新到的cookie类型 DISPATCHER_TYPE.BUILD或DISPATCHER_TYPE.DEBUG
:return: None :return: None
""" """
if http_cookie_manager_id is None: # 如果cookie池没有的话先创建默认值
try: if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
http_cookie_manager_id = request.cookies.get(HTTPCookieManagerConfig.COOKIE_NAME) project_id = session.get('project_id')
except KeyError: if project_id:
raise KeyError('在请求上下文cookies中未找到名为%s的cookie' % HTTPCookieManagerConfig.COOKIE_NAME) if project_id not in __class__.ScheduleCookiePool:
self._add_empty_to_cookie_pool()
if rcj is None or type is None:
self._add_empty_to_cookie_pool(http_cookie_manager_id)
else: else:
http_cookie_manager_id = self._get_http_cookie_manager_id()
if http_cookie_manager_id not in __class__.CookiePool: if http_cookie_manager_id not in __class__.CookiePool:
self._add_empty_to_cookie_pool(http_cookie_manager_id) self._add_empty_to_cookie_pool()
if len(rcj) == 0: # 空的RequestsCookieJar # 如果入参rcj为空的RequestsCookieJar则不更新直接返回
return if len(rcj) == 0:
if type in [DISPATCHER_TYPE.DEBUG, DISPATCHER_TYPE.BUILD]: return
# 根据type和dispatcher_trigger_type进行cookie更新
if type in [DISPATCHER_TYPE.DEBUG, DISPATCHER_TYPE.BUILD]:
if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
project_id = session.get('project_id')
if project_id:
old_rcj = __class__.ScheduleCookiePool[project_id]
if old_rcj is None:
__class__.ScheduleCookiePool[project_id] = rcj
else:
self._update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
else:
http_cookie_manager_id = self._get_http_cookie_manager_id()
old_rcj = __class__.CookiePool[http_cookie_manager_id][type] old_rcj = __class__.CookiePool[http_cookie_manager_id][type]
if old_rcj is None: if old_rcj is None:
__class__.CookiePool[http_cookie_manager_id][type] = rcj __class__.CookiePool[http_cookie_manager_id][type] = rcj
else: else:
self._update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj) self._update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
else: else:
raise ValueError('不支持传入type值为%s只支持type=single_case 或 type=build_case' % type) raise ValueError('不支持传入type值为%s只支持type=single_case 或 type=build_case' % type)
def _update_request_cookie_jar(self, old_rcj: RequestsCookieJar, new_rcj: RequestsCookieJar): def _update_request_cookie_jar(self, old_rcj: RequestsCookieJar, new_rcj: RequestsCookieJar):
"""将新的rcj更新到老的rcj上""" """将新的rcj更新到老的rcj上"""
old_rcj.update(other=new_rcj) old_rcj.update(other=new_rcj)
def _add_empty_to_cookie_pool(self, http_cookie_manager_id: str): def _add_empty_to_cookie_pool(self):
""" """
为当前会话在cookie pool中添加一个新的默认值为RequestsCookieJar() 为当前会话在cookie pool中添加一个新的默认值为RequestsCookieJar()
:param http_cookie_manager_id: 标识当前会话cookie编号
:return: None :return: None
""" """
__class__.CookiePool.update({ if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
http_cookie_manager_id: { project_id = session.get('project_id')
DISPATCHER_TYPE.DEBUG: RequestsCookieJar(), if project_id:
DISPATCHER_TYPE.BUILD: RequestsCookieJar(), __class__.ScheduleCookiePool.update({
} project_id: RequestsCookieJar(),
}) })
else:
http_cookie_manager_id = self._get_http_cookie_manager_id()
__class__.CookiePool.update({
http_cookie_manager_id: {
DISPATCHER_TYPE.DEBUG: RequestsCookieJar(),
DISPATCHER_TYPE.BUILD: RequestsCookieJar(),
}
})
def get_request_cookie_jar(self, type: str) -> Optional[RequestsCookieJar]: def get_request_cookie_jar(self, type: str) -> Optional[RequestsCookieJar]:
""" """
获取当前会话中指定类型的cookie数据 获取当前会话中指定类型的cookie数据
:param type: cookie类型 single_case或build_case :param type: cookie类型 DISPATCHER_TYPE.BUILD或DISPATCHER_TYPE.DEBUG
:return: RequestsCookieJar :return: RequestsCookieJar
""" """
try: if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
http_cookie_manager_id = request.cookies.get(HTTPCookieManagerConfig.COOKIE_NAME) project_id = session.get('project_id')
except KeyError: if project_id:
raise KeyError('在请求上下文cookies中未找到名为%s的cookie' % HTTPCookieManagerConfig.COOKIE_NAME) if project_id not in __class__.ScheduleCookiePool:
if http_cookie_manager_id not in __class__.CookiePool: return
return return __class__.ScheduleCookiePool[project_id]
return __class__.CookiePool[http_cookie_manager_id][type] else:
http_cookie_manager_id = self._get_http_cookie_manager_id()
if http_cookie_manager_id not in __class__.CookiePool:
return
return __class__.CookiePool[http_cookie_manager_id][type]
def clear_and_reset(self, rcj: RequestsCookieJar, cookies: List[Mapping]): def clear_and_reset(self, rcj: RequestsCookieJar, cookies: List[Mapping]):
""" """
@ -120,7 +164,6 @@ class HTTPCookieManager:
path='/', path='/',
secure=HTTPCookieManagerConfig.COOKIE_SECURE, secure=HTTPCookieManagerConfig.COOKIE_SECURE,
httponly=HTTPCookieManagerConfig.COOKIE_HTTPONLY) httponly=HTTPCookieManagerConfig.COOKIE_HTTPONLY)
self._add_empty_to_cookie_pool(uuid_)
else: else:
pass pass
return response return response
@ -132,15 +175,24 @@ class HTTPCookieManager:
:return: :return:
""" """
if self.get_request_cookie_jar(type=type) is not None: if self.get_request_cookie_jar(type=type) is not None:
try:
http_cookie_manager_id = request.cookies.get(HTTPCookieManagerConfig.COOKIE_NAME)
except KeyError:
raise KeyError('在请求上下文cookies中未找到名为%s的cookie' % HTTPCookieManagerConfig.COOKIE_NAME)
if type in [DISPATCHER_TYPE.DEBUG, DISPATCHER_TYPE.BUILD]: if type in [DISPATCHER_TYPE.DEBUG, DISPATCHER_TYPE.BUILD]:
__class__.CookiePool[http_cookie_manager_id][type] = RequestsCookieJar() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
project_id = session.get('project_id')
if project_id:
__class__.ScheduleCookiePool[project_id] = RequestsCookieJar()
else:
http_cookie_manager_id = self._get_http_cookie_manager_id()
__class__.CookiePool[http_cookie_manager_id][type] = RequestsCookieJar()
else: else:
raise ValueError('不支持传入type值为%s只支持type=single_case 或 type=build_case' % type) raise ValueError('不支持传入type值为%s只支持type=single_case 或 type=build_case' % type)
def _get_http_cookie_manager_id(self):
"""获取请求上下文中的http_cookie_manager_id"""
try:
return request.cookies.get(HTTPCookieManagerConfig.COOKIE_NAME)
except KeyError:
raise KeyError('在请求上下文cookies中未找到名为%s的cookie' % HTTPCookieManagerConfig.COOKIE_NAME)
class HTTPCookieManagerConfig: class HTTPCookieManagerConfig:
# 标识HTTPCookieManager在当前浏览器客户端对应id的cookie # 标识HTTPCookieManager在当前浏览器客户端对应id的cookie

View File

@ -1,8 +1,10 @@
# coding=utf-8 # coding=utf-8
from typing import Dict from typing import Dict
from flask import session
from app.extensions import session_id_manager from app.extensions import session_id_manager
from app.cores.dictionaries import DISPATCHER_TRIGGER_TYPE
class Variable: class Variable:
@ -21,12 +23,28 @@ class Variable:
}, },
session_id2:{ session_id2:{
... ...
} },
...
}
ScheduleVariablePool = {
project_id: {
key: value,
key1: value1,
...
},
project_id2: {
...
},
...
} }
""" """
# 变量池
VariablePool = {} VariablePool = {}
# 定时任务变量池
ScheduleVariablePool = {}
@classmethod @classmethod
def get_project_variable(cls, project_id): def get_project_variable(cls, project_id):
""" """
@ -36,9 +54,14 @@ class Variable:
:return: 项目级所有变量 :return: 项目级所有变量
:rtype: Dict :rtype: Dict
""" """
session_id = session_id_manager.get_session_id()
cls._set_default_project_variable(project_id=project_id) cls._set_default_project_variable(project_id=project_id)
return cls.VariablePool.get(session_id).get(project_id) if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
project_id = session.get('project_id')
if project_id:
return cls.ScheduleVariablePool.get(project_id)
else:
session_id = session_id_manager.get_session_id()
return cls.VariablePool.get(session_id).get(project_id)
@classmethod @classmethod
def set_project_variable(cls, project_id, key, value): def set_project_variable(cls, project_id, key, value):
@ -51,27 +74,42 @@ class Variable:
:param value: 变量值 :param value: 变量值
:type value: str :type value: str
""" """
session_id = session_id_manager.get_session_id()
cls._set_default_project_variable(project_id=project_id) cls._set_default_project_variable(project_id=project_id)
cls.VariablePool.get(session_id).get(project_id).update({ if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
key: value project_id = session.get('project_id')
}) if project_id:
cls.ScheduleVariablePool.get(project_id).update({
key: value
})
else:
session_id = session_id_manager.get_session_id()
cls.VariablePool.get(session_id).get(project_id).update({
key: value
})
@classmethod @classmethod
def _set_default_project_variable(cls, project_id): def _set_default_project_variable(cls, project_id):
"""当VariablePool为空时为其设置默认值避免取出值为None的项目级变量池""" """当VariablePool为空时为其设置默认值避免取出值为None的项目级变量池"""
session_id = session_id_manager.get_session_id() if session.get('dispatcher_trigger_type') == DISPATCHER_TRIGGER_TYPE.BY_SCHEDULE:
session_var = cls.VariablePool.get(session_id) project_id = session.get('project_id')
if session_var is None: if project_id:
cls.VariablePool.update({ project_var = cls.ScheduleVariablePool.get(project_id)
session_id: { if project_var is None:
project_id: {} cls.ScheduleVariablePool.update({
} project_id: {}
}) })
return else:
project_var = cls.VariablePool.get(session_id).get(project_id) session_id = session_id_manager.get_session_id()
if project_var is None: session_var = cls.VariablePool.get(session_id)
cls.VariablePool.get(session_id).update({ if session_var is None:
project_id: {} cls.VariablePool.update({
}) session_id: {
return project_id: {}
}
})
else:
project_var = cls.VariablePool.get(session_id).get(project_id)
if project_var is None:
cls.VariablePool.get(session_id).update({
project_id: {}
})