前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Requests源码阅读v0.8.0

Requests源码阅读v0.8.0

作者头像
岂不美哉Frost
发布2019-11-30 14:40:09
4340
发布2019-11-30 14:40:09
举报
文章被收录于专栏:Frost's BlogFrost's Blog

工作两年了,一直用python写一些API之类的东西,自动化框架也有涉及,却一直感觉对个人技能提升缓慢。决定开这个坑,是之前看到@wangshunping的read requests,生动有趣,可惜0.8.0之后没有更新了。待我稍稍有了一点看源码的动力,就想接着下去写。真是漫漫长路啊,4409个commit,1000多个PR,更何况还有珠玉在前,实在没有把握能把这块硬骨头给啃下来,写一点是一点吧。作为python的小学生,一些错误在所难免,希望大家指出,互相讨论。 下面就开始吧!

目标

代码语言:javascript
复制
0.8.0 (2011-11-13)
++++++++++++++++++

* Keep-alive support!
* Complete removal of Urllib2
* Complete removal of Poster
* Complete removal of CookieJars
* New ConnectionError raising
* Safe_mode for error catching
* prefetch parameter for request methods
* OPTION method
* Async pool size throttling
* File uploads send real names

源码阅读

v0.7.1

代码语言:javascript
复制
0.7.1 (2011-10-23)
++++++++++++++++++

* Move away from urllib2 authentication handling.
* Fully Remove AuthManager, AuthObject, &c.
* New tuple-based auth system with handler callbacks.
  • 移除urllib2的authentication处理
  • 完全移除AuthManager, AuthObject和。。。&c?
  • 新的元组形式的auth机制和处理器回调函数。
1. 移除urllib2的authentication处理

添加一个auth.py文件,加入了自己实现的auth处理器,包含http_basichttp_digest,分别对应Headers中AutohorizationBasicDigest开头的情形。

2. 完全删除AuthManager, AuthObject和。。。&c?

由于接口改用了session,于是就没有必要使用AuthManager储存认证信息。使用自己实现的处理器,完全删除models.py中相关的代码。

3. 新的元组形式的auth机制和处理器回调函数。

现在:

Python

代码语言:javascript
复制
self.auth = auth_dispatch(auth)

if self.auth:
    auth_func, auth_args = self.auth
    r = auth_func(self, *auth_args)
    self.__dict__.update(r.__dict__)

Python

代码语言:javascript
复制
def dispatch(t):
    """Given an auth tuple, return an expanded version."""

    if not t:
        return t
    else:
        t = list(t)

    # Make sure they're passing in something.
    assert len(t) >= 2

    # If only two items are passed in, assume HTTPBasic.
    if (len(t) == 2):
        t.insert(0, 'basic')

    # Allow built-in string referenced auths.
    if isinstance(t[0], basestring):
        if t[0] in ('basic', 'forced_basic'):
            t[0] = http_basic
        elif t[0] in ('digest',):
            t[0] = http_digest

    # Return a custom callable.
    return (t[0], tuple(t[1:]))

通过dispatch函数,若传入二元元组,则默认前面加上'basic',使用http_basic处理,否则需要指定处理类型。支持自定义处理器:

Python

代码语言:javascript
复制
def pizza_auth(r, username):
    """Attaches HTTP Pizza Authentication to the given Request object.
    """
    r.headers['X-Pizza'] = username

    return r

Then, we can make a request using our Pizza Auth::

>>> requests.get('http://pizzabin.org/admin', auth=(pizza_auth, 'kenneth'))
<Response [200]>

v0.7.2

代码语言:javascript
复制
0.7.2 (2011-10-23)
++++++++++++++++++

* PATCH Fix.

修正BUG(略)

v0.7.3

代码语言:javascript
复制
0.7.3 (2011-10-23)
++++++++++++++++++

* Digest Auth fix.

修正Digest Auth的BUG 主要是删除了一些debug的print语句,估计当时作者脑子也不清醒了,我还注意到他改了一个文件头的"~"的长度,是有够无聊的!0.7.1到0.7.3都在一个多小时内完成,小伙子动力很足啊!

v0.7.4

代码语言:javascript
复制
0.7.4 (2011-10-26)
++++++++++++++++++

* Sesion Hooks fix.

主要是一些代码的美化和小BUG,给session加了一个keep_alive参数,暂时还没用上,应该是为以后做准备。

v0.7.5

代码语言:javascript
复制
0.7.5 (2001-11-04)
++++++++++++++++++

* Response.content = None if there was an invalid repsonse.
* Redirection auth handling.

咦?日期穿越了10年?哈哈,什么时候会改呢?

  • 如果是无效响应则content = None
  • 重定向认证处理
1. 无效响应content = None

加入一个Error Handling:

Python

代码语言:javascript
复制
try:
    self._content = self.raw.read()
except AttributeError:
    return None
2. 重定向认证处理

一个BUG,原来是用dispatch后的auth构造新的Request会导致错误,现在使用self._auth保存原始auth并传入新的Request对象。

v0.7.6

代码语言:javascript
复制
0.7.6 (2011-11-07)
++++++++++++++++++

* Digest authentication bugfix (attach query data to path)
  • Digest 认证的BUG 修复(在路径后附上query)

原来:

Python

代码语言:javascript
复制
path = urlparse(r.request.url).path

现在:

Python

代码语言:javascript
复制
p_parsed = urlparse(r.request.url)
path = p_parsed.path + p_parsed.query

我注意到日期问题已经修复了:

Updated your 2001, to 2011... unless you went back in time ;)

这个幽默。

v0.8.0

代码语言:javascript
复制
0.8.0 (2011-11-13)
++++++++++++++++++

* Keep-alive support!
* Complete removal of Urllib2
* Complete removal of Poster
* Complete removal of CookieJars
* New ConnectionError raising
* Safe_mode for error catching
* prefetch parameter for request methods
* OPTION method
* Async pool size throttling
* File uploads send real names
  • 支持keep_alive参数(填坑来了)
  • 完全抛弃urllib2
  • 完全抛弃Poster
  • 完全抛弃CookieJars
  • 新的ConnectionError抛出
  • 安全的处理异常机制。
  • 为请求方法加入prefetch参数
  • 新的OPTION方法
  • 节省Async池的大小
  • 上传文件发送真实文件名
1. 支持keep_alive参数

作者在v0.8.0全面转向urllib3,这是个第三方的轮子,它相对于urllib2最大的改进是可以重用 HTTP 连接,不用每个 request 都新建一个连接了。这样大大加快了大量 request 时的响应速度。

Python

代码语言:javascript
复制
self.poolmanager = PoolManager(
    num_pools=self.config.get('pool_connections'),
    maxsize=self.config.get('pool_maxsize')
)

Python

代码语言:javascript
复制
proxy = self.proxies.get(_p.scheme)

if proxy:
    conn = poolmanager.proxy_from_url(url)
else:
    # Check to see if keep_alive is allowed.
    if self.config.get('keep_alive'):
        conn = self._poolmanager.connection_from_url(url)
    else:
        conn = connectionpool.connection_from_url(url)

keep_alive是默认打开的,在urllib3中维护了一个连接池,当对某个url进行请求时,会从连接池中取出该连接,然后发送请求时直接调用此连接的子方法。

2. 完全抛弃urllib2

删除了models.py中用来发送请求的build_opener函数,使用urllib3conn.urlopen方法。

3.完全抛弃Poster

同上,用一个轮子换了另一个轮子。。

4. 完全抛弃CookieJars

上测试

Python

代码语言:javascript
复制
def test_session_persistent_cookies(self):

    s = requests.session()

    # Internally dispatched cookies are sent.
    _c = {'kenneth': 'reitz', 'bessie': 'monke'}
    r = s.get(httpbin('cookies'), cookies=_c)
    r = s.get(httpbin('cookies'))

    # Those cookies persist transparently.
    c = json.loads(r.content).get('cookies')
    assert c == _c

    # Double check.
    r = s.get(httpbin('cookies'), cookies={})
    c = json.loads(r.content).get('cookies')
    assert c == _c

    # Remove a cookie by setting it's value to None.
    r = s.get(httpbin('cookies'), cookies={'bessie': None})
    c = json.loads(r.content).get('cookies')
    del _c['bessie']
    assert c == _c

    # Test session-level cookies.
    s = requests.session(cookies=_c)
    r = s.get(httpbin('cookies'))
    c = json.loads(r.content).get('cookies')
    assert c == _c

    # Have the server set a cookie.
    r = s.get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True)
    c = json.loads(r.content).get('cookies')

    assert 'k' in c

    # And server-set cookie persistience.
    r = s.get(httpbin('cookies'))
    c = json.loads(r.content).get('cookies')

    assert 'k' in c

处理响应的cookie:

Python

代码语言:javascript
复制
if 'set-cookie' in response.headers:
    cookie_header = response.headers['set-cookie']

    c = SimpleCookie()
    c.load(cookie_header)

    for k,v in c.items():
        cookies.update({k: v.value})

# Save cookies in Response.
response.cookies = cookies
cookies = self.cookies
self.cookies.update(r.cookies)

发送请求时:

Python

代码语言:javascript
复制
if self.cookies:

    # Skip if 'cookie' header is explicitly set.
    if 'cookie' not in self.headers:

        # Simple cookie with our dict.
        c = SimpleCookie()
        for (k, v) in self.cookies.items():
            c[k] = v

        # Turn it into a header.
        cookie_header = c.output(header='').strip()

        # Attach Cookie header to request.
        self.headers['Cookie'] = cookie_header

使用了标准库里的SimpleCookie处理和生成cookie,而读取cookie全部都是字典类型。其实这些都是为了新的urllib3接口而服务的,从原来的各种Handler改成conn.urlopen以后原来的东西都相应的变化。

5. 新的ConnectionError
6. 安全模式

直接看代码吧:

Python

代码语言:javascript
复制
except MaxRetryError, e:
    if not self.config.get('safe_mode', False):
        raise ConnectionError(e)
    else:
        r = None

except (_SSLError, _HTTPError), e:
    if not self.config.get('safe_mode', False):
        raise Timeout('Request timed out.')

所谓安全模式就是不抛出异常。

7. 新的prefetch参数

也是urllib3支持的参数,当为True时,在发送请求时就读取响应内容,否则跟原来一样调用content方法时读取。至于这个有什么用我还不是太懂,因为我发现当prefetch=True时读取content会出错并且无法获取响应内容,疑似BUG,先放在这里。

8. OPTION请求方法

Option 是一种 HTTP 的请求类型,返回当前 url 支持的全部方法。

9. 节省 async 池的大小

原来:

Python

代码语言:javascript
复制
jobs = [gevent.spawn(send, r) for r in requests]
gevent.joinall(jobs)

现在:

Python

代码语言:javascript
复制
if size:
    pool = Pool(size)
    pool.map(send, requests)
    pool.join()
else:
    jobs = [gevent.spawn(send, r) for r in requests]
    gevent.joinall(jobs)

大概就是传入一个size参数,所有的异步请求都在这个有限大小的池里处理,嗯,又是池,真是一个好用的东西。

10. 上传文件时包含真实文件名

看代码:

Python

代码语言:javascript
复制
def guess_filename(obj):
    """Tries to guess the filename of the given object."""
    name = getattr(obj, 'name', None)
    if name and name[0] != '<' and name[-1] != '>':
        return name

嗯,怎么得到真实文件名?靠猜啊,没有就拉倒。

后记

呼,终于整完了,v0.8.0 包含一个大的重构,我这个累的啊。第一次写这种东西,感觉不是很满意,代码太多了自己的试验不太够,总的也就能理解 80% 左右吧。不管怎样,谢谢大家的阅读,欢迎交流。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-06-03T,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目标
  • 源码阅读
    • v0.7.1
      • 1. 移除urllib2的authentication处理
      • 2. 完全删除AuthManager, AuthObject和。。。&c?
      • 3. 新的元组形式的auth机制和处理器回调函数。
    • v0.7.2
      • v0.7.3
        • v0.7.4
          • v0.7.5
            • 1. 无效响应content = None
            • 2. 重定向认证处理
          • v0.7.6
            • v0.8.0
              • 1. 支持keep_alive参数
              • 2. 完全抛弃urllib2
              • 3.完全抛弃Poster
              • 4. 完全抛弃CookieJars
              • 5. 新的ConnectionError
              • 6. 安全模式
              • 7. 新的prefetch参数
              • 8. OPTION请求方法
              • 9. 节省 async 池的大小
              • 10. 上传文件时包含真实文件名
          • 后记
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档