• HttpRunner3源码阅读:6.请求客户端client


    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"
    ================== {r_type} details ==================
    "
            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} : {}
    ".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
    
    
    作者:zy7y
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    UVA 11488 Hyper Prefix Sets (字典树)
    UVALive 3295 Counting Triangles
    POJ 2752 Seek the Name, Seek the Fame (KMP)
    UVA 11584 Partitioning by Palindromes (字符串区间dp)
    UVA 11100 The Trip, 2007 (贪心)
    JXNU暑期选拔赛
    计蒜客---N的-2进制表示
    计蒜客---线段的总长
    计蒜客---最大质因数
    JustOj 2009: P1016 (dp)
  • 原文地址:https://www.cnblogs.com/zy7y/p/15104775.html
Copyright © 2020-2023  润新知