程序员Python 运维

python requests库源码阅读

2017-10-28  本文已影响1275人  allen哦

python requests库提供了丰富便捷的http接口,属于python中最常用的第三方库之一。之前用requests做并发请求的时候遇到过一些问题,因此想深入了解这个库的用法和实现原理,就试着阅读了这个库的源码,做一下梳理和记录。

代码梳理

可以看到这个request方法的是通过sessions.Session()来实现的,一直只知道requests中的 session是用来维持长链接的,但其实requests提供的基本http方法底层也是通过session实现
的,只不过每一个基本方法调用完,这个session就结束了,其相关的资源(包括连接,cookies等)也就
释放了。

也可以看到requests默认只支持httphttps两种schema

PoolManager类则通过一个RecentlyUsedContainer管理着一定数量的http连接池,每一个相同的(schema, host, port)维护一个连接池,每个连接池维护一定数量的http连接,HTTPAdapter类提供的默认值为10个连接池,每个连接池最多维护10个连接。

定制化

requests提供了非常简易的接口供用户使用,但是并不代表requests底层实现就非常简陋,其底层有相当多的考量,但是这些考量在简单的接口中被一些默认的使用方式或参数屏蔽掉了,平时遇到比较复杂的问题,就需要定制化,自己在一个项目中通过代理模式做过一个简单的参数和异常处理的定制化。

# 通过这个类实现异常和错误的统一处理
class _ErrorResponse(Response):

    def __init__(self, reason):
        super(_ErrorResponse, self).__init__()
        self.reason = reason

    @property
    def ok(self):
        return False

    def __bool__(self):
        return self.ok
    

# requests.Session类的代理类,实现一系列的定制化    
class _SessionWrap(object):
    DEFAULT_RETRY = 3
    
    #引入退火因子,可能会导致失败重试时间延迟增大
    DEFAULT_BACKOFF_FACTOR = 1
    
    HTTP_METHODS = ('HEAD', 'GET', 'PUT', 'POST', 'DELETE', 'PATCH')
    
    # 对pool数量和每个pool中维护的连接数量定制,可以根据实际情况调整
    DEFAULT_POOL_CONNS = 100
    DEFAULT_POOL_MAXSIZE = 100

    def __init__(self, retry=DEFAULT_RETRY,
                 backoff_factor=DEFAULT_BACKOFF_FACTOR):
        
        #retry的定制
        self._retry = Retry(total=retry, backoff_factor=backoff_factor)
        self._http_adapter = HTTPAdapter(pool_connections=self.DEFAULT_POOL_CONNS,
                                         pool_maxsize=self.DEFAULT_POOL_MAXSIZE,
                                         max_retries=retry)
        self._session = requests.session()
        self._session.mount("http://", self._http_adapter)
        self._session.mount("https://", self._http_adapter)
        
    # 实现上下文管理器协议,提供更简约、安全的接口

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        return self._session.close()


     # 通过这个方法来统一处理异常和错误
    def _wrap_http(self, method):
        def http_request(url, **kwargs):
            try:
                response = getattr(self._session, method)(url, **kwargs)
                if not response:
                    msg = u'http request {0} failed, with method {1}, kwargs: {2},' \
                          u'return status_code: {3}, content: {4}'.format(
                            response.url, method, json.dumps(kwargs, ensure_ascii=False),
                            response.status_code, response.content)
                    logger.error(msg)
                    response.reason = msg
            except Exception as e:
                msg = u'http request {0} failed, with method {1}, kwargs: {2}, error: {3}'.format(
                    url, method, json.dumps(kwargs, ensure_ascii=False), str(e))
                logger.exception(msg)
                response = _ErrorResponse(reason=msg)
            return response
        return http_request


     # 代理方法和属性至self._session
    def __getattr__(self, item):
        if item in self.__dict__:
            return self.__dict__[item]
        elif item.upper() in self.HTTP_METHODS:
            return self._wrap_http(item.lower())
        else:
            return getattr(self._session, item)


# 也效仿requests中的session.py提供一个简约的函数接口,屏蔽掉_SessionWrap的复杂实现逻辑
def http_session(retry=3, backoff_factor=1):
    return _SessionWrap(retry=retry, backoff_factor=backoff_factor)

启发

  1. 对于常用类库还是要多了解其源码和实现逻辑,在很多情况下才可以发挥其最大威力。

  2. 类库的视线需要考虑到各种情况,但通过各种默认方式和参数可以屏蔽掉底层复杂的实现,为最常用功能提供最非常的接口,大多数情况下能用得很爽,如果遇到比较特殊的情况又可以很容易定制。

  3. 一些非核心功能可以通过Mixin模式来实现,既保证了主干类逻辑的简单清晰,也实现了相关功能。

微信公众号

欢迎关注我的微信公众号: allen_thing(allen独白), 后期主要会在微信公众号分享自己的一些总结和思考


微信公众号二维码
上一篇下一篇

猜你喜欢

热点阅读