深入 http.client:CPython 标准库 HTTP/HTTPS 协议客户端完全指南
深入 http.clientCPython 标准库 HTTP/HTTPS 协议客户端完全指南【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpythonhttp.client是 CPython 标准库中实现 HTTP/1.1 与 HTTPS 客户端侧的底层模块其源码位于 Lib/http/client.py。它通常不作为终端用户首选的直接入口——urllib.request正是通过它完成 HTTP/HTTPS URL 的处理——但当需要精细控制请求头、分块编码、代理隧道或底层连接时它提供了比高层接口更透明的能力。读完本文你将掌握HTTPConnection/HTTPSConnection的完整参数语义、request与分步发送两种请求方式、HTTPResponse的读取与属性体系、异常树、CONNECT 隧道用法以及这些行为在 CPython 源码中的真实实现依据。适用范围说明本仓库是 CPython 主线开发分支参见 Include/patchlevel.h 中版本号 3.16因此文中涉及的max_response_headers3.15 新增、blocksize3.7 新增等均以当前仓库实现为准。HTTPS 功能仅在 Python 以 SSL 支持编译经由ssl模块时可用且http.client依赖 socket在文档注明的受限 WASM 平台上不可用。模块定位客户端、协议层与库之间的边界模块 docstringLib/http/client.py 顶部清楚地描述了它的角色实现 HTTP/1.1 客户端库。模块文档明确说明本模块定义了实现 HTTP 与 HTTPS 协议客户端侧的类通常不直接使用——urllib.request模块使用它来处理 HTTP 与 HTTPS 的 URL。 这意味着三层结构urllib.request高层URL 语义、重定向、Cookie 等http.client本文主角单个 HTTP 事务、报文读写socket/ssl传输层连接建立与加密。若你只需要拿 URL 发请求拿响应官方文档建议优先考虑更高层的接口若你需要针对特定服务器手工拼报文、用CONNECT打隧道、控制长连接复用细节http.client就是那座足够低、又足够完整的桥。三类核心对象与一个函数模块提供以下对象对象职责HTTPConnection与一台 HTTP 服务器进行一次事务的底层连接对象HTTPSConnectionHTTPConnection的子类使用 SSL/TLS 与安全服务器通信默认端口 443HTTPResponse连接成功建立并发出请求后返回的响应对象包装状态行、响应头与实体正文parse_headers(fp)从文件指针解析 RFC 5322 风格的头部行返回HTTPMessage不含载荷其中HTTPResponse不直接由用户实例化其构造函数签名是HTTPResponse(sock, debuglevel0, methodNone, urlNone)而是由getresponse()在后台创建。HTTPConnection构造参数逐一解析HTTPConnection(host, portNone[, timeout], source_addressNone, blocksize8192, max_response_headersNone)HTTPConnection实例代表与 HTTP 服务器的一次事务实例化时传入主机名与可选的端口号host / port若省略 port会先从 host 字符串中解析。若 host 形如host:port则取其中端口否则使用默认 HTTP 端口 80。底层解析逻辑在_get_hostport()Lib/http/client.pyIPv6 字面量以[...]包裹构造时自动剥去括号端口非数字或为空时会抛出InvalidURL。timeout阻塞操作如连接尝试的超时秒数。不传则使用全局默认超时设置。源码中默认值为socket._GLOBAL_DEFAULT_TIMEOUT见构造函数签名 Lib/http/client.py。source_address3.2 起(host, port)元组作为连接所使用的源地址。blocksize3.7 起发送文件类消息体时的缓冲字节数默认 8192。发送文件对象时send()会以data.read(self.blocksize)循环读取并sendallLib/http/client.py。max_response_headers3.15 起允许的最大响应头数量用于抵御拒绝服务攻击默认值由模块常量_MAXHEADERS 100决定Lib/http/client.py。该上限同样作用于 chunked 响应的 trailer 区见下文。以下四个调用都连接到同一台主机同一端口import http.client h1 http.client.HTTPConnection(www.python.org) h2 http.client.HTTPConnection(www.python.org:80) h3 http.client.HTTPConnection(www.python.org, 80) h4 http.client.HTTPConnection(www.python.org, 80, timeout10)历史版本说明3.4 起strict参数被移除不再支持 HTTP/0.9 风格的 Simple Responses。HTTPSConnection安全层细节HTTPSConnection(host, portNone, *[, timeout], source_addressNone, contextNone, blocksize8192, max_response_headersNone)默认端口为 443context必须是一个ssl.SSLContext实例描述各种 SSL 选项若context为None源码中通过_create_https_context()Lib/http/client.py构建默认上下文内部调用ssl._create_default_https_context()即执行证书与主机名校验同时set_alpn_protocols([http/1.1])发送 ALPN 扩展表明协议为http/1.1并在可用时开启 TLS 1.3 的post_handshake_auth。自定义context时若需要 ALPN应自行以set_alpn_protocols设置。版本行为沿革源码与文档一致3.2加入source_address、context、check_hostname在ssl.HAS_SNI为真时支持 HTTPS 虚拟主机3.4.3默认执行全部必要的证书与主机名校验需要恢复旧式不校验行为时可传入ssl._create_unverified_context()3.10未给context时自动发送 ALPN 指示http/1.13.12废弃的key_file、cert_file、check_hostname参数被移除。HTTPSConnection.connect()在父类完成 TCP 连接后调用context.wrap_socket(sock, server_hostname...)完成 TLS 握手——有隧道时server_hostname取隧道目标主机否则取构造时传入的 hostLib/http/client.py。从最简会话到完整示例GET / HEAD / POST / PUT文档的 Examples 一节给出了四种典型会话这里完整复现并注释。GET一次读与分块读import http.client conn http.client.HTTPSConnection(www.python.org) conn.request(GET, /) r1 conn.getresponse() print(r1.status, r1.reason) # 200 OK data1 r1.read() # 读取整个响应体 # 下面演示按块读取每块 200 字节 conn.request(GET, /) r1 conn.getresponse() while chunk : r1.read(200): print(repr(chunk)) # b!doctype html\n!--[if ... # 无效请求示例返回 404 conn http.client.HTTPSConnection(docs.python.org) conn.request(GET, /parrot.spam) r2 conn.getresponse() print(r2.status, r2.reason) # 404 Not Found data2 r2.read() conn.close()带显式Host头的 GET文档示例指向https://docs.python.org/3/import http.client host docs.python.org conn http.client.HTTPSConnection(host) conn.request(GET, /3/, headers{Host: host}) response conn.getresponse() print(response.status, response.reason) # 200 OKHEAD永不返回正文import http.client conn http.client.HTTPSConnection(www.python.org) conn.request(HEAD, /) res conn.getresponse() print(res.status, res.reason) # 200 OK data res.read() print(len(data)) # 0 print(data b) # TrueHEAD的处理在响应端是特判的HTTPResponse.read()/readinto()在方法为HEAD时直接关闭连接并返回空Lib/http/client.pybegin()也会把length置 0Lib/http/client.py。POST表单编码的经典写法import http.client, urllib.parse params urllib.parse.urlencode({number: 12524, type: issue, action: show}) headers {Content-type: application/x-www-form-urlencoded, Accept: text/plain} conn http.client.HTTPConnection(bugs.python.org) conn.request(POST, , params, headers) response conn.getresponse() print(response.status, response.reason) # 302 Found data response.read() conn.close()PUT与 POST 几乎相同import http.client BODY ***filecontents*** conn http.client.HTTPConnection(localhost, 8080) conn.request(PUT, /file, BODY) response conn.getresponse() print(response.status, response.reason) # 200 OK文档特别说明自定义 HTTP 方法同样可以在urllib.request.Request中通过设置合适的method属性来处理。request 一站式方法body、headers 与自动定长的规则HTTPConnection.request(method, url, bodyNone, headers{}, *, encode_chunkedFalse)会发送一次完整请求等价于内部依次调用putrequest、写头部、endheaders并发送 body。其实现路径是_send_request()Lib/http/client.py。关键规则源码中的实际判定逻辑url 必须是绝对路径以符合 RFC 2616 §5.1.2连接 HTTP 代理服务器、或使用OPTIONS/CONNECT方法时除外源码_validate_path会拒绝含控制字符含\r\n的路径以防请求走私与头部注入CVE-2019-9740 防护见 Lib/http/client.py。body 的类型语义str按 HTTP 默认的 ISO-8859-1Latin-1编码发送——源码_encode()给出明确报错提示若数据不在 Latin-1 范围内建议改用body.encode(utf-8)传入 bytesbytes-like 对象原样发送打开的 file object需至少支持read()若是io.TextIOBase文本流read()结果按 ISO-8859-1 编码否则原样发送。文本文件在send()中按blocksize分块读并逐个sendall可迭代对象逐个元素发送直至耗尽。Content-Length / Transfer-Encoding 自动推导对应_get_content_length()Lib/http/client.py以及_send_request的分支headers中若已含 Content-Length 或 Transfer-Encoding调用方自行负责encode_chunked仅在显式给出 Transfer-Encoding 时有意义body 为None且方法为PUT/POST/PATCH时自动设置Content-Length: 0源码中_METHODS_EXPECTING_BODY {PATCH, POST, PUT}否则部分服务器会回 411body 为字符串或非文件类的bytes-like 对象时自动设置其长度的 Content-Length其余类型文件与一般可迭代对象自动走 chunked 编码设置Transfer-Encoding: chunked而不再尝试确定其长度3.6 起。encode_chunked 参数仅当headers中指定了 Transfer-Encoding 时相关。为False时HTTPConnection假定所有编码由调用方处理为True时 body 会被分块编码。源码中_send_output()Lib/http/client.py是真正的发送器先把缓冲的各行以\r\n拼接并补一个空行结束头部随后按encode_chunked and _http_vsn 11将每个数据块包装成f{len(chunk):X}\r\n chunk b\r\n结束时发送终止块b0\r\n\r\n。两个补充注意点空块会被丢弃因 chunked 规范迭代器产生的空块会被编码器忽略避免畸形编码提前终止服务器端读取分块编码是 HTTP/1.1 的特性除非确认服务器支持 1.1调用方要么显式给出 Content-Length要么以非文件类的str/bytes-like 对象作为 body。分步构造请求putrequest / putheader / endheaders / send除request()外四个底层方法允许按字节级节奏逐行发送对调试或特殊代理场景很有用。它们还体现了连接状态机见下节。putrequest(method, url, skip_hostFalse, skip_accept_encodingFalse)连接建立后应第一个调用。它发送由方法、URL 与HTTP/1.1版本组成的状态行源码putrequest以%s %s %s % (method, url, self._http_vsn_str)组装并_encode_request按 ASCII 编码。若不想自动发送Host:或Accept-Encoding:头例如希望接受更多内容编码可分别给skip_host、skip_accept_encoding传入真值。在 HTTP/1.1 下Lib/http/client.pyputrequest会自动补发Host:头除非skip_host。若 URL 自带http(s)://网络位置则以 URL 为准否则用连接主机非默认端口会带上:port有隧道时以隧道目标为准。非 ASCII 主机名经 IDNA 编码IPv6 地址加[]Accept-Encoding: identity除非skip_accept_encoding。conn http.client.HTTPSConnection(www.python.org) conn.putrequest(HEAD, /index.html)putheader(header, argument[, ...])发送 RFC 822 风格的头部行首参数构成Header: value行后续每个参数作为以制表符缩进的续行输出源码以b\r\n\t.join(values)拼接。发送前会校验头名与头值合法性_is_legal_header_name/_is_illegal_header_value正则见 Lib/http/client.py非法时抛ValueError。此调用要求连接处于Request-started状态否则抛CannotSendHeader。endheaders(message_bodyNone, *, encode_chunkedFalse)发送空行以表示头部结束可选的message_body携带请求体。状态由_CS_REQ_STARTED切换为_CS_REQ_SENT后触发_send_output。若encode_chunkedTruebody 按 RFC 7230 §3.3.1 逐块编码编码方式依对象类型而定实现 buffer 接口的对象 → 编码为单个 chunkcollections.abc.Iterable→ 每次迭代产生一个 chunkfile object → 每次.read()结果一个 chunk方法会自动在 body 结束后发送 chunked 终止标记。send(data)把数据直接发给服务器仅在endheaders之后、getresponse之前使用。若连接未建立且auto_open为真会自动connect()否则抛NotConnected。数据可以是 str、bytes、数组、带.read()的文件对象或可迭代对象。注意send()与connect()一样会触发审计事件http.client.sendconnect触发http.client.connect。连接状态机何时能发下一个请求HTTPConnection内部通过模块 docstring 描述的状态机约束请求节奏Lib/http/client.py。三个内部状态逻辑状态__state__responseIdle_CS_IDLENoneRequest-started_CS_REQ_STARTEDNoneRequest-sent_CS_REQ_SENTNoneUnread-response_CS_IDLEresponse_classReq-started-unread-response_CS_REQ_STARTEDresponse_classReq-sent-unread-response_CS_REQ_SENTresponse_class规则要点在响应头被读完之前不能开始第二个请求在请求发送完成之前不能获取响应对象该约束由HTTPConnection施加HTTPResponse本身并不强制状态机因此高级客户端可以自行加速请求/响应流水线——但必须谨慎例如在读到响应头之前无法判断服务器是否会关闭连接盲目 pipelining 可能踩空。同时文档与源码都强调复用连接的前提是响应已被读完getresponse()抛出非ConnectionError异常后必须读完整响应或调用close()才能对同一连接发送新请求。此外3.5 起若getresponse()抛出ConnectionError及其子类典型如RemoteDisconnected连接对象会自动处于可重连状态下一个请求会自动新建连接但底层 socket 抛出的OSError不在此列此时调用方须自行close()旧连接。读取响应HTTPResponse 的方法与属性HTTPResponse包装服务器的响应提供请求头与实体正文访问它可迭代也可用于with语句。3.5 起实现了io.BufferedIOBase接口全部读操作均可用。属性一览属性含义status服务器返回的状态码如 200reason服务器返回的原因短语如 OKversion服务器使用的 HTTP 协议版本10 表示 HTTP/1.011 表示 HTTP/1.1源码中 0.9 也按 1.0 处理msg含响应头的HTTPMessage实例email.message.Message子类headers以email.message.EmailMessage形式呈现的响应头urllib侧接口url检索到的资源 URL常用于判断是否跟随了重定向debuglevel调试钩子大于 0 时读取/解析响应过程中向 stdout 打印消息closed流是否已关闭msg与headers在实现中是同一对象的两个名字见 Lib/http/client.py分别服务于 http 老客户端与 urllib 的接口预期。方法一览方法说明read([amt])读取并返回响应体传amt则最多读那么多字节readinto(b)把最多len(b)字节读入缓冲区b返回读到的字节数3.3 起read1(n)/peek(n)/readline()由io.BufferedIOBase语义派生的细粒度读取getheader(name, defaultNone)返回名为name的头值无匹配时返回default多个同名头以, 连接default为可迭代对象时同样以逗号连接其元素getheaders()返回(header, value)元组列表fileno()返回底层 socket 的文件描述符3.9 起以下旧接口被废弃请分别改用属性geturl()→urlinfo()→headersgetcode()→status内容长度、chunked 与连接生命周期响应解析在begin()Lib/http/client.py中完成逻辑包括跳过最多_MAXINTERIMRESPONSES100个 100 系列中间响应如100 Continue避免恶意服务器无限流式发送中间响应导致getresponse()挂死若Transfer-Encoding: chunked则进入 chunked 模式逐块读取十六进制块长、跳过;后的 chunk 扩展、以0块结束并丢弃 trailertrailer 行数同样受max_response_headers上限约束见_read_and_discard_trailer()依据Connection/Keep-Alive/Proxy-Connection头与版本判断will_closeHTTP/1.1 默认长连接、显式close除外HTTP/1.0 仅在带 Keep-Alive 相关头时保持有Content-Length时严格按长度读取防止多读阻塞读满后自动关闭底层文件长度不足而 EOF 时在无界读场景下抛IncompleteRead无长度、非 chunked、又不关闭连接的按规范假定连接会关闭。由于HTTPResponse的fp是sock.makefile(rb)客户端切记不要越过 Content-Length 盲读否则可能阻塞到服务器超时。常量与状态码HTTP_PORT / HTTPS_PORT / responses模块常量HTTP_PORTHTTP 协议默认端口恒为 80HTTPS_PORTHTTPS 协议默认端口恒为 443responsesHTTP 1.1 状态码到 W3C 名称的字典例如http.client.responses[http.client.NOT_FOUND]为Not Found。状态码常量本身并不逐个在http.client源码中罗列而是通过两个向后兼容小技巧注入globals().update(http.HTTPStatus.__members__) # 使 http.client.OK 200 responses {v: v.phrase for v in http.HTTPStatus.__members__.values()}即所有标准状态码都来自 Lib/http/init.py 中的HTTPStatus枚举值同时携带.phrase与.description例如HTTPStatus.OK值 200、短语 OKhttp.client直接暴露这些成员名。HTTP/1.1 状态码的完整列表与分类is_informational/is_success/is_client_error等属性见 Doc/library/http.rst 中http-status-codes一节。parse_headers 与 HTTPMessage只解析头部模块级函数parse_headers(fp)从表示 HTTP 请求/响应的文件指针fp中解析头部返回一个HTTPMessage实例持头字段、不含载荷与HTTPResponse.msg、http.server.BaseHTTPRequestHandler.headers一致。使用前提fp必须是io.BufferedIOBase读端二进制提供合法的 RFC 5322 头部parse_headers不解析起始行只解析Name: value行——调用前首行应已被消费返回后fp已就绪可读 HTTP 正文。实现Lib/http/client.py分两步_read_headers按_MAXLINE限制行长、按max_headers限制行数逐行读超过任一上限分别抛LineTooLong/HTTPException_parse_header_lines把字节解码为 ISO-8859-1 后交给email.parser.Parser解析所以头部才具备 email 消息的全部查询能力例如get_all、折叠处理等。HTTPMessage继承自email.message.Message。set_tunnel 与 HTTP CONNECT 代理隧道HTTPConnection.set_tunnel(host, portNone, headersNone)3.2 起把连接改造为通过代理服务器转发的 HTTP CONNECT 隧道典型应用是穿透 HTTPS 代理访问目标构造连接时传代理地址set_tunnel传真正想访问的端点即 CONNECT 请求目标而非代理地址headers是随 CONNECT 请求发送的额外头映射HTTP/1.1 下Host:头为必需RFC 7231 §4.3.6若调用方未提供源码会自动生成Host: 目标主机:端口IDNA 编码见 Lib/http/client.py并随请求发送3.12 起 CONNECT 由 HTTP/1.0 升级为 HTTP/1.1必须在连接建立前调用否则抛RuntimeError。隧道握手由_tunnel()Lib/http/client.py完成把全部请求行与头部拼接成单个send()促使操作系统采用更优包大小随后读取代理响应非 200 状态会关闭连接并抛OSError。import http.client # 穿过本地 8080 端口的 HTTPS 代理去访问 python.org conn http.client.HTTPSConnection(localhost, 8080) conn.set_tunnel(www.python.org) conn.request(HEAD, /index.html)get_proxy_response_headers()3.12 起返回代理对 CONNECT 请求响应的头字典若尚未发送过 CONNECT返回None。异常体系从基类到具体失败异常层级如下源码见 Lib/http/client.py异常父类抛出场景HTTPExceptionException模块异常基类别名error为向后兼容保留NotConnectedHTTPException未连接却尝试发送等操作InvalidURLHTTPException端口非数字或为空URL/主机含控制字符UnknownProtocolHTTPException服务器返回未知 HTTP 版本UnknownTransferEncodingHTTPException未知的传输编码UnimplementedFileModeHTTPException未实现文件模式IncompleteReadHTTPException内容长度未满足即遇 EOF携带partial与可选expected字段ImproperConnectionStateHTTPException连接状态非法基类CannotSendRequestImproperConnectionState状态不允许再发请求如在Request-sent后再次putrequestCannotSendHeaderImproperConnectionState状态不允许发送头未先putrequest等ResponseNotReadyImproperConnectionState响应尚未就绪就取用未发请求或前响应未处理完BadStatusLineHTTPException服务器返回无法理解的状态行LineTooLongHTTPException收到的协议行超过_MAXLINE65536 字节RemoteDisconnectedConnectionResetError、BadStatusLinegetresponse()读到零字节表明远端已关闭连接3.5 起此前抛BadStatusLine()其中三个状态不当异常的共同基类ImproperConnectionState支持用一个 except 捕获全部顺序错误。特别地LineTooLong与超头数HTTPException同时是响应解析期间的 DoS 防线行太长会迅速终止而非无限缓冲。安全机制与审计钩子结合源码http.client内置了多处主动安全设计值得调用方知晓行数与头数上限单行读取上限_MAXLINE 65536头/ trailer 数量上限默认_MAXHEADERS 100均可通过构造参数max_response_headers调整以平衡兼容与防护中间响应上限_MAXINTERIMRESPONSES 100防止getresponse被 100 Continue 流拖死输入字符校验URL、主机、方法名中的控制字符会在发送前被拒绝对应 CVE-2019-9740 / CVE-2019-18348 的修复见_validate_path/_validate_host/_validate_method阻止通过\r\n注入额外报文、污染代理缓存或篡改请求审计事件http.client.connect携带 self、host、port与http.client.send携带 self、data会在相应操作时触发sys.audit供审计框架观察出站连接与载荷HTTPS 默认校验默认 SSL 上下文按系统 CA 校验证书并检查主机名避免裸奔。测试佐证与源码地图模块行为有系统化测试支撑见 Lib/test/test_httplib.py其中与本主题直接相关的包括test_max_response_headers、test_chunked系列test_chunked_trailers、test_chunked_too_many_trailers、test_chunked_missing_end等、test_readinto_head、test_set_tunnel_host_port_headers_*、test_tunnel_connect_single_send_connection_setup等可作为理解与验证上述行为的活教材。关注点仓库位置模块主实现类、状态机、解析、异常Lib/http/client.pyHTTPStatus/HTTPMethod枚举与状态码表Lib/http/init.py系统化测试Lib/test/test_httplib.py模块参考文档Doc/library/http.client.rst状态码常量文档Doc/library/http.rst何时选它与上层库的边界如果你的程序要处理的只是URL → 响应的常规请求http.client不是唯一选项urllib.request在它之上提供 URL 语义官方模块文档也建议需要更高级 HTTP 客户端接口连接池、会话、自动重试等的场景可以考察第三方的高层 HTTP 库。而当你要精确控制单个连接的每个报文细节——手工构造分块体、复用 TCP 长连接、穿 HTTPS 代理打 CONNECT 隧道、或者深入阅读服务器原始头部——http.client以薄封装直通 socket/ssl 的设计使它成为 CPython 生态中值得吃透的一块底层基石。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考