Requests源码解读01-README文件&api模块&session模块
Requests是什么?
requests是一个python的网络请求库,它的口号是“让 HTTP 服务人类”!事实上它也确实是最好用的网络请求库(远远好用于urllib、httplib等)。正如其官方所说,requests是为人类设计的Python HTTP 库。而之所以requsets能够如此优雅,也正是因为其开发团队是以 PEP 20 的箴言为中心开发的,这也是他们的开发哲学:
- Beautiful is better than ugly.(美丽优于丑陋)
- Explicit is better than implicit.(直白优于含蓄)
- Simple is better than complex.(简单优于复杂)
- Complex is better than complicated.(复杂优于繁琐)
- Readability counts.(可读性很重要)
为什么要用Requests
正是因为其简单易用,恰到好处的抽象使其对外提供了非常优美的API,帮助你减少绝大多数的手工劳动(这一切都是因为requests对urllib3进行了高度封装)。
源码分析
不管你是日常使用还是作为学习,都建议你静下心来阅读一下requests的源码,这也是最符合Python 风格的代码,简单、优美。
分析版本:v2.18.1
首先看一下requests的目录结构:
requests
├── AUTHORS.rst
├── HISTORY.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── NOTICE
├── README.md
├── docs
│ ├── Makefile
│ ├── _static
│ │ └── custom.css
│ ├── _templates
│ │ ├── hacks.html
│ │ ├── sidebarintro.html
│ │ └── sidebarlogo.html
│ ├── _themes
│ │ ├── LICENSE
│ │ └── flask_theme_support.py
│ ├── api.rst
│ ├── community
│ │ ├── faq.rst
│ │ ├── out-there.rst
│ │ ├── recommended.rst
│ │ ├── release-process.rst
│ │ ├── support.rst
│ │ ├── updates.rst
│ │ └── vulnerabilities.rst
│ ├── conf.py
│ ├── dev
│ │ ├── authors.rst
│ │ └── contributing.rst
│ ├── index.rst
│ ├── make.bat
│ └── user
│ ├── advanced.rst
│ ├── authentication.rst
│ ├── install.rst
│ └── quickstart.rst
├── ext
│ ├── LICENSE
│ ├── flower-of-life.jpg
│ ├── kr-compressed.png
│ ├── kr.png
│ ├── psf-compressed.png
│ ├── psf.png
│ ├── requests-logo-compressed.png
│ ├── requests-logo.ai
│ ├── requests-logo.png
│ ├── requests-logo.svg
│ ├── ss-compressed.png
│ └── ss.png
├── pytest.ini
├── requests
│ ├── __init__.py
│ ├── __version__.py
│ ├── _internal_utils.py
│ ├── adapters.py
│ ├── api.py
│ ├── auth.py
│ ├── certs.py
│ ├── compat.py
│ ├── cookies.py
│ ├── exceptions.py
│ ├── help.py
│ ├── hooks.py
│ ├── models.py
│ ├── packages.py
│ ├── sessions.py
│ ├── status_codes.py
│ ├── structures.py
│ └── utils.py
├── requirements-dev.txt
├── setup.cfg
├── setup.py
├── tests
│ ├── __init__.py
│ ├── compat.py
│ ├── conftest.py
│ ├── test_help.py
│ ├── test_hooks.py
│ ├── test_lowlevel.py
│ ├── test_packages.py
│ ├── test_requests.py
│ ├── test_structures.py
│ ├── test_testserver.py
│ ├── test_utils.py
│ ├── testserver
│ │ ├── __init__.py
│ │ └── server.py
│ └── utils.py
└── tox.ini
11 directories, 80 files
对于一个项目而言,首先应该认真看一下README文件,这个文件里面一般都是作者最想让你了解的,包括这个项目的设计思路、如何安装、上手等。
README文件
README文件的第一部分通过一个简单的使用示例,从而说明Requests能够让你非常轻松的发送一个HTTP/1.1 请求,你不需要手动为URL添加查询字符串,也不需要为post数据进行表单编码。而且,这仅仅是Requests的冰山一角!
README文件的第二部分介绍了如何安装及支持版本,这里不过多介绍。
README文件的第三部分介绍了Requests的功能特性,主要有以下几点:
- Keep-Alive & 连接池
- 国际化域名和 URL
- 带持久 Cookie 的会话
- 浏览器式的 SSL 认证
- 自动内容解码
- 基本/摘要式的身份认证
- 优雅的 key/value Cookie
- 自动解压
- Unicode 响应体
- HTTP(S) 代理支持
- 文件分块上传
- 流下载
- 连接超时
- 分块请求
- 支持 .netrc
README文件最后放入了用户使用文档,主要介绍了API的使用。
requests是在urllib3的基础上(划重点)进行了高度封装,从而对外提供了及其易用的API,对外暴露的所有api统一在/requests/api.py文件中接下来我们一起去这个文件看一下requests对外提供了哪些api。
api.py
首先,我们看一下api模块里面对外提供了哪些api:
除了第一个request方法,其他7个方法是不是很熟悉,没错,正是HTTP协议的基本方法。正是这恰到好处的抽象,使得api如此优美、简单!
这里简单介绍以下这几种HTTP方法:
- GET方法:获取URL指定的资源
- OPTIONS方法:查询URL指定的资源支持的方法
- HEAD方法:获取报文首部,用于确认URL的有效性等
- POST方法:传输实体主体
- PUT方法:传输文件
- PATCH方法:用来对已知资源进行局部更新(对PUT方法的补充)
- DELETE方法:删除URL指定的资源
接下来我们来看看这几个api方法。
get方法
源码:
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
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)
get方法的入参为:
- url 请求的url链接,用来创建Request对象的
- params 可选,Request对象需要发送的查询字符串,可以是字典、列表、元组或者字节码
- **kwargs 不定长参数,Request对象的可选参数
get方法的方法体:
首先是
kwargs.setdefault('allow_redirects', True)
将allow_redirects设置为True,既开启自动重定向处理。
接下来就是调用request方法,将method设为get,并将结果返回
return request('get', url, params=params, **kwargs)
options方法
源码:
def options(url, **kwargs):
r"""Sends an OPTIONS request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('options', url, **kwargs)
options方法的入参为:
- url 请求的url链接,用来创建Request对象的
- **kwargs 不定长参数,Request对象的可选参数
options方法的方法体:
首先是
kwargs.setdefault('allow_redirects', True)
将allow_redirects设置为True,既开启自动重定向处理。
接下来就是调用request方法,将method设为options,并将结果返回
return request('options', url, **kwargs)
head方法
源码:
def head(url, **kwargs):
r"""Sends a HEAD request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes. If
`allow_redirects` is not provided, it will be set to `False` (as
opposed to the default :meth:`request` behavior).
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', False)
return request('head', url, **kwargs)
head方法的入参为:
- url 请求的url链接,用来创建Request对象的
- **kwargs 不定长参数,Request对象的可选参数
head方法的方法体:
首先是
kwargs.setdefault('allow_redirects', False)
将allow_redirects设置为False,既关闭自动重定向处理。
接下来就是调用request方法,将method设为head,并将结果返回
return request('head', url, **kwargs)
post方法
源码:
def post(url, data=None, json=None, **kwargs):
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('post', url, data=data, json=json, **kwargs)
post方法的入参为:
- url 请求的url链接,用来创建Request对象的
- data 可选,Request对象需要发送的body,可以是字典、列表、元组、字节、类文件对象
- json 可选,Request对象需要发送的body,json数据,将会自动进行json编码转换成json对象
- **kwargs 不定长参数,Request对象的可选参数
post方法的方法体:
调用request方法,将method设为post,data和json作为参数传递给request方法,并将结果返回
return request('post', url, data=data, json=json, **kwargs)
put方法
源码:
def put(url, data=None, **kwargs):
r"""Sends a PUT request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('put', url, data=data, **kwargs)
put方法的入参为:
- url 请求的url链接,用来创建Request对象的
- data 可选,Request对象需要发送的body,可以是字典、列表、元组、字节、类文件对象
- **kwargs 不定长参数,Request对象的可选参数
put方法的方法体:
调用request方法,将method设为put,data作为参数传递给request方法,并将结果返回
return request('put', url, data=data, **kwargs)
patch方法
源码:
def patch(url, data=None, **kwargs):
r"""Sends a PATCH request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('patch', url, data=data, **kwargs)
patch方法的入参为:
- url 请求的url链接,用来创建Request对象的
- data 可选,Request对象需要发送的body,可以是字典、列表、元组、字节、类文件对象
- **kwargs 不定长参数,Request对象的可选参数
patch方法的方法体:
调用request方法,将method设为patch,data作为参数传递给request方法,并将结果返回
return request('patch', url, data=data, **kwargs)
delete方法
源码:
def delete(url, **kwargs):
r"""Sends a DELETE request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('delete', url, **kwargs)
delete方法的入参为:
- url 请求的url链接,用来创建Request对象的
- **kwargs 不定长参数,Request对象的可选参数
delete方法的方法体:
调用request方法,将method设为delete,data作为参数传递给request方法,并将结果返回
return request('delete', url, **kwargs)
通过上面源码可以看出,get、options、head、post、put、patch、delete这7个方法都是调用了request方法,无非就是入参不同,并且做了一些初始化的工作(设置allow_redirects)。值得注意的是,除了header,其他几种方法默认都是开启自动处理重定向,你可以通过Response对象的history方法来追踪重定向,history方法返回的是一个 Response 对象的列表。你也可以手动通过allow_redirects参数关闭自动处理重定向。
既然所有的方法都是调用request方法,那么我们就来看看这个方法是怎么发起请求的。
request方法
源码;
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
:param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object 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 ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
to add for the file.
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) How many seconds to wait for the server to send data
before giving up, as a float, or a :ref:`(connect timeout, read
timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
:return: :class:`Response <Response>` object
:rtype: requests.Response
Usage::
>>> import requests
>>> req = requests.request('GET', 'https://httpbin.org/get')
>>> req
<Response [200]>
"""
# By using the 'with' statement we are sure the session is closed, thus we
# avoid leaving sockets open which can trigger a ResourceWarning in some
# cases, and look like a memory leak in others.
with sessions.Session() as session:
return session.request(method=method, url=url, **kwargs)
从注释中可以看出来,这个方法主要是构造并发送一个Request对象的。
request方法入参:
- method 创建Request对象需要的method:
GET
,OPTIONS
,HEAD
,POST
,PUT
,PATCH
,DELETE
。 - url 创建Request对象需要的url。
- params 可选,Request对象需要发送的查询字符串,可以是字典、列表、元组、字节。
- data 可选,Request对象需要发送的body数据,可以是字典、列表、数组、字节或者类文件对象。
- json 可选,Request对象需要发送的body数据,是一个Json序列化对象。
- headers 可选,Request对象需要发送的一个字典对象HTTP请求头。
- cookies 可选,Request对象需要发送的一个字典对象或者CookieJar对象。
- files 可选,用于分段编码上传的字典(
'name': file-like-objects
或者{'name': file-tuple}
格式),file-tuple
可以是('filename', fileobj)
格式,或者('filename', fileobj, 'content_type')
格式,或者('filename', fileobj, 'content_type', custom_headers)
,其中'content-type'
是一个字符串,用来定义文件内容的类型。custom_headers
类似于一个字典,包含了为这个文件额外添加的标题。 - auth 可选, 认证元组,用来启用Basic/Digest/Custom HTTP认证。
- timeout 可选,等待服务器响应的时间,单位是秒,超过设定的时间,则放弃。float型小数或者timeouts型元组。
- allow_redirects 可选,Boolean类型,开启或者关闭
- (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD重定向,默认是
True
- proxies 可选,字典类型,映射协议到代理的URL。
- verify 可选,可以是boolean值,这时候它控制要不要验证服务器的TLS证书。也可以是字符串,这时候它必须是一个CA证书组的路径。默认是
True
。 - stream 可选,如果是
False
,则将立即下载响应内容。 - cert 可选,如果是字符串,则必须是ssl证书(.pem)的路径。如果是元组,则必须是(‘cert’, ‘key’)对。
request方法响应:Response对象
request方法体:
with sessions.Session() as session:
return session.request(method=method, url=url, **kwargs)
源码解读:首先通过with语句创建一个sessions对象session
,然后调用session对象的request方法,将所有的参数全都传进去。就是这么简单!那么,大家可以想想,为什么要用with语句?
至此,整个api.py模块我们已经看完了,Requests对外暴露了8个api,其实都是调用的request方法,而request方法就是创建了一个sessions对象,并调用了该对象的request方法。那么接下来我们再深入一层看一下这个对象方法的源码。
sessions.py
首先我们看一下模块介绍上已经说的很清楚了,这个模块提供了一个session对象,让你能够跨请求的管理和持久化一些参数(cookies, auth, proxies)。
比如说,如果你需要向同一台主机发起多次请求,使用session对象可以让你底层的TCP连接被重用(只需要与服务器建立一次TCP连接),从而显著提升性能。
通过api模块的源码分析我们知道,所有的api方法最终都是调用session对象的request()方法的,那么我们就直接来看一下这个示例方法的源码。
request方法
源码:
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):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object.
:param method: method for the new :class:`Request` object.
: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 data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json 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 :ref:`(connect timeout,
read timeout) <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 or protocol and
hostname to the URL of the proxy.
:param stream: (optional) whether to immediately download the response
content. Defaults to ``False``.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``. When set to
``False``, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to
man-in-the-middle (MitM) attacks. Setting verify to ``False``
may be useful during local development or testing.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
:rtype: requests.Response
"""
# Create the Request.
req = Request(
method=method.upper(),
url=url,
headers=headers,
files=files,
data=data or {},
json=json,
params=params or {},
auth=auth,
cookies=cookies,
hooks=hooks,
)
prep = self.prepare_request(req)
proxies = proxies or {}
settings = self.merge_environment_settings(
prep.url, proxies, stream, verify, cert
)
# Send the request.
send_kwargs = {
'timeout': timeout,
'allow_redirects': allow_redirects,
}
send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs)
return resp
request方法入参:可以看到,session对象的request方法的入参和api模块中request方法的入参完全一样(因为api那边直接调用的这边),所以我就不再说明一遍了。
request方法体:
request方法主要做了一下几件事,第一步是实例化一个Request对象req
req = Request(
method=method.upper(),
url=url,
headers=headers,
files=files,
data=data or {},
json=json,
params=params or {},
auth=auth,
cookies=cookies,
hooks=hooks,
)
接下来是调用prepare_request()方法,将创建的req对象传进去。其实这个方法就是构造了一个PreparedRequest对象,将Request对象和Session对象的设置合并到一起,做请求前的准备工作。
prep = self.prepare_request(req)
我们来看一下内部的代码实现。
prepare_request方法
def prepare_request(self, request):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for
transmission and returns it. The :class:`PreparedRequest` has settings
merged from the :class:`Request <Request>` instance and those of the
:class:`Session`.
:param request: :class:`Request` instance to prepare with this
session's settings.
:rtype: requests.PreparedRequest
"""
cookies = request.cookies or {}
# 将cookies转换为CookieJar对象,先判断是不是CookieJar类型,如果不是,那就是字典类型,那么就调用cookiejar_from_dict()方法将其变为CookieJar类型的对象
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# 将传入的request对象的cookies和session对象的cookies进行合并
merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
# 如果没有明确设置(request对象、session对象中都没有设置auth,且trust_env为true),则设置一下环境的基本身份认证
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
#创建PreparedRequest对象并进行初始化,做请求前的准备
p = PreparedRequest()
#解析所有参数
p.prepare(
method=request.method.upper(),
url=request.url,
files=request.files,
data=request.data,
json=request.json,
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),#将request对象的headers和session对象的headers合并
params=merge_setting(request.params, self.params),#将request对象的参数和session对象的参数合并
auth=merge_setting(auth, self.auth),#将request对象的auth和session对象的auth合并
cookies=merged_cookies,#将request对象的cookies和session对象的cookies进行合并的结果给到cookies
hooks=merge_hooks(request.hooks, self.hooks),
)
#返回处理后的结果
return p
prepare_request方法入参:入参其实只有一个Request对象。
而prepare_request做的事情主要就是将request对象和session对象的一些设置参数进行合并,做好请求前的准备。首先是通过CookieJar对象来管理cookies并将传入的request对象的cookies和session对象的cookies合并,接下来那就是做一些鉴权(何为http auth?其实就是一种基础的用户验证,原理就是将用户名:密码
base64加密后放在http的请求头部Authorization
发给服务器。)的设置,最后就是用这些参数来初始化(解析所有参数)一个PreparedRequest对象并返回。具体每一步代码做了什么,我都已经在上面注释了,可以去看一下。
我们再回到session对象的request方法中继续看,在得到预处理好之后的PreparedRequest对象prep以后,就是设置代理参数,如果传了值(开启了代理),则设置为传进来的实参(代理服务),如果没有则设置为一个空字典。
proxies = proxies or {}
再往下就是做一些环境相关的设置,包括请求的URL,代理设置、是否支持流式下载、证书认证、证书设置。
settings = self.merge_environment_settings(
prep.url, proxies, stream, verify, cert
)
最后就是做发送请求相关设置并发送请求了。
# 设置超市时间和是否开启自动重定向
send_kwargs = {
'timeout': timeout,
'allow_redirects': allow_redirects,
}
send_kwargs.update(settings)
# 发送请求并得到响应对象resp
resp = self.send(prep, **send_kwargs)
# 返回结果
return resp
这里我们可以看到,真正发送请求的就是sessoin对象的send方法,那么我们就来看看这个send方法是怎么实现的。
session.send()方法
源码:
def send(self, request, **kwargs):
"""Send a given PreparedRequest.
:rtype: requests.Response
"""
# 设置hooks可以使用的默认值,以确保能够正确重现上一次请求
kwargs.setdefault('stream', self.stream)
kwargs.setdefault('verify', self.verify)
kwargs.setdefault('cert', self.cert)
kwargs.setdefault('proxies', self.proxies)
# 防止用户发送的是Request对象
if isinstance(request, Request):
raise ValueError('You can only send PreparedRequests.')
# 设置resolve_redirects所需的变量以及派发hooks
allow_redirects = kwargs.pop('allow_redirects', True) #将参数allow_redirects赋值给变量allow_redirects,如果没有传,则设置为True
stream = kwargs.get('stream') #将参数stream赋值给变量stream
#初始化hooks
hooks = request.hooks
# 获取合适的连接适配器
adapter = self.get_adapter(url=request.url)
# 请求开始的时间
start = preferred_clock()
# 发送请求
r = adapter.send(request, **kwargs)
# 请求耗时
elapsed = preferred_clock() - start
# 将请求耗时赋值给Response对象的elapsed属性
r.elapsed = timedelta(seconds=elapsed)
# 在hooks中为响应数据r添加一个hook,类型为'response',目前Requests只有这一种钩子类型
r = dispatch_hook('response', hooks, r, **kwargs)
# 维持 cookies
if r.history:
# 如果 hooks 创建了多个response(重定向),我们需要将每个响应数据的cookie都维持起来
for resp in r.history:
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)#合并cookies,将request和历史response中的cookies都合并到session中
extract_cookies_to_jar(self.cookies, request, r.raw)#合并cookies,将request和重定向后的response中的cookies都合并到session中
# 处理重定向
if allow_redirects:
# 如果开启自动重定向,则利用生成器将Request历史所有Response放入history
gen = self.resolve_redirects(r, request, **kwargs)
history = [resp for resp in gen]
else:
#如果没开启自动重定向,则history为空
history = []
# 如果有history,进行排序(从最老到最新,不包含最终的response)
if history:
# 将第一个请求既原始请求放在第一个元素
history.insert(0, r)
# 通过pop将最后的response去除
r = history.pop()
# 将history变量赋值给r对象的history属性
r.history = history
# 如果未开启自动重定向, 将响应存储在响应对象中,通过的next()方法获取
if not allow_redirects:
try:
r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
except StopIteration:
pass
if not stream:
r.content
return r
以上就是session模块中的send()方法所有的源码了,主要功能我都做了详细的注释。最核心的就是利用hook机制(后续我会单独写一篇文章来介绍hook机制),用来操控部分请求过程,或信号事件处理。将这次请求创建的所有Response放入history。而建立、维护连接以及发送请求都是通过adapter连接适配器来控制的。在下一篇章我们将会继续往下看这个适配器是怎么维护连接并且发送请求的。
小结
至此,我们从最外层的api.py一直到会话层的session模块都已经分析过了。api模块只要是对外提供的api方法,session模块用来管理会话的,其实就是合并单次请求与会话之间的配置,让你能够跨请求的保持某些参数(比如cookies)。看完之后最大的感受就是,封装的恰到好处,以至于对外提供的api是那么的清晰、简洁、易用,一切看起来都那么的自然。
如有任何疑问,欢迎大家留言交流!