Skip to content

扩展功能

请求和响应扩展提供了一个无类型的空间,可以用于添加额外信息。

扩展功能应当用于那些可能并非所有传输方式都支持的特性,以及不适合直接放入底层 httpcore 包作为 API 使用的简化请求/响应模型中的特性。

请求支持以下几种扩展:

# 请求超时实际上是作为请求的一个扩展实现的,
# 确保它们能在整个调用栈中传递。
client = httpx.Client()
response = client.get(
    "https://www.example.com",
    extensions={"timeout": {"connect": 5.0}}
)
response.request.extensions["timeout"]
{"connect": 5.0}

响应也支持扩展:

client = httpx.Client()
response = client.get("https://www.example.com")
print(response.extensions["http_version"])  # b"HTTP/1.1"

# 其他服务器响应可能是
# b"HTTP/0.9", b"HTTP/1.0" 或 b"HTTP/1.1"

请求扩展

"trace"

trace 扩展允许安装一个回调处理器,用于监控底层 httpcore 传输内部的事件流。

最简单的解释方式是通过示例:

import httpx

def log(event_name, info):
    print(event_name, info)

client = httpx.Client()
response = client.get("https://www.example.com/", extensions={"trace": log})

# connection.connect_tcp.started {'host': 'www.example.com', 'port': 443, 'local_address': None, 'timeout': None}

# connection.connect_tcp.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f94d0>}

# connection.start_tls.started {'ssl_context': <ssl.SSLContext object at 0x1093ee750>, 'server_hostname': b'www.example.com', 'timeout': None}

# connection.start_tls.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f9450>}

# http11.send_request_headers.started {'request': <Request [b'GET']>}

# http11.send_request_headers.complete {'return_value': None}

# http11.send_request_body.started {'request': <Request [b'GET']>}

# http11.send_request_body.complete {'return_value': None}

# http11.receive_response_headers.started {'request': <Request [b'GET']>}

# http11.receive_response_headers.complete {'return_value': (b'HTTP/1.1', 200, b'OK', [(b'Age', b'553715'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Thu, 21 Oct 2021 17:08:42 GMT'), (b'Etag', b'"3147526947+ident"'), (b'Expires', b'Thu, 28 Oct 2021 17:08:42 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1DCD)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])}

# http11.receive_response_body.started {'request': <Request [b'GET']>}

# http11.receive_response_body.complete {'return_value': None}

# http11.response_closed.started {}

# http11.response_closed.complete {'return_value': None}

这里的 event_nameinfo 参数会是以下形式之一:

  • {event_type}.{event_name}.started, <关键字参数字典>
  • {event_type}.{event_name}.complete, {"return_value": <...>}
  • {event_type}.{event_name}.failed, {"exception": <...>}

请注意,在使用异步代码时,传递给 "trace" 的处理函数必须是 async def ... 函数。

当前暴露的以下事件类型...

建立连接

  • "connection.connect_tcp"
  • "connection.connect_unix_socket"
  • "connection.start_tls"

HTTP/1.1 事件

  • "http11.send_request_headers"
  • "http11.send_request_body"
  • "http11.receive_response"
  • "http11.receive_response_body"
  • "http11.response_closed"

HTTP/2 事件

  • "http2.send_connection_init"
  • "http2.send_request_headers"
  • "http2.send_request_body"
  • "http2.receive_response_headers"
  • "http2.receive_response_body"
  • "http2.response_closed"

httpcore 不同版本中追踪事件的具体集合可能会有所变化。如果您需要依赖特定的事件集合,建议将该包的安装固定到特定版本。

"sni_hostname"

服务器的主机名,用于验证 SSL 证书提供的主机名。

如果您想连接到明确的 IP 地址而非使用标准的 DNS 主机名查找,则需要使用此请求扩展。

例如:

# 连接到 '185.199.108.153' 但 Host 头中使用 'www.encode.io',

# 并在 SSL 验证服务器主机名时使用 'www.encode.io'。
client = httpx.Client()
headers = {"Host": "www.encode.io"}
extensions = {"sni_hostname": "www.encode.io"}
response = client.get(
    "https://185.199.108.153/path",
    headers=headers,
    extensions=extensions
)

"timeout"

一个包含 str: Optional[float] 超时值的字典。

可以包含 'connect''read''write''pool' 的值。

例如:

# 如果连接建立耗时超过 5 秒,或

# 等待连接池阻塞超过 10 秒则超时。
client = httpx.Client()
response = client.get(
    "https://www.example.com",
    extensions={"timeout": {"connect": 5.0, "pool": 10.0}}
)

该扩展是 httpx 超时功能的实现方式,确保超时值与请求实例相关联并传递至整个堆栈。通常您不应直接使用此扩展,而应使用更高层的 timeout API。

"target"

用作 HTTP 目标而非 URL 路径 的目标参数。

这使得可以构造原本不受支持的请求:

  • 应用了非标准转义的 URL 路径
  • 使用绝对 URI 的转发代理请求
  • 使用 CONNECT 并以主机名作为目标的隧道代理请求
  • 服务器范围的 OPTIONS * 请求

一些示例:

使用 'target' 扩展发送不遵循标准路径转义规则的请求...

# 通常请求 "https://www.example.com/test^path" 会
# 连接到 "www.example.com" 并发送如下 HTTP/1.1 请求...
#
# GET /test%5Epath HTTP/1.1
#
# 使用 target 扩展我们可以包含字面的 '^'...
#
# GET /test^path HTTP/1.1
#
# 注意请求仍需是有效的 HTTP 请求
# 例如在 target 中包含空格会引发 `LocalProtocolError`
extensions = {"target": b"/test^path"}
response = httpx.get("https://www.example.com", extensions=extensions)

target 扩展还允许构造服务器范围的 OPTIONS * 请求...

# 这会发送如下请求...
#
# CONNECT * HTTP/1.1
extensions = {"target": b"*"}
response = httpx.request("CONNECT", "https://www.example.com", extensions=extensions)

响应扩展

"http_version"

HTTP 版本,以字节形式表示。例如 b"HTTP/1.1"

使用 HTTP/1.1 时,响应行包含显式版本号,此键的值可能是 b"HTTP/0.9"b"HTTP/1.0"b"HTTP/1.1" 之一。

使用 HTTP/2 时,协议中不再包含进一步的响应版本信息,此键的值将始终为 b"HTTP/2"

"reason_phrase"

HTTP 响应的原因短语(reason-phrase),以字节形式返回。例如 b"OK"。某些服务器可能会包含自定义的原因短语,尽管这种做法不被推荐。

从 HTTP/2 开始,协议中不再包含原因短语。

当响应头中未包含该字段时,可能会基于状态码使用默认值。

"stream_id"

当使用 HTTP/2 协议时,可以通过 "stream_id" 响应扩展来获取该响应所对应的数据流 ID。

"network_stream"

"network_stream" 扩展允许开发者通过提供一套脱离标准请求/响应模型的 API 来处理 HTTP CONNECTUpgrade 请求,并可直接读写网络流。

网络流提供的接口包括:

  • read(max_bytes, timeout = None) -> bytes
  • write(buffer, timeout = None)
  • close()
  • start_tls(ssl_context, server_hostname = None, timeout = None) -> NetworkStream
  • get_extra_info(info) -> Any

此 API 可作为处理 HTTP 代理、WebSocket 升级及其他高级用例的基础。

有关直接操作网络流的更多信息,请参阅网络后端文档

额外网络信息

网络流抽象层还允许访问底层套接字可能暴露的各种低级信息:

response = httpx.get("https://www.example.com")
network_stream = response.extensions["network_stream"]

client_addr = network_stream.get_extra_info("client_addr")
server_addr = network_stream.get_extra_info("server_addr")
print("客户端地址", client_addr)
print("服务端地址", server_addr)

套接字的 SSL 信息也可以通过此接口获取,不过需要确保底层连接仍然处于打开状态才能访问...

with httpx.stream("GET", "https://www.example.com") as response:
    network_stream = response.extensions["network_stream"]

    ssl_object = network_stream.get_extra_info("ssl_object")
    print("TLS 版本", ssl_object.version())