Envoy Mobile Python 库实战:用 asyncio 与 httpx 集成 Envoy 高性能网络引擎
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
Envoy Mobile Python 库 为 Python 应用提供了 Envoy 的原生绑定(bindings),让你不必亲自启动一个独立的 Envoy 进程,就能在进程内复用 Envoy 的高性能网络能力——包括 HTTP/2、HTTP/3(QUIC)、连接池、DNS 缓存与丰富的指标。本文围绕该库的官方 README 展开,完整讲解AsyncClient高层异步 API 与直接操作Engine/Stream的底层用法,并深入其源码(mobile/library/python/envoy_mobile)与集成测试(mobile/test/python),说明请求头规范化、显式流控、httpx transport 适配等实现细节,帮助你快速在自己的 Python 项目中落地 Envoy Mobile。
功能特性总览
按照 README 的描述,该库提供四大核心能力:
- 高层异步 API(High-level Async API):基于 Python 标准库
asyncio实现,AsyncClient提供熟悉的非阻塞 HTTP 请求接口,与httpx的调用风格接近; - Envoy 引擎集成(Envoy Engine Integration):可直接访问 Envoy 引擎进行高级配置与性能调优(连接超时、DNS 刷新、QUIC 提示、gzip/brotli 解压、socket tagging 等);
- 现代协议支持(Modern Protocols):原生支持 HTTP/2 与 HTTP/3(QUIC);
- 可观测性(Observable):集成 Envoy 丰富的指标(stats)与日志(logging)。
从包结构上看,envoy_mobile/init.py 对外导出AsyncClient、Engine、EngineBuilder、LogLevel、Stream、StreamPrototype、EnvoyError、ErrorCode、StreamIntel、FinalStreamIntel以及三组 httpx transport(AsyncEnvoyClientTransport、EnvoyClientTransport、EnvoyTransportFactory),分层清晰:
envoy_engine:pybind11 生成的 C++ 绑定模块(.so);async_client/:纯 Python 的高层AsyncClient及其Response、Executor、请求规范化工具;*_client_transport.py、transport_factory.py:面向 httpx 生态的 transport 适配层。
使用 AsyncClient 发起异步请求
AsyncClient是官方推荐的入口。它“每个客户端实例独占一个 Envoy 引擎和 executor”,request()在流完成后返回Response对象,整个操作借助底层asyncio事件循环与AsyncioExecutor完全非阻塞(见 client.py 的类文档)。
最小可用示例
README 中的示例完整可运行,其核心流程是:构造EngineBuilder→ 配置日志级别 → 将 builder 交给AsyncClient作为上下文管理器 → 发起请求 → 读取响应:
import asyncio from envoy_mobile import AsyncClient, EngineBuilder, LogLevel async def main(): # Configure the engine builder = EngineBuilder().set_log_level(LogLevel.info) # Use AsyncClient as a context manager async with AsyncClient(builder) as client: # Make a request response = await client.request( method="GET", url="https://api.github.com/repos/envoyproxy/envoy-mobile" ) print(f"Status: {response.status_code}") body = await response.body() print(f"Body length: {len(body)}") if __name__ == "__main__": asyncio.run(main())生命周期与引擎就绪等待
AsyncClient作为异步上下文管理器使用时,__aenter__内部做了三件事(见 client.py):
- 创建一个
asyncio.Event作为引擎就绪信号,并用AsyncioExecutor(loop=asyncio.get_running_loop())绑定当前事件循环; - 调用
builder.set_on_engine_running(...)注册回调后build()引擎——这是为了让回调能安全地跨线程调度回 asyncio 循环(AsyncioExecutor.wrap内部使用loop.call_soon_threadsafe,见 executor.py); await self._engine_running.wait()阻塞直到引擎真正跑起来(DNS 等子系统就绪)。
退出时__aexit__调用engine.terminate()释放资源,__del__也做了兜底清理,避免引擎泄漏。
请求参数与响应读取
request(method, url, **kwargs)是全部 HTTP 动词方法(get/post/put/delete/head/options/patch/trace)的统一实现,支持json、data、headers、timeout四个扩展参数(client.py):
json:序列化为 JSON 字节并自动设置content-type: application/json;与data同时传入会抛出ValueError;data:字符串按 UTF-8 编码;dict/list 会被urlencode为表单并设置application/x-www-form-urlencoded;同时自动计算content-length;headers:dict 形式,值为字符串或字符串列表;timeout:int/float/timedelta 均可,最终换算为毫秒并写入x-envoy-upstream-rq-timeout-ms头(见 utils.py)。
Response对象(response.py)暴露了丰富的读取能力:
response.status_code/response.headers/response.trailers:从:status伪头解析状态码,头信息由回调逐条填充;await response.body:读取完整响应体并缓存,重复调用直接返回缓存;await response.text:以 UTF-8 解码响应体(当前未根据 charset 头动态解码,属 TODO 项);await response.json():将响应体解析为 JSON;response.content.read(n):StreamReader流式读取接口,可每次读取 n 字节,避免大响应整体缓冲(默认_READ_SIZE = 1024,见 response.py);response.ok:状态码小于 400 即为真;raise_for_status()在非 2xx/3xx 时抛ClientResponseError;async with response或response.close()可取消未完成的底层流。
Response内部通过attach()注册on_headers/on_data/on_trailers/on_complete/on_error/on_cancel六个回调,且开启explicit_flow_control=True(显式流控),即每次通过stream.read_data(n)主动向 Envoy 请求数据块(response.py)。
并发请求
同一个AsyncClient可并发发起多个请求。集成测试 async_client_fetch_test.py 展示了用asyncio.gather同时发送 GET/POST 并统一等待响应的写法,测试还覆盖了自定义请求头、流式逐字节读取、JSON 请求、json/data冲突报错、404 时raise_for_status等场景,可作为最佳实践参考。
直接使用 Engine 与 Stream 底层 API
对于需要精细控制的高级场景,README 提供了绕过AsyncClient、直接与Engine和Stream交互的方式。该方式不依赖 asyncio,适合在纯回调/线程模型中使用:
from envoy_mobile import EngineBuilder, LogLevel def on_data(data, end_stream): print(f"Received data: {data}") builder = EngineBuilder().set_log_level(LogLevel.debug) engine = builder.build() # Create a stream and send a request stream = engine.get_stream_client().new_stream_prototype() \ .on_data(on_data) \ .start() stream.send_headers({"method": "GET", "scheme": "https", "authority": "google.com", "path": "/"}, True)注意:当前仓库 API 与 README 示例的差异
需要指出的是,当前仓库版本的绑定 API 与 README 示例存在细微差异:在 module_definition.cc 中,StreamClient通过engine.stream_client(listener_name)获取(listener_name默认为空字符串),流回调通过StreamPrototype.start(on_headers=..., on_data=..., on_complete=..., ...)关键字参数一次性注册,Stream提供send_headers(headers, end_stream, idempotent=False)、send_data(bytes)、close(bytes|dict)、cancel()、read_data(n)方法。集成测试 fetch_test.py 展示了当前仓库中直接流式编程的标准写法:
stream = ( engine.stream_client() .new_stream_prototype() .start( on_headers=on_headers, on_data=on_data, on_complete=on_complete, on_error=on_error, on_cancel=on_cancel, ) ) headers = { ":method": "GET", ":scheme": "http", ":authority": self._echo_server_url, ":path": "/", } stream.send_headers(headers, end_stream=True)因此在实际编码时,应以本仓库 module_definition.cc 中 pybind11 暴露的签名为准。请求头必须使用 HTTP/2 伪头形式(:method、:scheme、:authority、:path),end_stream=True表示请求侧立即结束。
回调与流内省数据
流回调携带两类内省对象(module_definition.cc):
StreamIntel:流级信息,含stream_id、connection_id、attempt_count(重试次数)、consumed_bytes_from_response(已消费的响应字节数);FinalStreamIntel:流结束时的最终统计,含 DNS 解析、TCP 连接、TLS 握手、发送/接收各阶段的毫秒时间戳(未发生阶段为 -1)、socket_reused、sent_byte_count、received_byte_count、upstream_protocol等。
on_error收到的EnvoyError带有error_code与message,错误码枚举ErrorCode包括UndefinedError、StreamReset、ConnectionFailure、BufferLimitExceeded、RequestTimeout(module_definition.cc)。测试 fetch_test.py 验证了取消流的语义:取消后stream_start_ms/stream_end_ms有值,而sending_end_ms、response_start_ms、upstream_protocol为 -1。
EngineBuilder 高级配置
EngineBuilder是引擎配置的入口,链式调用、逐项返回自身。pybind11 绑定(module_definition.cc)暴露了完整的配置面,核心项分类如下:
| 类别 | 方法 | 说明 |
|---|---|---|
| 日志 | set_log_level(level)、enable_logger(bool) | 日志级别枚举LogLevel:trace/debug/info/warn/error/critical/off |
| 生命周期 | set_on_engine_running(closure)、set_on_engine_exit(closure) | 引擎就绪/退出回调 |
| 网络超时 | add_connect_timeout_seconds(n)、set_stream_idle_timeout_seconds(n)、set_per_try_idle_timeout_seconds(n) | 连接超时、流空闲超时、单次尝试空闲超时 |
| DNS | add_dns_refresh_seconds(n)、add_dns_failure_refresh_seconds(base, max)、add_dns_query_timeout_seconds(n)、add_dns_min_refresh_seconds(n)、enable_dns_cache(on, save_interval_seconds=1) | DNS 刷新/失败回退/查询超时/最小刷新间隔/持久缓存 |
| 协议 | enable_http3(bool)、add_quic_hint(host, port)、add_quic_canonical_suffix(suffix)、add_h2_connection_keepalive_idle_interval_milliseconds(ms)、add_h2_connection_keepalive_timeout_seconds(n) | HTTP/3 开关、QUIC 预连接提示、H2 keepalive |
| 连接池 | add_max_connections_per_host(n) | 单主机最大连接数 |
| 传输优化 | enable_gzip_decompression(bool)、enable_brotli_decompression(bool)、enable_interface_binding(bool)、enable_socket_tagging(bool)、enable_worker_thread(bool) | 解压、网卡绑定、socket tag、worker 线程 |
| 安全 | enforce_trust_chain_verification(bool)、enable_platform_certificates_validation(bool)、set_upstream_tls_sni(sni) | 信任链校验、平台证书校验、上游 SNI |
| 标识 | set_app_version(v)、set_app_id(id)、set_device_os(os)、set_node_id(id) | 应用/设备/节点标识,用于统计上报 |
| 运行时 | add_runtime_guard(guard, value)、enable_stats_collection(bool)、set_network_thread_priority(n) | 运行时开关、统计采集、线程优先级 |
| 收尾 | build() | 构建并启动引擎(GIL 释放) |
集成测试中常见的组合是开启 worker 线程、统计与 socket tagging,并通过set_on_engine_running+threading.Event等待引擎就绪(见 httpx_transport_fetch_test.py):
engine_running = threading.Event() engine = ( EngineBuilder() .set_log_level(LogLevel.info) .enable_stats_collection(True) .enable_socket_tagging(True) .set_on_engine_running(lambda: engine_running.set()) .enable_worker_thread(True) .build() ) engine_running.wait(timeout=30)与 httpx 生态集成:Transport 与共享引擎
除自带的AsyncClient外,该库还提供一套完整的 httpx transport 适配,让httpx.Client/httpx.AsyncClient直接跑在 Envoy 引擎之上:
- EnvoyClientTransport:同步 transport,用
threading.Event和queue.Queue桥接 Envoy 回调与 httpx 同步迭代; - AsyncEnvoyClientTransport:异步 transport,用
asyncio.Future、asyncio.Queue桥接回调; - EnvoyTransportFactory:单例工厂,保证进程内只创建一个 Envoy 引擎(引擎初始化代价高,应存活于整个进程生命周期),通过
get_shared_engine()、get_async_transport()、get_sync_transport()对外提供。
from envoy_mobile import EnvoyTransportFactory import httpx transport = EnvoyTransportFactory.get_async_transport() async with httpx.AsyncClient(transport=transport) as client: response = await client.get("https://example.com")请求头映射与限制
transport 层通过 get_envoy_headers 将httpx.Request映射为 Envoy 伪头格式,并有几点工程化处理:
- 跳过连接相关头:
connection、keep-alive、proxy-connection、transfer-encoding、upgrade这些 HTTP/1.1 专有头在 HTTP/2 中是被禁止的,直接丢弃,交由 Envoy 自己设置正确的上游连接头; - 避免覆盖伪头与 host:
:开头的头和host头会被跳过,由 Envoy Mobile 自动设置权威主机; - socket tag:每个 transport 实例通过
itertools.count生成唯一的 32 位 tag,并写入x-envoy-mobile-socket-tag头,用于连接池隔离;transport 关闭时调用engine.drain_connections_by_socket_tag(tag)精准排空该 transport 的连接而不影响其他 transport(测试见 httpx_transport_fetch_test.py)。
错误映射
Envoy 错误码会被映射为 httpx 异常(httpx_utils.py):ConnectionFailure(2)→httpx.ConnectError,RequestTimeout(4)→httpx.ReadTimeout,StreamReset(1)→httpx.RemoteProtocolError,其余归入httpx.RequestError。
请求体流式发送
同步/异步 transport 都采用“先发头(end_stream=False)→ 逐块send_data→stream.close(b"")标记结束”的三段式发送(async_client_transport.py),支持生成器作为请求体逐块发送、大文件上传不整体驻留内存;响应侧则以 64KB 为单位read_data(65536)拉取数据(async_client_transport.py)。
构建与打包(Bazel)
该库使用 Bazel 构建,mobile/library/python/BUILD 给出了完整定义:
envoy_engine:pybind11 扩展(pybind_extension),入口 module_definition.cc,依赖 C++ 层的//library/cc:engine_builder_lib、//library/cc:envoy_engine_cc_lib_no_stamp等;产物为envoy_engine.so(Windows 为 .pyd),并通过 genrule 拷贝到envoy_mobile/包目录内以便随 wheel 分发;envoy_mobile_lib:纯 Python 包(glob全部envoy_mobile/**/*.py),依赖httpx(来自@mobile_pip3的 pip 依赖),并打包py.typed标记(提供类型提示);envoy_mobile_wheel:py_wheel目标,发行名为envoy-mobile-client,版本 0.5.0,可通过--//library/python:python_platform与--//library/python:python_version两个 flag 选择平台(manylinux2014_x86_64、macosx_10_15_x86_64、macosx_11_0_arm64)与 Python 版本(3.12/3.13/3.14)。
README 注释中的示例构建命令为:
bazel build -c opt --strip=always \ //library/python:envoy_mobile_wheel \ --//library/python:python_platform="manylinux2014_x86_64"小结
Envoy Mobile Python 库把 Envoy 的进程内网络引擎能力带给了 Python:日常开发优先使用基于 asyncio 的AsyncClient及其便捷的Response读取接口;需要精细化控制时直接操作Engine/Stream底层 API,借助StreamIntel/FinalStreamIntel获取完整的连接与传输时序;若已在 httpx 生态中,可选用同步/异步 transport 或共享引擎工厂,获得请求体流式发送、连接池隔离与精准排空等高级能力。实践时请注意以本仓库 module_definition.cc 中 pybind11 暴露的实际签名为准,并参考 mobile/test/python 下的集成测试验证各 API 的真实行为。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考