requests源码简略阅读

背景

requests库号称是为人类设计的HTTP请求库,个人也经常使用这个库,因此对它的源码产生了兴趣,带着这个目的去看看它的源码。

api

requests的最外层是api层,这里提供了对外暴露的接口,比如我们使用requests.get那么这个get方法就是在api这里定义的方法。

def get(url, params=None, **kwargs):
    """Sends a GET request.

    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    kwargs.setdefault('allow_redirects', True)
    return request('get', url, params=params, **kwargs)

这里观察了一系列的方法啊,可以看到这里其实调用的是api中的request方法,而request中调用的是session中的方法。继续跟到session中去

session

sessionrequests用来会话管理的这么一个类,在api层看到的方法参数其实只是一个**kwargs,我们并不知道可以传什么参数进来,但是在session层我们就能看到它接受的参数了。

    def request(self, method, url,
        params=None,
        data=None,
        headers=None,
        cookies=None,
        files=None,
        auth=None,
        timeout=None,
        allow_redirects=True,
        proxies=None,
        hooks=None,
        stream=None,
        verify=None,
        cert=None,
        json=None):

api层会根据调用方法定义好method,而url是用户传入的参数,我们常用的paramsdataheaderscookies等参数在这里就能看到了。

之后代码会把这些参数用来初始化Model层的一个Request对象。初始化后调用prepare_request方法做请求前的准备,其实说白了就是要做请求串的拼装了,因为HTTP请求,本质上来说是TCP的请求,只是数据格式被严格定义好了的字符串。

prepare

准备阶段首先是处理Cookie

cookies = request.cookies or {}

# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
    cookies = cookiejar_from_dict(cookies)

# Merge with session cookies
merged_cookies = merge_cookies(
    merge_cookies(RequestsCookieJar(), self.cookies), cookies)

从源码可以看到,Cookie是通过cookiejar这个对象来管理的,这里会把传入的cookie与会话中的cookie做一个合并。再往下就是鉴权。

cookie和鉴权处理了之后,就会把参数全部用来初始化Model层的PreparedRequest对象,之后把这个对象返回回去。

当然,在PreparedRequest对象中会把改解析的数据全部做一次解析,源码如下:

    def prepare(self, method=None, url=None, headers=None, files=None,
        data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
        """Prepares the entire request with the given parameters."""

        self.prepare_method(method)
        self.prepare_url(url, params)
        self.prepare_headers(headers)
        self.prepare_cookies(cookies)
        self.prepare_body(data, files, json)
        self.prepare_auth(auth, url)

        # Note that prepare_auth must be last to enable authentication schemes
        # such as OAuth to work on a fully prepared request.

        # This MUST go after prepare_auth. Authenticators could add a hook
        self.prepare_hooks(hooks)

这里继续跟下去,会看到很多编码的处理。具体的代码有兴趣的可以自己跟一下,基本上都是一些字符串的处理。

  • Cookie的处理

值得一提的是,之前我一直有一个困惑,Cookie在请求头里面的,为什么requests的参数中会把cookie单独列出来,我直接在headers里面写上"Cookie": "abcdefg"和用cookie参数传进去会有什么区别。从源代码里面去看,实际上是没有区别的。

    def prepare_cookies(self, cookies):
        if isinstance(cookies, cookielib.CookieJar):
            self._cookies = cookies
        else:
            self._cookies = cookiejar_from_dict(cookies)

        cookie_header = get_cookie_header(self._cookies, self)
        if cookie_header is not None:
            self.headers['Cookie'] = cookie_header

可以看到,最后会把cookiejar对象中的数据转成字符串后扔到header里面。其实也确实应该最终放到头部,毕竟在HTTP标准中,Cookie就是放在头部往下传的。

  • 数据层的处理

请求数据有三种类型,json数据、表单数据文件

首先会处理json

if not data and json is not None:
    content_type = 'application/json'
    body = complexjson.dumps(json)

如果传入参数是json,那么就会把请求的Content-Type设置为application/json,然后用json.dumps()去处理数据。

然后来判断是否是流信息。

is_stream = all([
            hasattr(data, '__iter__'),
            not isinstance(data, (basestring, list, tuple, dict))
        ])

这里有意思的是all这个方法。

再接下来是处理文件类型和如果是文件类型。

文件类型支持多个参数,比较重要的是要调用open方法把文件打开后传入,而源码在处理的时候会调用对象的read()方法,fdata = fp.read()

最后才是处理表单类型的数据

if data:
    body = self._encode_params(data)
    if isinstance(data, basestring) or hasattr(data, 'read'):
        content_type = None
    else:
        content_type = 'application/x-www-form-urlencoded'

发送请求

发送请求其实调用的是urllib3的能力。在上面的准备阶段做完之后,会把配置做一个合并,然后调用send方法,send方法会调到adapter.py中封装的方法,请求完毕后,返回请求的结果。为了使用起来友好,在拿到返回结果之后,会把请求的对象和返回的对象做一个合并。

return self.build_response(request, resp)

    def build_response(self, req, resp):
        response = Response()

        # Fallback to None if there's no status_code, for whatever reason.
        response.status_code = getattr(resp, 'status', None)

        # Make headers case-insensitive.
        response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {}))

        # Set encoding.
        response.encoding = get_encoding_from_headers(response.headers)
        response.raw = resp
        response.reason = response.raw.reason

        if isinstance(req.url, bytes):
            response.url = req.url.decode('utf-8')
        else:
            response.url = req.url

        # Add new cookies from the server.
        extract_cookies_to_jar(response.cookies, req, resp)

        # Give the Response some context.
        response.request = req
        response.connection = self

        return response

这里就是对返回结果的一些封装了。

最后

可以看到requests库的封装比较好,通过api层来暴露接口,往下用一个session来做规划管理,收拢数据和配置,再根据关键的method做路由分发,组参数。最后调用请求方法,通过这样的分层包装,使用起来非常简单,但功能却很强大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容