requests源码分析

0.前言

(1) 拆部分reques中感兴趣t的轮子

(2)对一些感兴趣的pythonic写法做一些归纳

1.用object.__setattr__来初始化构造函数

反正我之前就是直接实例对象时把所有参数传入构造函数的,一般人都这样..但事实证明这种方式并不好(可能),所以后来作者又把这种方式改掉了...但原谅我也不知道这两者有什么好坏之分..

class Request(object):
    """The :class:`Request` object. It carries out all functionality of
    Requests. Recommended interface is with the Requests functions.

    """

    _METHODS = (‘GET‘, ‘HEAD‘, ‘PUT‘, ‘POST‘, ‘DELETE‘)

    def __init__(self):
        self.url = None
        self.headers = dict()
        self.method = None
        self.params = {}
        self.data = {}
        self.response = Response()
        self.auth = None
        self.sent = False

    def __repr__(self):
        try:
            repr = ‘<Request [%s]>‘ % (self.method)
        except:
            repr = ‘<Request object>‘
        return repr

    def __setattr__(self, name, value):
        if (name == ‘method‘) and (value):
            if not value in self._METHODS:
                raise InvalidMethod()

        object.__setattr__(self, name, value)

初始化操作:

def get(url, params={}, headers={}, auth=None):
    """Sends a GET request. Returns :class:`Response` object.
    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
    :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
    :param auth: (optional) AuthObject to enable Basic HTTP Auth.
    """

    r = Request()

    r.method = ‘GET‘
    r.url = url
    r.params = params
    r.headers = headers
    r.auth = _detect_auth(url, auth)

    r.send()

    return r.response

2.大量复杂的参数传递时采用**kwargs

用**kwargs可在方法间的传递大量参数,不需要自己每次都初始化一个dict用来传参(嗯,之前我就是这样的傻逼)

def get(url, params={}, headers={}, cookies=None, auth=None):
    return request(‘GET‘, url, params=params, headers=headers, cookiejar=cookies, auth=auth)

def request(method, url, **kwargs):
    data = kwargs.pop(‘data‘, dict()) or kwargs.pop(‘params‘, dict())

    r = Request(method=method, url=url, data=data, headers=kwargs.pop(‘headers‘, {}),
                cookiejar=kwargs.pop(‘cookies‘, None), files=kwargs.pop(‘files‘, None),
                auth=kwargs.pop(‘auth‘, auth_manager.get_auth(url)))
    r.send()

    return r.response

3.monkey patch

热修复技术方案,可以参考协程,协程为了实现异步效果,替换了python原生的很多库。就是模块在加载前,把自己的模块在系统加载前替换掉原系统模块,然后达到自己的(不可告人的)目的。

这里其实不是requests使用了monkey patch,而是pyopenssl这个库,这个是为了修复python2.7中SNI的bug,将原来的ssl_wrap_socket方法做了替换(不过我没看到requests有任何注入操作,坑爹...)

# 替换
def inject_into_urllib3():
    ‘Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.‘

    connection.ssl_wrap_socket = ssl_wrap_socket
    util.HAS_SNI = HAS_SNI
    util.IS_PYOPENSSL = True

# 还原
def extract_from_urllib3():
    ‘Undo monkey-patching by :func:`inject_into_urllib3`.‘

    connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket
    util.HAS_SNI = orig_util_HAS_SNI
    util.IS_PYOPENSSL = False

如果在请求https过程中出现SNIMissing的问题,可以考虑这么解决:

pip install pyopenssl ndg-httpsclient pyasn1

try:
    import urllib3.contrib.pyopenssl
    urllib3.contrib.pyopenssl.inject_into_urllib3()
except ImportError:
    pass

相当于就是执行主动注入的操作(但这个不应该是requests框架自己该集成的么...)

4.hook函数

requests中有一个钩子函数,看历史版本,原来提供的回调入口有好几个,目前只有response一个回调入口了,测试代码如下

import requests

def print_url(r, *args, **kwargs):
    print r.content
    print r.url

requests.get(‘http://httpbin.org‘, hooks=dict(response=print_url))

这会发生什么呢?requests会在requests.Response返回前回调这个print_url这个方法。可以看到,回调操作是在requests拿到请求结果后才去操作的

    def send(self, request, **kwargs):
        """
        Send a given PreparedRequest.

        :rtype: requests.Response
        """
        ...

        # Get the appropriate adapter to use
        adapter = self.get_adapter(url=request.url)

        # Start time (approximately) of the request
        start = datetime.utcnow()

        # Send the request
        r = adapter.send(request, **kwargs)

        # Total elapsed time of the request (approximately)
        r.elapsed = datetime.utcnow() - start

        # Response manipulation hooks
        r = dispatch_hook(‘response‘, hooks, r, **kwargs)    

那dispatch_hook又干了什么呢?

def dispatch_hook(key, hooks, hook_data, **kwargs):
    """Dispatches a hook dictionary on a given piece of data."""
    hooks = hooks or dict()
    hooks = hooks.get(key)
    if hooks:
        if hasattr(hooks, ‘__call__‘):
            hooks = [hooks]
        for hook in hooks:
            _hook_data = hook(hook_data, **kwargs)
            if _hook_data is not None:
                hook_data = _hook_data
    return hook_data

可以看到dispatch_hook本身是可以拓展的,但可惜的是目前requests只有response入口了,也许是为了安全吧。

其实说真的,requests的hook使用起来真的不够好,真正好用的hook,可以看看flask.

5.上下文管理器(历史版本)

with requests.settings(timeout=0.5):
    requests.get(‘http://example.org‘)
    requests.get(‘http://example.org‘, timeout=10)

在with之中,所有的配置加载都是在局部生效的,就算requests.get(‘http://example.org‘, timeout=10),但requests对象中的timeout属性依然是0.5而不是10,怎么实现的呢?

class settings:
    """Context manager for settings."""

    cache = {}

    def __init__(self, timeout):
        self.module = inspect.getmodule(self)

        # Cache settings
        self.cache[‘timeout‘] = self.module.timeout

        self.module.timeout = timeout

    def __enter__(self):
        pass

    def __exit__(self, type, value, traceback):
        # Restore settings
        for key in self.cache:
            setattr(self.module, key, self.cache[key])

其实很简单,只要在进入这个context时,将原有的属性储存起来,退出context时,重新set回去就行了。

6.重定向redirect

requests对每一个send请求都会做重定向的判断,具体就是如果是重定向,那就执行以下这个方法

    def resolve_redirects(self, resp, req, stream=False, timeout=None,
                          verify=True, cert=None, proxies=None, **adapter_kwargs):
        """Receives a Response. Returns a generator of Responses."""

        i = 0
        hist = [] # keep track of history

        while resp.is_redirect:
            prepared_request = req.copy()

            if i > 0:
                # Update history and keep track of redirects.
                hist.append(resp)
                new_hist = list(hist)
                resp.history = new_hist
       ...

            url = resp.headers[‘location‘]

            # Handle redirection without scheme (see: RFC 1808 Section 4)
            if url.startswith(‘//‘):
                parsed_rurl = urlparse(resp.url)
                url = ‘%s:%s‘ % (parsed_rurl.scheme, url)

       ...
            extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
            prepared_request._cookies.update(self.cookies)
            prepared_request.prepare_cookies(prepared_request._cookies)

            # Rebuild auth and proxy information.
            proxies = self.rebuild_proxies(prepared_request, proxies)
            self.rebuild_auth(prepared_request, resp)

            # Override the original request.
            req = prepared_request

            resp = self.send(
                req,
                stream=stream,
                timeout=timeout,
                verify=verify,
                cert=cert,
                proxies=proxies,
                allow_redirects=False,
                **adapter_kwargs
            )

            extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)

            i += 1
            yield resp

可以看到,requests会从url = resp.headers[‘location‘]取出重定向后的url,将resp追加到history中,然后重设head,cookie,proxy,auth执行self.send操作,然后yield resp后进入下一次循环,判断是否是redirect,最多redirect次数为30次.

时间: 2024-10-10 05:52:28

requests源码分析的相关文章

SpringMVC源码分析(3)DispatcherServlet的请求处理流程

<SpringMVC源码分析(1)标签解析>:介绍了解析过程中,初始化若干组件. <SpringMVC源码分析(2)DispatcherServlet的初始化>:初始化DispatcherServlet的多个组件. 本文继续分析DispatcherServlet解析请求的过程. 概览 ①:DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件. ②:HanlerMappi

ABP源码分析三十三:ABP.Web

ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事一,在Application_Start完成AbpBootstrapper的初始化.整个ABP系统的初始化就是通过AbpBootstrapper完成初始化的.二,在Application_BeginRequest设置根据request或cookie中的Culture信息,完成当前工作线程的CurrentCu

[Android]Volley源码分析(四)

上篇中有提到NetworkDispatcher是通过mNetwork(Network类型)来进行网络访问的,现在来看一下关于Network是如何进行网络访问的. Network部分的类图: Network有一个实现类BasicNetwork,它有一个mHttpStack的属性,实际的网络请求是由这个mHttpStack来进行的,看BasicNetwork的performRequest()方法, 1 @Override 2 public NetworkResponse performRequest

Linux tcp被动打开内核源码分析

[我是从2个角度来看,其实所谓2个角度,是发现我分析源码时,分析重复了,写了2个分析报告,所以现在都贴出来.] [如果你是想看看,了解一下内核tcp被动打开时如何实现的话,我觉得还是看看概念就可以了,因为即使看了源码,过一个个礼拜你就忘了,如果是你正在修改协议栈,为不知道流程而发愁,那么希望你能看看源码以及注释,希望你给你帮助.] 概念: tcp被动打开,前提是你listen,这个被动打开的前提.你listen过后,其实创建了一个监听套接字,专门负责监听,不会负责传输数据. 当第一个syn包到达

Volley源码分析

Volley源码分析 Volley简介 volley官方地址 在Google I/0 2013中发布了Volley.Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮. 这是Volley名称的由来:a burst or emission of many things or a large amount at once.Volley特别适合数据量不大但是通信频繁的场景. Github上面已经有大神做了镜像,使用Gradle更方便.Volley On Github Vo

Solr4.8.0源码分析(19)之缓存机制(二)

Solr4.8.0源码分析(19)之缓存机制(二) 前文<Solr4.8.0源码分析(18)之缓存机制(一)>介绍了Solr缓存的生命周期,重点介绍了Solr缓存的warn过程.本节将更深入的来介绍下Solr的四种缓存类型,以及两种SolrCache接口实现类. 1.SolrCache接口实现类 前文已经提到SolrCache有两种接口实现类:solr.search.LRUCache 和 solr.search.LRUCache. 那么两者具体有啥区别呢? 1.1 solr.search.LR

Solr4.8.0源码分析(5)之查询流程分析总述

Solr4.8.0源码分析(5)之查询流程分析总述 前面已经写到,solr查询是通过http发送命令,solr servlet接受并进行处理.所以solr的查询流程从SolrDispatchsFilter的dofilter开始.dofilter包含了对http的各个请求的操作.Solr的查询方式有很多,比如q,fq等,本章只关注select和q.页面下发的查询请求如下:http://localhost:8080/solr/test/select?q=code%3A%E8%BE%BD*+AND+l

[软件测试]网站压测工具Webbench源码分析

一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench使用C语言编写,下面是其下载链接: http://home.tiscali.cz/~cz210552/webbench.html 说到这里,我赶脚非常有必要给这个网站局部一个截图,如下图: 第一次看到这张图片,着实吃了一精!居然是2004年最后一次更新,我和我的小伙伴们都惊呆了.不过既然现在大家还都

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte