目录
API接口和Session
API接口
session
小结
请求流程介绍
Request对象
Request对象小结
prepare_request方法
身份验证
构造PreparedRequest对象
prepare_request方法小结
环境参数合并
请求发送
附源码结构梳理
Requests是python中使用率极高的一个请求库,具体使用方法可以查询官网.
本文旨在从源码入手,梳理逻辑,以更好的理解Requsets库的请求过程.
Requests库发起网络请求有两种方式,最简单的一种是API接口方式,一种是Session方式.
API接口是在方法层面,Session方式是在会话层面.
通过官方的介绍,我们知道方法层面的每次请求都是独立的,所有的请求的参数都是不持续的.
而会话层面的方法是可以将一些请求参数持续化的.比如cookie.
通过API接口方法的调用,就能得到响应内容,接下来用代码来看看API接口的方式
import requests
r = requests.get('https://api.github.com/events')
r = requests.post('https://httpbin.org/post', data = {'key':'value'})
r = requests.put('https://httpbin.org/put', data = {'key':'value'})
r = requests.delete('https://httpbin.org/delete')
r = requests.head('https://httpbin.org/get')
r = requests.options('https://httpbin.org/get')
上面见到的get,post等等方法都是在Reqeusts库的__init__文件提前导入的,很明显这些方法都来自api.py的文件.
from .api import request, get, head, post, patch, put, delete, options
接下来看看这些方法在api.py文件中是怎么定义的呢?(仅为展现需要,代码进行了删减)
from . import sessions
def request(method, url, **kwargs):
pass
def get(url, params=None, **kwargs):
pass
def post(url, data=None, json=None, **kwargs):
pass
......
该api.py文件中一共定义了八个方法
除了第一个reqeust方法,其他的都是HTTP协议中的请求方法
暂时先不管这个reqeust方法,我们就从最常用的get方法入手,源码很简短,如下:
def get(url, params=None, **kwargs):
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
要搞懂这段代码,首先要了解**kwargs形式的参数.简单来说,这是python中一种对参数的封装方式.
可以把 key=value 这样键值对形式的参数封装成字典,举个例子:
def func(**kwargs):
print(kwargs)
func(a=1,b=2)
#打印:{'a': 1, 'b': 2}
了解了kwargs就是一个字典后,就能很容易的看懂第一行代码了.
通过字典的内置setdefault方法,为kwargs这个参数字典设置一个allow_redirects主键,如果该主键不存在则值为True.
实际上allow_redirects是代表允许重定向
最后通过调用之前提到的reqeust方法,并将request方法的结果返回.
至此,可以来研究下reqeust方法的源码了:
from . import sessions
def request(method, url, **kwargs):
with sessions.Session() as session:
return session.request(method=method, url=url, **kwargs)
这个request方法的源代码也极为简洁 ,但是注意该方法调用了session模块的内容.
通过上下文管理的方式调用了session.py文件中的Session类的request方法.
如果熟悉with关键字的作用的话,便理解为什么方法层面的请求中的参数是不可持续的了.
虽然现在还不清楚session.Session.request方法中的逻辑
但起码知道每一次方法层面的请求都是在调用会话层的方法,并在一次请求结束后关闭了session.
在此就仅以get方法为例了,其他的post,put等等方法也都是类似的.
由上述所知,方法层面都是调用的session.py文件中的方法.
那么现在来谈谈会话层了.首先来看看session.py文件中的内容.
# import so much things!!!
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
pass
def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
pass
class SessionRedirectMixin(object):
pass
class Session(SessionRedirectMixin):
pass
def session():
pass
该文件中导入的其他模块或者方法很多,等碰到有用的模块或方法时在介绍,此处就不啰嗦了.
在该文件中定义了3个方法和2个类
既然之前被调用的是Session类,那么就从Session类先看.
在研究之前要声明一点,本文只探讨Request库的请求过程,至于网络传输等等其他方面的不在本文研究范围内.
下面我们先列出Session类中的在本次讨论范围内的部分代码(同样做了删减)
class Session(SessionRedirectMixin):
def prepare_request(self, request):
pass
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):
pass
def get(self, url, **kwargs):
kwargs.setdefault('allow_redirects', True)
return self.request('GET', url, **kwargs)
def send(self, request, **kwargs):
pass
既然刚刚是调用的reqeust方法,那么就直接研究Session类中的这个reqeust方法吧.
咋眼一看,该方法的参数实在不少...... 还是直接上源码吧
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):
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_kwargs = {
'timeout': timeout,
'allow_redirects': allow_redirects,
}
send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs)
return resp
不管怎么样,看到最后的return时,起码知道这个request方法才是最后拿到响应内容的核心方法了.
其实这一堆的参数都和HTTP协议是相关了,也就是在请求体中要发给Sever端的相关的必要的参数和内容.
先大概看看这个reqeust方法干了些什么事吧,把最关键的几个步骤梳理下.
首先实例化了一个Request对象req(request这个词用的比较多,注意区分)
然后调用 self.prepare_request 方法处理这个实例对象req,并生成一个新的prep对象
接下来进行了一些参数合并之类的工作,也就是把方法层面请求的参数与会话层面请求的参数进行合并
最后通过self.send方法将构造好的prep对象发送出去,并得到响应内容对象resp.
通过之前的分析,我们知道了以下几点:
1、api.py文件中定义的方法(如get)是通过request方法封装后调用的session
2、session.py文件中的Session类的request方法才是拿到网络响应的核心方法
3、上下文管理with的方式导致了每次API方法请求的参数的不可持续性
4、在向网络发送请求前,API方法携带的参数与session的参数进行了合并
前面的流程梳理中,我们已经知道Session类的request方法是整个请求调用的核心方法
同时也梳理出了整个流程的步骤
先构造Request对象req
然后使用self.prepare_request 方法处理这个实例对象req,并生成一个新的prep对象
接着合并环境参数
最后发送prep对象和环境参数并得到网络响应
接下来就按照这个步骤对整个流程进行分析
在HTTP协议中,Client端、Sever端的使用Request(请求)和Response(响应)两个对象进行交互.
要进行网络交互,必然就存在着这两个对象
而之前的已经介绍到了session.Session.request方法,该方法中的第一步也是实例化了一个Request对象req.
其实这个Request类位于Requests库中的models.py文件中
在models.py文件中,声明了五个类, 分别是:
RequestEncodingMixin,RequestHooksMixin,Request,PreparedRequest,Response
很明显Request和Response分别与HTTP协议中的请求和响应对象相对应
要继续了解整个请求流程,我们只有从Request类入手,看看源码:
class Request(RequestHooksMixin):
def __init__(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
data = [] if data is None else data
files = [] if files is None else files
headers = {} if headers is None else headers
params = {} if params is None else params
hooks = {} if hooks is None else hooks
self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
self.register_hook(event=k, hook=v)
self.method = method
self.url = url
self.headers = headers
self.files = files
self.data = data
self.json = json
self.params = params
self.auth = auth
self.cookies = cookies
def __repr__(self):
return '' % (self.method)
def prepare(self):
p = PreparedRequest()
p.prepare(
method=self.method,
url=self.url,
headers=self.headers,
files=self.files,
data=self.data,
json=self.json,
params=self.params,
auth=self.auth,
cookies=self.cookies,
hooks=self.hooks,
)
return p
这个Request类对象其实也比较简单,整个源码中该只有一堆的各种属性和一个方法.
之所以说只有一个方法,是因为__repr__方法是与print相对应使用的魔术方法
也就是,在使用print函数打印该对象时,会查找__repr__方法.
先从一大堆的属性说起吧
虽然都是属性,但需要区分类属性和实例属性
简单点说,在类的定义中,有self的都是实例属性,没有的就可以看做类属性
在__init__初始化构造方法中
首先通过三元判断的方法对默认或者传入参数的检查核对,构造出几个关键的类属性.
data = [] if data is None else data
files = [] if files is None else files
headers = {} if headers is None else headers
params = {} if params is None else params
hooks = {} if hooks is None else hooks
很明显,data,files参数是列表,而headers,parama,hooks都是字典.
在构造了类属性后,接下来就是创建10个实例属性.
self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
self.register_hook(event=k, hook=v)
self.method = method
self.url = url
self.headers = headers
self.files = files
self.data = data
self.json = json
self.params = params
self.auth = auth
self.cookies = cookies
这10个实例属性理论上是与传入的参数相对应的.
但注意self.data,self.files,self.headers,slef.params这四个实例属性对应的是之前创建的类属性了
最后还剩下一个hooks有一些特殊的处理.
在官网文档的高阶用法中,也是提到了requests库是支持事件钩子的
事件钩子也就是回调函数,并且是通过hooks参数来进行支持的.
在requests库,回调函数是用来操控response对象的属性的
我们先看完源码中对hooks的定义,再来看回调函数的例子.
为了方便阅读理解,把相关的涉及到其他模块的函数和源代码放到了一起.
self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
self.register_hook(event=k, hook=v)
#hooks.py文件中的内容:
HOOKS = ['response']
def default_hooks():
return {event: [] for event in HOOKS}
#继承的方法:
def register_hook(self, event, hook):
if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
if isinstance(hook, Callable):
self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'):
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
通过源码,我们知道default_hooks函数生成的是 {'response' : [ ] } 这样的数据结构
也就说实例属性self.hooks默认情况就是 {'response' : [ ] } 这样的数据结构
若构造该对象时hooks参数为空,则类属性hooks是空字典.,否则就是原传入的hooks参数(很明显也必须为字典类型)
在model.py文件中,定义了五个类,而Request类是继承于RequestHooksMixin类的
class Request(RequestHooksMixin)
所以self.register_hook方法也就是继承而来的
RequestHooksMixin的源代码也很简单,就是定义了一个注册hook的函数和一个注销hook的函数
class RequestHooksMixin(object):
def register_hook(self, event, hook):
pass #上文已有代码
def deregister_hook(self, event, hook):
try:
self.hooks[event].remove(hook)
return True
except ValueError:
return False
还是说回之前的流程, 实例化属性self.hooks是 {'response' : [ ] } 这样的数据结构
类属性hooks是一个字典(可能为空,可能不为空).
然后通过对类属性hooks的主键和值进行迭代,并将 键值对 作为参数传入继承而来的self.register_hook方法
self.register_hook方法首先判断主键是否存在于实例化属性self.hooks之中,如果不存在则抛出错误.
很明显,类属性hooks这个字典的主键值也只允许是'respones'.
这也很好理解,因为我们回调函数也只有一个respones对象可以处理而已.
self.register_hook方法其次判断value值是否是可调用对象
如果可调用就直接将其添加到实例化属性self.hooks数据 {'response' : [ ] }的值的列表中
如果不可调用,则判断该value是否可被迭代
如果可以被迭代再判断每个迭代的元素是否可以被调用
只将可以被调用的元素添加到实例化属性self.hooks数据 {'response' : [ ] }的值的列表中
自此,实例属性self.hooks创建完成.
PS:deregister_hook函数也就是从实例属性self.hooks数据 {'response' : [ ] }的值的列表中删除指定元素.
Request对象的属性介绍完了,至于还有一个prepare方法就暂时省略
因为该方法与后面的内容有重复,就留到后面一起再介绍.
1、Rquest对象是与HTTP协议中的请求对象相对象的,通过构建属性来与HTTP协议相匹配
2、Rquest对象有类属性和实例属性,逻辑上是有不同含义的
3、Rquest对象对属性进行了数据结构的构建或默认值,但对回调函数hooks有特殊的结构设置
在Request对象构造好之后,就应该调用self.prepare_request方法来处理req对象了
那么看下prepare_request方法的源码:
def prepare_request(self, request):
cookies = request.cookies or {}
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
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),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
cookies=merged_cookies,
hooks=merge_hooks(request.hooks, self.hooks),
)
return p
从源码上看,整个prepare_request方法大概进行了三个方面的处理
一是处理了cookie相关的数据
二是处理了身份验证auth相关的数据
三是构造了一个PreparedRequest对象,并将其作为整个函数的返回值
下面就按照这个流程来进行分析.
本流程涉及到HTTP协议里面cookie的规范问题,同时也涉及到python内置库http中的内容
如果要全部展开的话......将是很恐怖的篇幅,所以此处不再进行详细的说明,仅对流程进行讲解.
特别注意如果在源码中看到cookielib的话,得需要注意下,因为在3版本的python中是不存在这个库的
这个库实际就是http.cookiejar,为了兼容性问题用了cookielib这个别名而已
在compat.py这个专门处理兼容性的文件中可以找到答案:
from http import cookiejar as cookielib
在介绍prepare_request方法之前,做一点基础铺垫
http是根据HTTP协议规范开发的python内置模块,cooliejar是该包中的一个.py文件.
cookiejar.py文件中有定义了很多的类和方法,而在本流程中要使用到其中的CookJar和Cookie两个类
而requests库在继承于CookJar类的基础上进行了扩展,创造了具有字典操作功能的RequestCookieJar类
有了这点铺垫,我们来看一看prepare_request方法,其涉及到cookie处理的代码很简短:
cookies = request.cookies or {}
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
merged_cookies = merge_cookies(merge_cookies(RequestsCookieJar(), self.cookies), cookies)
在这简单的代码中,有涉及到了两个方法,cookiejar_from_dict和merge_cookies,而且这两个方法会被多次使用
所以要理解cookie的处理过程,是必须要理解这个两个方法的.这两个方法都为于cookies.py文件中.
首先来说cookiejar_from_dict,该方法:
def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
if cookiejar is None:
cookiejar = RequestsCookieJar()
if cookie_dict is not None:
names_from_jar = [cookie.name for cookie in cookiejar]
for name in cookie_dict:
if overwrite or (name not in names_from_jar):
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
return cookiejar
首先注意该方法的三个参数,除了第一个位置参数cookie_dict,其余两个都有默认值.
cookiejar_from_dict方法中又调用了creat_cookie方法和一个set.cookie的方法
creat_cookie方法也位于cookies.py中,而set.cookie是RequestCookieJar类的实例方法.
从上面的代码中也可以发现,代码中的变量cookiejar也RequestCookieJar的实例
但这里的这个set.cookie不仅仅是RequestCookieJar的实例方法,也是CookJar类的实例方法
我们可以在RequestCookieJar的实例方法set.cookie源码中找到答案:
def set_cookie(self, cookie, *args, **kwargs):
if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
cookie.value = cookie.value.replace('\\"', '')
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
而之前提到过, RequestCookieJar是继承于cookielib.CookJar的
class RequestsCookieJar(cookielib.CookieJar, MutableMapping)
所以最终调用的 cookielib.CookJar的set.cookie方法.
这里搞清楚逻辑关系即可,也就不展开了,总之这个set.cookie方法处理的是creat_cookie方法的返回值
那么再在看看这个creat_cookie方法的源码:
def create_cookie(name, value, **kwargs):
result = {
'version': 0,
'name': name,
'value': value,
'port': None,
'domain': '',
'path': '/',
'secure': False,
'expires': None,
'discard': True,
'comment': None,
'comment_url': None,
'rest': {'HttpOnly': None},
'rfc2109': False,
}
badargs = set(kwargs) - set(result)
if badargs:
err = 'create_cookie() got unexpected keyword arguments: %s'
raise TypeError(err % list(badargs))
result.update(kwargs)
result['port_specified'] = bool(result['port'])
result['domain_specified'] = bool(result['domain'])
result['domain_initial_dot'] = result['domain'].startswith('.')
result['path_specified'] = bool(result['path'])
return cookielib.Cookie(**result)
可以看到该方法最后返回的是一个上面提到的Cookie类对象.
这里就不进行展开了,简单的说creat_cookie方法就是以一对键值为参数对创造出一个符合cookie协议规范的对象.
再结合set.cookie方法,将相关的cookie参数封装到这个RequestCookieJar的实例对象cookiejar中
介绍完了cookiejar_from_dict方法中的使用的creat_cookie方法和set.cookie方法
再回到cookiejar_from_dict方法来,再贴一次源代码:
def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
if cookiejar is None:
cookiejar = RequestsCookieJar()
if cookie_dict is not None:
names_from_jar = [cookie.name for cookie in cookiejar]
for name in cookie_dict:
if overwrite or (name not in names_from_jar):
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
return cookiejar
该方法的三个参数,除了第一个位置参数cookie_dict,其余两个都有默认值.
因为的默认cookjar参数默认值就是None,所以该方法中首先创造了一个RequestCookieJar类的实例对象cookiejar.
其次判断cookie_dict参数传入的值是否为空,还记得cookie_dict参数传入的值是什么吗?
再看一下prepare_request方法出来cookie的代码:
cookies = request.cookies or {}
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
merged_cookies = merge_cookies(merge_cookies(RequestsCookieJar(), self.cookies), cookies)
没错,就是Request对象的实例rep对象中的cookie属性或者空字典.
若判断 cookie_dict不为空的话,
先以列表的方式提取RequestCookieJar类的实例对象cookiejar的所有属性
再判断cookie_dict中的主键是否与cookiejar的属性名存在重叠
如果存在重叠则通过creat_cookie方法和set.cookie方法
将cookie_dict的键值对按照协议规范封装到RequestCookieJar类的实例对象cookiejar中
也就是代码中的变量cookiejar并返回.
现在来看看另外一个重要的merge_cookies方法:
def merge_cookies(cookiejar, cookies):
if not isinstance(cookiejar, cookielib.CookieJar):
raise ValueError('You can only merge into CookieJar')
if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
elif isinstance(cookies, cookielib.CookieJar):
try:
cookiejar.update(cookies)
except AttributeError:
for cookie_in_jar in cookies:
cookiejar.set_cookie(cookie_in_jar)
return cookiejar
该 merge_cookies 方法接收两个参数
首先对第一个参数进行类型判断,若不是cookielib.CookieJar的实例就抛出错误.
这也就是说第一个参数必须是cookielib.CookieJar的实例.
其次判断第二个参数是否是字典,如果是则调用刚刚讲解的cookiejar_from_dict
返回一个RequestCookieJar类的实例对象cookiejar ,该对象也是cookielib.CookieJar的实例
并且该返回值直接覆盖掉第一个参数传入的cookiejar,并返回该值.
如果第二个参数不是字典,则判断其是否是cookielib.CookieJar的实例
如果是的话则直接将其数据更新到第一个参数传入的cookiejar中并返回.
如果第二个参数既不是字典,也不是cookielib.CookieJar的实例
那么采用迭代和set_cookie方法,将其更新到第一个参数传入的cookiejar并返回.
综上所述,该merge_cookies方法的作用是将第二参数传入的数据合并到第一个参数的cookielib.CookieJar的实例中
在看看在之前代码中 merge_cookies方法的用处:
cookies = request.cookies or {}
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
merged_cookies = merge_cookies(merge_cookies(RequestsCookieJar(), self.cookies), cookies)
这里连续两次调用 merge_cookies方法
第一个调用时将session.Session类的实例属性self.cookies合并和一个RequestsCookieJar实例对象中
那么Session类的实例属性self.cookies是什么数据呢?从源码中可以找到答案(代码进行了删减,只保留要需要的)
class Session(SessionRedirectMixin):
def __init__(self):
self.cookies = cookiejar_from_dict({})
是调用 cookiejar_from_dict方法处理一个空字典,那么返回值必然是一个RequestCookieJar类的实例对象.
那么在回到之前的逻辑中
第一次调用merge_cookies方法就是将两个RequestCookieJar类的实例对象合并为一个.
然后再将这个合并得到的RequestCookieJar类的实例对象与cookie进行合并
cookie是源自Request实例对象req或者空字典调用cookiejar_from_dict方法得到的一个RequestCookieJar类的实例
这样也就是再次将两个RequestCookieJar类的实例对象合并成一个
本质也就是将方法层携带的cookie数据与会话层设置的cookie数据进行合并.
自此,cookie数据处理完成.
1、需要梳理清楚Request自创的RequestCookieJar类和http.cookiejar中CookJar类、Cookie类的关系
2、理解cookiejar_from_dict方法,merge_cookies方法以及http.cookiejar.CookJar.set_cookie方法的逻辑
3、cookie处理的本质是合并方法层的cookie数据与会话层设置的cookie数据
身份验证的代码相对cookie处理来说,不仅更简短,也更简单.
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
这里涉及到两个实例属性在Session类的源码中能找到对应的值(代码进行了删减,只保留要需要的):
class Session(SessionRedirectMixin):
def __init__(self):
self.trust_env = True
self.auth = None
从这两个默认属性可以看出,如果Request实例req对象的auth不为空,也就是说有auth参数的话
该判断既为False,既不行身份认证了.
如果auth参数为None的话,会调用 get_netrc_auth方法默认的user目录中的netrc文件
如果与req对象中访问的url地址能匹配上,最后会返回一个auth认证需要的的tuple.
该方法涉及了python内置的netrc模块,此处也不进行展开了,有兴趣可以自行研究.
贴上源码:
def get_netrc_auth(url, raise_errors=False):
"""Returns the Requests tuple auth for a given url from netrc."""
try:
from netrc import netrc, NetrcParseError
netrc_path = None
for f in NETRC_FILES:
try:
loc = os.path.expanduser('~/{}'.format(f))
except KeyError:
return
if os.path.exists(loc):
netrc_path = loc
break
if netrc_path is None:
return
ri = urlparse(url)
splitstr = b':'
if isinstance(url, str):
splitstr = splitstr.decode('ascii')
host = ri.netloc.split(splitstr)[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
if _netrc:
login_i = (0 if _netrc[0] else 1)
return (_netrc[login_i], _netrc[2])
except (NetrcParseError, IOError):
if raise_errors:
raise
except (ImportError, AttributeError):
pass
在prepare_reqeust方法中关于PreparedReques对象的构造的的代码中
一开始就要实例化一个 PreparedRequest对象,那还是只能从PreparedRequest的源码看起
PreparedRequest的源码位于model.py文件中.下面只贴了部分比较重要的代码:
class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
def __init__(self):
self.method = None
self.url = None
self.headers = None
self._cookies = None
self.body = None
self.hooks = default_hooks()
self._body_position = None
def prepare(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
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)
self.prepare_hooks(hooks)
同样在__init__初始化函数中设置了七个实例属性,其中default_hooks方法在之前已经讲解过,不在重复
最重要的是prepare方法,在prepare_reqeust方法中也是调用这个prepare方法
prepare方法中又调用了七个实例方法(鉴于部分方法代码较长,就不贴了详细代码了):
def prepare_method(self, method):
pass
def prepare_url(self, url, params):
pass
def prepare_headers(self, headers):
pass
def prepare_body(self, data, files, json=None):
pass
def prepare_auth(self, auth, url=''):
pass
def prepare_cookies(self, cookies):
pass
def prepare_hooks(self, hooks):
pass
其实这七个实例方法与七个实例属性相对应,每个实例方法对一个实例属性进行操作.
也就说,默认情况下七个实例属性都是None,除了self.hooks是有特殊结构的.
每个实例方法都执行后,都会给七个实例属性赋于新的值,不在是默认值
下面简单总结下每个方法的作用:
prepare_method:按协议规范将method转换为大写并进行ascii编码
prepare_url:按照协议规范对url进行修正检查,能修正的进行修正,如果不符合协议抛出错误
prepare_headers:按照协议对headers的各个数据进行规范检查,不符合抛出错误,这里引入了大小写不敏感的字典类.
prepare_body:将data,josn参数的数据按协议规范封装入body
prepare_auth:按照协议对auth参数进行封装
prepare_cookies:按照之前分析过的原理检查cookie,并最终将其封装入headers中
prepare_hooks:注册回调函数,原理与之前介绍的一致.
现在看回prepare_reqeust方法中的代码:
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),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
cookies=merged_cookies,
hooks=merge_hooks(request.hooks, self.hooks),
)
PreparedRequest的实例对象和parpred方法都已经了解了
只是调用parpred方法时,一些参数传入前调用了merge_setting方法进行处理
那么就再研究下merge_setting方法:
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
if session_setting is None:
return request_setting
if request_setting is None:
return session_setting
if not (
isinstance(session_setting, Mapping) and
isinstance(request_setting, Mapping)
):
return request_setting
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
none_keys = [k for (k, v) in merged_setting.items() if v is None]
for key in none_keys:
del merged_setting[key]
return merged_setting
此方法中又涉及一个 to_key_val_list方法,位于utils.py文件中:
def to_key_val_list(value):
if value is None:
return None
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
if isinstance(value, Mapping):
value = value.items()
这个 to_key_val_list方法对value进行类型检查,若为None就直接返回None
若为str, bytes, bool, int之中的任意类型也抛出错误
若为Mapping的实例,则返回其他items,也就是字典的items()方法
回到merge_setting方法中
同样最初也就进行空值检查,分别检查方法层和会话层的数据,若一方为空则返回另一方的数据
若两方都不为None且都是Mapping的实例,则先将会话层的数据转换成dict_class参数指定的数据类型
然后将方法层的数据更新到由会话层转换而来的这个数据结构中
最后从这个数据结构中查找并删除value值为空的键值对,将最终的结构返回.
出了merge_setting方法,还用到一个merge_hooks方法:
def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
if session_hooks is None or session_hooks.get('response') == []:
return request_hooks
if request_hooks is None or request_hooks.get('response') == []:
return session_hooks
return merge_setting(request_hooks, session_hooks, dict_class)
同样的,该方法最终也是调用merge_setting方法进行方法层和会话层hooks参数的合并,只是在之前进行了条件判断.
了解完了这些代码,也就明白了相关数据或者参数在PreparedRequest对象构造
以及prepare方法是如何对PreparedRequest对象进行更进一步的属性值操作的
最后将进行了以系列属性操作后的PreparedRequest对象作为self.prepare_request方法的返回结果
1、prepare_request方法是请求过程中最核心的方法
2、prepare_request方法中经过了cookie处理,身份验证,PreparedRequest对象构造三个步骤
3、PreparedRequest对象是requests库中最底层也是最核心的对象
4、prepare_request方法中最核心的思想就是按照HTTP协议进行数据检查和数据合并
还是先贴上环境参数合并的这部代码:
proxies = proxies or {}
settings = self.merge_environment_settings( prep.url, proxies, stream, verify, cert)
send_kwargs = {'timeout': timeout,'allow_redirects': allow_redirects,}
send_kwargs.update(settings)
这段代码中,最重要的就是理解 self.merge_environment_settings方法:
def merge_environment_settings(self, url, proxies, stream, verify, cert):
if self.trust_env:
no_proxy = proxies.get('no_proxy') if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items():
proxies.setdefault(k, v)
if verify is True or verify is None:
verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
os.environ.get('CURL_CA_BUNDLE'))
proxies = merge_setting(proxies, self.proxies)
stream = merge_setting(stream, self.stream)
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
return {'verify': verify, 'proxies': proxies, 'stream': stream,'cert': cert}
总的来说,这个 self.merge_environment_settings方法也并不复杂
首先从本机环境中检查代理和SSL 证书的情况
然后通过很熟悉的merge_setting方法将各种参数进行合并
最后返回一个与HTTP协议中关键名称相对应的包含各个参数的字典对象
再回到下面这段代码:
proxies = proxies or {}
settings = self.merge_environment_settings( prep.url, proxies, stream, verify, cert)
send_kwargs = {'timeout': timeout,'allow_redirects': allow_redirects,}
send_kwargs.update(settings)
代理,SSL证书等参数设置合并完成后,在添加上超时和重定向参数,构成最后的参数字典对象.
请求发送仅仅是调用self.send方法将封装好的prep对象和参数字典发送到指定的服务器
从而等待网络响应且将自动的将响应封装成一个response对象
resp = self.send(prep, **send_kwargs)
这里涉及到的socket等网络传输过程就不展开了.
总结前梳理下reqeust库中各个py文件代码的大概用途:
py文件名 | 作用说明 |
---|---|
__init__ | 必须的python包指示文件,主要进行兼容,版本验证 |
__version__ | 作者版权等相关信息 |
_internal_utils | 提供内部使用的函数方法 |
adapters | 传输适配器相关 |
api | API接口调用方法 |
auth | 身份验证相关的方法 |
certs | certifi包的CA证书 |
compat | 主要处理python2和python3版本之间的兼容性问题 |
cookies | cookie相关处理 |
exceptions | 异常定义 |
help | 帮助,bug报告相关 |
hooks | 事件钩子,回调函数 |
models | Requests中的主要对象,包括请求对象,响应对象等 |
packages | 三方依赖库的向后的兼容能力 |
sessions | session对象 |
status_codes |
HTTP协议状态码 |
structures | requests库中定义的数据结构 |
utils | requests库中使用以及外部扩展所需要的函数方法 |