HttpRunner3源码阅读:6.请求客户端client
2021-08-09 11:03
661 查看
client
上一篇读了
parser.py,处理语法$var,${func($var1)}变量、函数的文件,这次来看client.py看名字就差不多明白了 请求方法封装
导包
import json # 内置json库 import time # 时间处理 import requests # 第三方请求库 requests import urllib3 # 内置的 urllib3 requests库基于它实现 from loguru import logger # 日志库 from requests import Request, Response # 导入 请求类、响应类 from requests.exceptions import ( # 一些异常类 InvalidSchema, InvalidURL, MissingSchema, RequestException, ) from httprunner.models import RequestData, ResponseData from httprunner.models import SessionData, ReqRespData from httprunner.utils import lower_dict_keys, omit_long_data # key转换小写, 长度处理
RequestsData & ResponseData
这里把之前的模型类复制过来
# 请求 class RequestData(BaseModel): method: MethodEnum = MethodEnum.GET url: Url headers: Headers = {} cookies: Cookies = {} body: Union[Text, bytes, List, Dict, None] = {} # 响应 class ResponseData(BaseModel): status_code: int headers: Dict cookies: Cookies encoding: Union[Text, None] = None content_type: Text body: Union[Text, bytes, List, Dict]
源码附注释
# 禁用InsecureRequestWarning urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 继承Response 类 ,重写 raise_for_status 状态码异常方法 class ApiResponse(Response): def raise_for_status(self): if hasattr(self, "error") and self.error: raise self.error Response.raise_for_status(self) # 从响应对象获取到请求和响应信息 def get_req_resp_record(resp_obj: Response) -> ReqRespData: """ get request and response info from Response() object. """ def log_print(req_or_resp, r_type): msg = f"\n================== {r_type} details ==================\n" for key, value in req_or_resp.dict().items(): if isinstance(value, dict) or isinstance(value, list): value = json.dumps(value, indent=4, ensure_ascii=False) msg += "{:<8} : {}\n".format(key, value) logger.debug(msg) # record actual request info # 响应对象拿到请求header request_headers = dict(resp_obj.request.headers) request_cookies = resp_obj.request._cookies.get_dict() # 拿到请求cookies request_body = resp_obj.request.body # 拿到请求体 if request_body is not None: try: request_body = json.loads(request_body) except json.JSONDecodeError: # str: a=1&b=2 pass except UnicodeDecodeError: # bytes/bytearray: request body in protobuf pass except TypeError: # neither str nor bytes/bytearray, e.g. <MultipartEncoder> pass request_content_type = lower_dict_keys(request_headers).get("content-type") if request_content_type and "multipart/form-data" in request_content_type: # upload file type 如果是上传文件 就改请求体内容 request_body = "upload file stream (OMITTED)" # 实例RequestData模型 request_data = RequestData( method=resp_obj.request.method, url=resp_obj.request.url, headers=request_headers, cookies=request_cookies, body=request_body, ) # log request details in debug mode log_print(request_data, "request") # record response info resp_headers = dict(resp_obj.headers) # 响应头 lower_resp_headers = lower_dict_keys(resp_headers) content_type = lower_resp_headers.get("content-type", "") if "image" in content_type: # response is image type, record bytes content only response_body = resp_obj.content # 二进制内容获取 else: try: # try to record json data response_body = resp_obj.json() # 响应结果 except ValueError: # only record at most 512 text charactors resp_text = resp_obj.text response_body = omit_long_data(resp_text) # 长度处理 # 实例化ResponseData模型 response_data = ResponseData( status_code=resp_obj.status_code, cookies=resp_obj.cookies or {}, encoding=resp_obj.encoding, headers=resp_headers, content_type=content_type, body=response_body, ) # log response details in debug mode log_print(response_data, "response") # 实例化ReqRespData 其就是 RequestData ResponseData 组成 req_resp_data = ReqRespData(request=request_data, response=response_data) return req_resp_data # 继承requests.Session class HttpSession(requests.Session): """ Class for performing HTTP requests and holding (session-) cookies between requests (in order to be able to log in and out of websites). Each request is logged so that HttpRunner can display statistics. This is a slightly extended version of `python-request <http://python-requests.org>`_'s :py:class:`requests.Session` class and mostly this class works exactly the same. """ def __init__(self): # 调用父类构造方法 super(HttpSession, self).__init__() # 实例SessionData模型 self.data = SessionData() def update_last_req_resp_record(self, resp_obj): # 更新响应对象 """ update request and response info from Response() object. """ # TODO: fix self.data.req_resps.pop() self.data.req_resps.append(get_req_resp_record(resp_obj)) def request(self, method, url, name=None, **kwargs): # 请求方法 """ Constructs and sends a :py:class:`requests.Request`. Returns :py:class:`requests.Response` object. :param method: method for the new :class:`Request` object. :param url: URL for the new :class:`Request` object. :param name: (optional) Placeholder, make compatible with Locust's HttpSession :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of ``'filename': file-like-objects`` for multipart encoding upload. :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) How long to wait for the server to send data before giving up, as a float, or \ a (`connect timeout, read timeout <user/advanced.html#timeouts>`_) tuple. :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. """ self.data = SessionData() # timeout default to 120 seconds kwargs.setdefault("timeout", 120) # set stream to True, in order to get client/server IP/Port kwargs["stream"] = True start_timestamp = time.time() # 计时 response = self._send_request_safe_mode(method, url, **kwargs) response_time_ms = round((time.time() - start_timestamp) * 1000, 2) # 算时间 try: # 拿客户端数据,确实现在才知道这种操作 client_ip, client_port = response.raw.connection.sock.getsockname() self.data.address.client_ip = client_ip self.data.address.client_port = client_port logger.debug(f"client IP: {client_ip}, Port: {client_port}") except AttributeError as ex: logger.warning(f"failed to get client address info: {ex}") try: # 拿服务端数据 server_ip, server_port = response.raw.connection.sock.getpeername() self.data.address.server_ip = server_ip self.data.address.server_port = server_port logger.debug(f"server IP: {server_ip}, Port: {server_port}") except AttributeError as ex: logger.warning(f"failed to get server address info: {ex}") # get length of the response content content_size = int(dict(response.headers).get("content-length") or 0) # record the consumed time self.data.stat.response_time_ms = response_time_ms self.data.stat.elapsed_ms = response.elapsed.microseconds / 1000.0 # 响应时间 self.data.stat.content_size = content_size # record request and response histories, include 30X redirection response_list = response.history + [response] self.data.req_resps = [ get_req_resp_record(resp_obj) for resp_obj in response_list ] try: response.raise_for_status() except RequestException as ex: logger.error(f"{str(ex)}") else: logger.info( f"status_code: {response.status_code}, " f"response_time(ms): {response_time_ms} ms, " f"response_length: {content_size} bytes" ) return response def _send_request_safe_mode(self, method, url, **kwargs): """ Send a HTTP request, and catch any exception that might occur due to connection problems. Safe mode has been removed from requests 1.x. """ try: return requests.Session.request(self, method, url, **kwargs) except (MissingSchema, InvalidSchema, InvalidURL): raise except RequestException as ex: resp = ApiResponse() resp.error = ex resp.status_code = 0 # with this status_code, content returns None resp.request = Request(method, url).prepare() # "Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it. return resp
相关文章推荐
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- 使用AsycHttpClient请求Tomcat的新闻客户端的实现
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- HttpRunner3源码阅读:7.响应后处理 response.py
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- Httpclient 请求客户端 服务端代码
- HttpRunner3源码阅读:8. 用例文件生成并格式化make
- Android开发之HttpClient异步请求数据的方法详解【附demo源码下载】
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- HttpClient4.3.6源码阅读 RequestConfig.Builder(Builder模式实践)
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- commons-httpclient 服务端模拟客户端发送请求
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- HttpRunner3源码阅读:9. 测试用例中的类定义testcase
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- HttpAsyncClient请求流程源码分析
- HttpRunner3源码阅读:10.测试执行的处理 runner
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- Android Asynchronous Http Client-Android异步网络请求客户端接口