在 requests
库的源代码中,models.py
文件定义了核心的请求和响应模型类,主要包括 Request
、PreparedRequest
和 Response
三个类。这些类共同构成了 requests
库处理 HTTP 请求和响应的基础架构。
文件结构概览
models.py
主要包含以下内容:
-
Mixin 类:
-
RequestHooksMixin
:管理请求钩子(hooks) -
RequestEncodingMixin
:处理 URL 编码和文件上传
-
-
核心类:
-
Request
:用户创建的请求对象 -
PreparedRequest
:准备好发送的请求对象 -
Response
:服务器返回的响应对象
-
类关系图
核心协作流程
(1) Request
→ PreparedRequest
-
Request
(用户层)-
用户直接操作的类,提供友好的参数接口(如
method
,url
,json
等) -
继承
RequestHooksMixin
管理钩子(hooks) -
核心方法:
prepare()
调用后会生成一个PreparedRequest
对象,完成请求的最终组装。
-
-
PreparedRequest
(传输层)-
继承
RequestEncodingMixin
(处理 URL/参数编码、文件上传)和RequestHooksMixin
-
负责将
Request
的抽象参数转换为实际要发送的字节数据:-
编码 URL 和查询参数(
prepare_url()
) -
处理请求体(JSON/表单/文件,
prepare_body()
) -
添加认证头(
prepare_auth()
) -
生成最终的 HTTP 头和 Cookie(
prepare_headers()
,prepare_cookies()
)
-
-
-
关键点
Request
是“配方”,PreparedRequest
是“烹饪好的菜品”,后者才是实际发送到服务器的对象。
(2) PreparedRequest
→ Response
-
发送请求后,服务器返回的响应会被封装为
Response
对象。 -
Response
中会保留对原始PreparedRequest
的引用(通过response.request
属性),形成闭环。
RequestEncodingMixin类
class RequestEncodingMixin:
@property
def path_url(self):
"""Build the path URL to use."""
url = []
p = urlsplit(self.url)
path = p.path
if not path:
path = "/"
url.append(path)
query = p.query
if query:
url.append("?")
url.append(query)
return "".join(url)
@staticmethod
def _encode_params(data):
"""Encode parameters in a piece of data.
Will successfully encode parameters when passed as a dict or a list of
2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
if parameters are supplied as a dict.
"""
if isinstance(data, (str, bytes)):
return data
elif hasattr(data, "read"):
return data
elif hasattr(data, "__iter__"):
result = []
for k, vs in to_key_val_list(data):
if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
vs = [vs]
for v in vs:
if v is not None:
result.append(
(
k.encode("utf-8") if isinstance(k, str) else k,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
return urlencode(result, doseq=True)
else:
return data
@staticmethod
def _encode_files(files, data):
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
tuples. Order is retained if data is a list of tuples but arbitrary
if parameters are supplied as a dict.
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
or 4-tuples (filename, fileobj, contentype, custom_headers).
"""
if not files:
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
new_fields = []
fields = to_key_val_list(data or {})
files = to_key_val_list(files or {})
for field, val in fields:
if isinstance(val, basestring) or not hasattr(val, "__iter__"):
val = [val]
for v in val:
if v is not None:
# Don't call str() on bytestrings: in Py3 it all goes wrong.
if not isinstance(v, bytes):
v = str(v)
new_fields.append(
(
field.decode("utf-8")
if isinstance(field, bytes)
else field,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
for k, v in files:
# support for explicit filename
ft = None
fh = None
if isinstance(v, (tuple, list)):
if len(v) == 2:
fn, fp = v
elif len(v) == 3:
fn, fp, ft = v
else:
fn, fp, ft, fh = v
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
elif hasattr(fp, "read"):
fdata = fp.read()
elif fp is None:
continue
else:
fdata = fp
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
body, content_type = encode_multipart_formdata(new_fields)
return body, content_type
RequestEncodingMixin
是一个混合类,提供了三种主要的请求编码功能,用于处理 HTTP 请求的不同部分:路径 URL、参数和文件上传。下面详细解释每个方法的功能:
1. path_url 属性
@property def path_url(self): """构建要使用的路径 URL"""
功能:
-
从请求的完整 URL 中提取路径部分和查询字符串
-
确保路径至少包含一个斜杠 "/"
-
保留查询字符串部分(如果有)
处理逻辑:
-
使用
urlsplit
解析 URL -
获取路径部分,如果为空则设置为 "/"
-
如果有查询字符串,则附加 "?" 和查询字符串
-
将所有部分拼接成完整的路径 URL
示例:
-
输入
http://example.com/api?q=test
→ 输出/api?q=test
-
输入
http://example.com
→ 输出/
2. _encode_params 静态方法
@staticmethod def _encode_params(data): """编码数据中的参数"""
功能:
-
将参数编码为 URL 查询字符串格式
-
处理多种输入类型:字符串、字节、文件对象、字典或元组列表
-
支持多值参数(同一个键对应多个值)
-
自动处理 Unicode 编码
处理逻辑:
-
如果数据是字符串或字节,直接返回
-
如果数据有
read
方法(文件对象),直接返回 -
如果是可迭代对象(字典或元组列表):
-
将数据转换为键值对列表
-
处理多值情况(将单个值转换为列表)
-
对键和值进行 UTF-8 编码(如果是字符串)
-
使用
urlencode
生成查询字符串
-
示例:
-
输入
{'q': '测试', 'page': 1}
→ 输出q=%E6%B5%8B%E8%AF%95&page=1
-
输入
[('q', 'a'), ('q', 'b')]
→ 输出q=a&q=b
3. _encode_files 静态方法
@staticmethod def _encode_files(files, data): """为 multipart/form-data 请求构建请求体"""
功能:
-
构建用于文件上传的 multipart/form-data 格式请求体
-
处理普通表单字段和文件字段
-
支持多种文件格式:文件名+文件对象、带内容类型的、带自定义头部的
-
自动猜测文件名(如果未提供)
处理逻辑:
-
验证输入(必须有文件,数据不能是字符串)
-
处理普通表单字段:
-
转换为键值对列表
-
处理多值情况
-
处理编码问题
-
-
处理文件字段:
-
解析不同的文件格式(2-4元组)
-
读取文件内容
-
创建
RequestField
对象 -
设置为 multipart 格式
-
-
使用
encode_multipart_formdata
生成最终的请求体和内容类型
示例:
files = {'file': ('test.txt', open('test.txt', 'rb'), 'text/plain')} data = {'name': 'value'} # 生成 multipart/form-data 格式的请求体和对应的 Content-Type
总结
RequestEncodingMixin
提供了完整的 HTTP 请求编码功能:
-
处理 URL 路径和查询字符串
-
处理 URL 编码的参数
-
处理 multipart 文件上传
这些方法通常被 HTTP 客户端库(如 requests)内部使用,用于构建不同类型的 HTTP 请求。
RequestHooksMixin 类
class RequestHooksMixin:
def register_hook(self, event, hook):
"""Properly register a hook."""
if event not in self.hooks:
raise ValueError(f'Unsupported event specified, with event name "{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))
def deregister_hook(self, event, hook):
"""Deregister a previously registered hook.
Returns True if the hook existed, False if not.
"""
try:
self.hooks[event].remove(hook)
return True
except ValueError:
return False
RequestHooksMixin
是一个混合类,提供了钩子(hook)的注册和注销功能,用于在请求过程中插入自定义处理逻辑。下面详细解释每个方法的功能:
1. register_hook 方法
def register_hook(self, event, hook): """正确注册一个钩子"""
功能:
-
为特定事件注册一个或多个钩子函数
-
支持注册单个可调用对象或可迭代的多个可调用对象
-
验证事件名称是否有效
-
验证钩子是否可调用
参数:
-
event
: 要注册钩子的事件名称 -
hook
: 可以是单个可调用对象,或包含多个可调用对象的可迭代对象
处理逻辑:
-
检查事件名称是否存在于
self.hooks
中,不存在则抛出 ValueError -
如果钩子是单个可调用对象(Callable),添加到该事件的钩子列表
-
如果钩子是可迭代对象,遍历并添加其中所有可调用对