新闻详情

新闻详情

首页 / 资讯中心 / 详情

使用 tonic-web 为 tonic 服务直接接入 gRPC-Web 客户端:协议翻译、CORS 配置与实战指南

发布时间:2026/10/2 2:21:10来源:尧图网络
使用 tonic-web 为 tonic 服务直接接入 gRPC-Web 客户端:协议翻译、CORS 配置与实战指南
后端RPC框架【免费下载链接】grpc-rustA native gRPC client server implementation with async/await support.项目地址https://gitcode.com/GitHub_Trending/to/grpc-rust点击查看免费下载导读tonic-web是 grpc-rust 仓库中负责 gRPC-Web 协议翻译的独立 crate它能让基于 tonic 构建的 gRPC 服务直接接收来自浏览器端grpc-web客户端的请求彻底省去 Envoy 等外部代理这一中间环节。读完本文你将掌握如何在 tonic 服务上启用GrpcWebLayer、如何配置 CORS 与 TLS 场景下的部署差异、如何使用仓库自带的客户端示例发起 gRPC-Web 调用以及从源码层面理解请求/响应翻译与 trailer 处理的核心原理。一、为什么需要 gRPC-Web以及 tonic-web 的定位原生 gRPC 协议依赖 HTTP/2 的流式语义和 trailer 头部而浏览器环境下的XMLHttpRequest/fetch无法直接暴露这些能力。传统方案是在服务前面部署 Envoy 之类的代理把 gRPC-Web 请求翻译成原生 gRPC 请求转发给后端。tonic-web改变了这一局面如 tonic-web/README.md 所述它Enables tonic servers to handle requests fromgrpc-webclients directly, without the need of an external proxy即让 tonic 服务器直接处理来自 grpc-web 客户端的请求无需外部代理。从 tonic-web/src/lib.rs 的模块文档可以看到它的实现思路It achieves this by wrapping individual tonic services with a tower service that performs the translation between protocols and handles cors requests.也就是说它通过一个 tower 服务包装单个 tonic 服务在该层完成两个核心任务协议翻译在 gRPCHTTP/2与 gRPC-WebHTTP/1.1 base64两种线格式之间做双向转换CORS 处理响应浏览器的跨域预检preflight请求。tonic-web的版本信息与依赖声明见 tonic-web/Cargo.toml当前版本 0.14.6其运行依赖包括base64、bytes、tokio-stream、http-body、tonic与tower-service等其中 base64 引擎正是协议翻译中 text 编码模式的底层支撑。二、快速开始让 tonic 服务直接接受 gRPC-Web 请求2.1 最小启用示例README 给出的最快上手方式是在现有的 tonic 服务基础上加一个GrpcWebLayer并允许服务器接受 HTTP/1.1 请求#[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { let addr [::1]:50051.parse().unwrap(); let greeter GreeterServer::new(MyGreeter::default()); Server::builder() .accept_http1(true) .layer(GrpcWebLayer::new()) .add_service(greeter) .serve(addr) .await?; Ok(()) }其中两个关键点缺一不可GrpcWebLayer::new()把 gRPC-Web 翻译逻辑作为 tower Layer 挂到服务栈上。查看 tonic-web/src/layer.rs 可知它只是一个零成本构造的tower_layer::Layerlayer()方法内部创建GrpcWebService包装内层服务GrpcWebService同样实现了NamedService因此服务名称如helloworld.Greeter可以无缝透传到路由注册见 tonic-web/src/service.rs。accept_http1(true)gRPC-Web 在浏览器里本质上是跑在 HTTP/1.1 之上的所以必须显式开启 HTTP/1.1 支持让服务器既能处理 HTTP/2 的原生 gRPC 请求也能处理 HTTP/1.1 的 gRPC-Web 请求。2.2 仓库内可运行的完整示例仓库在 examples/src/grpc-web/server.rs 提供了开箱即用的服务端示例。它与 README 的最小示例不同之处在于把服务构建改成使用tower::ServiceBuilder并显式叠加了一个 CORS 层let greeter tower::ServiceBuilder::new() .layer(tower_http::cors::CorsLayer::new()) .layer(tonic_web::GrpcWebLayer::new()) .into_inner() .named_layer(GreeterServer::new(greeter)); Server::builder() // GrpcWeb is over http1 so we must enable it. .accept_http1(true) .add_service(greeter) .serve(addr) .await?;示例中的注释 GrpcWeb is over http1 so we must enable it 再次强调HTTP/1.1 支持是 gRPC-Web 服务化的硬性前提。示例监听127.0.0.1:3000对应的 protobuf 定义可参考 examples/proto/helloworld/helloworld.proto。三、自定义 CORS 配置浏览器端跨域访问 gRPC-Web 服务必然触发 CORS 机制。GrpcWebLayer本身内置了 CORS 处理逻辑用于 grpc-web 与 grpc-web preflight 请求但正如 tonic-web/src/lib.rs 所说You can customize the CORS configuration composing theGrpcWebLayerwith the cors layer of your choice.即你可以将GrpcWebLayer与任意你选择的 CORS layer例如tower-http的CorsLayer组合来自定义允许的来源origin、方法、请求头等策略。仓库示例 examples/src/grpc-web/server.rs 中CorsLayer::new()与GrpcWebLayer::new()的叠层组合就是这一能力的标准用法tonic-web的 dev-dependencies 也声明了对tower-http开启corsfeature的依赖见 tonic-web/Cargo.toml印证了该组合是官方推荐路径。需要注意的是自定义 CORS layer 的适用范围有限如 tonic-web/src/lib.rs 的限制说明所述the cors support implemented by this crate willonlyhandle grpc-web and grpc-web preflight requests它只为 gRPC-Web 场景服务不会越权去处理其他任意 HTTP 跨域请求。何时可以跳过自定义 CORSGrpcWebLayer对请求的分类逻辑决定了 CORS 的触发范围。查看 tonic-web/src/service.rs 的RequestKind定义只有content-type精确命中以下四种之一的请求才会被识别为 gRPC-Web 请求application/grpc-webapplication/grpc-webprotoapplication/grpc-web-textapplication/grpc-web-textproto从源码的匹配分支tonic-web/src/service.rs可以看到命中 gRPC-Web 且方法为POST的请求进入翻译链路命中但方法不是POST如 GET/PUT/DELETE 等会直接返回HTTP 405 Method Not Allowed。这一只认 POST 和 OPTIONS的严格行为也被 tonic-web/src/service.rs 的单元测试only_post_and_options_allowed所固化。四、TLS 服务器场景可以不开 accept_http1对于启用了 TLS 的服务器有一个值得注意的优化点。同样来自 tonic-web/src/lib.rs 的说明Alternatively, if you have a tls enabled server, you could skip settingaccept_http1totrue. This works because the browser will handleALPN.即当服务器配置了 TLS 时可以省略accept_http1(true)因为浏览器会通过 ALPNApplication-Layer Protocol Negotiation在 TLS 握手中自动协商出 HTTP/1.1从而让 gRPC-Web 流量得以承载。对应的 TLS 版示例代码如下#[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { let cert tokio::fs::read(server.pem).await?; let key tokio::fs::read(server.key).await?; let identity Identity::from_pem(cert, key); let addr [::1]:50051.parse().unwrap(); let greeter GreeterServer::new(MyGreeter::default()); // No need to enable HTTP/1 Server::builder() .tls_config(ServerTlsConfig::new().identity(identity))? .layer(GrpcWebLayer::new()) .add_service(greeter) .serve(addr) .await?; Ok(()) }其中Identity与ServerTlsConfig均来自 tonic 的 transport 层。仓库也提供了可直接用于测试的 TLS 证书材料例如 examples/data/tls/ 目录下的server.pem、server.key、ca.pem以及 interop/data/ca.pem 等。五、客户端侧用 GrpcWebClientLayer 发起 gRPC-Web 调用gRPC-Web 不只是服务端单向的翻译tonic-web同样提供了客户端支持。核心组件是GrpcWebClientLayer与GrpcWebClientService导出自 tonic-web/src/lib.rs。5.1 仓库示例通过 hyper 客户端 GrpcWebClientLayerexamples/src/grpc-web/client.rs 展示了完整用法。由于 gRPC-Web 是 HTTP/1.1 协议这里不能使用 tonic 默认的 h2 通道而是直接构造一个 hyper 的 HTTP/1.1 客户端再叠加GrpcWebClientLayer// Must use hyper directly... let client hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build_http(); let svc tower::ServiceBuilder::new() .layer(GrpcWebClientLayer::new()) .service(client); let mut client GreeterClient::with_origin(svc, http://127.0.0.1:3000.try_into()?); let request tonic::Request::new(HelloRequest { name: Tonic.into(), }); let response client.say_hello(request).await?; println!(RESPONSE{response:?});要点解析客户端必须基于 HTTP/1.1 的 hyper 客户端示例注释 Must use hyper directly...GreeterClient::with_origin用于指定目标地址http://127.0.0.1:3000与前面服务端示例的监听地址一一对应生成代码来自tonic::include_proto!(helloworld)对应的预生成文件可参考 examples/generated/helloworld/ 目录。5.2 客户端翻译的底层动作查看 tonic-web/src/client.rs 可以看到GrpcWebClientService在发出请求时做了三件事降级协议版本如果请求是 HTTP/2tonic 客户端的默认版本会强制改写为 HTTP/1.1*req.version_mut() Version::HTTP_11改写 Content-Type把content-type设置为application/grpc-web包装请求体把请求 body 用GrpcWebCall::client_request包装进入编码方向Direction::Encode。响应侧则用GrpcWebCall::client_response反向包装进入Direction::Decode负责把服务端返回的 gRPC-Web 帧还原为 tonic 客户端能消费的数据流见 tonic-web/src/client.rs。这样客户端业务代码完全无感仍以普通 tonic 客户端的方式编写调用。六、深入原理请求/响应的翻译是怎么完成的这一节从源码结构出发剖析tonic-web的翻译管线。它本质上是一个tower 服务栈 HTTP body 适配器的组合。6.1 请求分类GrpcWebService 的路由决策GrpcWebService::calltonic-web/src/service.rs首先对入站请求做分类决策逻辑如下表条件处理方式content-type 命中 4 种 gRPC-Web 类型之一且方法为POST翻译为 gRPC 请求转发给内层服务再把响应翻译回 gRPC-Webcontent-type 命中 gRPC-Web 类型但方法非POST直接返回HTTP 405非 gRPC-Web 请求但版本为 HTTP/2原样透传给内层服务保持原生 gRPC 兼容其他请求如 HTTP/1.1 的非 gRPC-Web 请求返回HTTP 400值得说明的是其他 HTTP/2 请求透传这一设计它保证了原生 gRPC 客户端HTTP/2与 gRPC-Web 客户端HTTP/1.1可以共用同一个服务实例这正是一层挂载、双协议兼容的架构红利。对应行为由 tonic-web/src/service.rs 的mod grpc测试验证HTTP/2 的application/grpc请求返回 OK而 HTTP/1.1 的application/grpc请求返回 400。6.2 请求头改写coerce_request对于命中的 gRPC-Web 请求coerce_requesttonic-web/src/service.rs会把 HTTP/1.1 的 gRPC-Web 请求头改写成符合 gRPC 规范的请求头移除content-lengthgRPC 用帧长度而非 HTTP 头表达消息长度设置content-type: application/grpc让内层 tonic 服务把它当作普通 gRPC 请求插入te: trailers声明客户端可接收 trailer设置accept-encoding: identity,deflate,gzip与 tonic 的压缩协商保持一致。随后请求 body 被GrpcWebCall::request(...)包装进入解码方向把 gRPC-Web 的 base64 载荷还原为 gRPC 二进制帧。6.3 帧格式与编码GrpcWebCallGrpcWebCallBtonic-web/src/call.rs是一个同时实现http_body::Body与Stream的适配器负责在字节流层面做转换。几个关键常量定义了 gRPC-Web 的线格式const GRPC_HEADER_SIZE: usize 1 4; // 帧头1 字节 flag 4 字节消息长度 const FRAME_HEADER_SIZE: usize 5; const GRPC_WEB_TRAILERS_BIT: u8 0b10000000; // 帧首字节 MSB 置位表示 trailer 帧 const BUFFER_SIZE: usize 8 * 1024; // base64 缓冲大小核心要点包括编码选择Encoding枚举只有两种——None二进制对应application/grpc-web(proto)与Base64对应application/grpc-web-text(proto)。Encoding::from_content_type/from_accept通过请求头决定编码方式响应头的 content-type 也据此回写见 tonic-web/src/call.rs。从服务端看text 变体要求接受端同时支持因此响应编码以Accept头为准Trailer 的帧化原生 gRPC 用 HTTP/2 trailer 传递grpc-status、grpc-message等状态元数据而 HTTP/1.1 没有 trailer 概念。tonic-web的做法是把 trailer 编码成 HTTP/1 头块格式key: value\r\n塞进一个特殊的 gRPC 数据帧——首字节置位GRPC_WEB_TRAILERS_BIT后面跟长度和 trailer 内容见make_trailers_frametonic-web/src/call.rs。这一约定是浏览器端 gRPC-Web 客户端的标准协议也是本 crate 处理 trailer 的核心解码侧 trailer 识别服务端解码请求、以及客户端解码响应时通过find_trailerstonic-web/src/call.rs逐帧扫描字节流按1 字节 flag 4 字节长度的帧头步进一旦遇到0b10000000位帧头即认定 trailer 帧开始若剩余字节不足以构成完整帧则判定缓冲区不完整IncompleteBuf等待更多数据到达再继续。decode_trailers_frame则把 trailer 帧内容解析回HeaderMap并兼容key: value冒号后有空格与key:value两种写法——后者在 tonic-web/src/call.rs 的测试中明确说明是为了兼容 connect-rpc 与标准 HTTP 的书写习惯base64 的分块解码decode_chunk按 4 的倍数切分缓冲max_decodable避免把不完整的分组交给 base64 解码器同时处理了以 base64 编码的 trailer这类边界情况。6.4 状态与错误转换过程中出现的任何解析失败都会被归一化为Status::internal(format!(tonic-web: {e}))tonic-web/src/call.rs例如非法帧位Invalid header bit {header} expected 0 or 1、畸形 base64、无法解析的 trailer 键值对等。这使得 gRPC 生态的错误传播模型在翻译层保持一致上层业务无需感知协议差异。七、能力边界与限制阅读 tonic-web/src/lib.rs 的Limitations一节可以明确本 crate 的设计边界避免踩坑只服务 gRPC-Web 兼容客户端tonic-web专为 gRPC-Web 客户端设计It is not expected to handle arbitrary HTTP/x.x requests or bespoke protocols。虽然 HTTP/2 的原生 gRPC 请求会透传但任意 HTTP 请求并不会得到 gRPC 之外的服务语义CORS 仅覆盖 gRPC-Web 场景只处理 gRPC-Web 请求与 gRPC-Web 预检preflight请求流类型限制当前 gRPC-Web 客户端只能发起unary一元和server-streaming服务端流式调用这也是本 crate 唯一设计的处理目标client-streaming 与双向流式要等浏览器端客户端支持后才会得到官方支持不支持 WebSocket 传输gRPC-Web 的 WebSocket transport 变体不在支持范围内。这些限制意味着如果你的服务大量使用双向流式接口且必须直接从浏览器访问那么仍需评估代理方案或等待 gRPC-Web 生态的后续演进。八、验证路径测试与可运行示例仓库为tonic-web提供了多层次的验证材料供读者自行对照实验单元测试集中在 tonic-web/src/service.rs 与 tonic-web/src/call.rs。前者覆盖请求分类4 种 content-type、非 POST 返回 405、原生 gRPC 透传、任意 HTTP/1.1 返回 400、带/不带 origin 的 CORS 预检等后者覆盖 trailer 编解码含多 trailer、值内冒号、冒号后空格、缓冲不完整等多种边界集成测试仓库在 tests/web/tests/grpc.rs 与 tests/web/tests/grpc_web.rs 提供端到端验证对应测试 proto 为 tests/web/proto/test.proto运行示例cd examples cargo run --bin grpc-web-server与cargo run --bin grpc-web-client可分别启动服务端与客户端示例源码见 examples/src/grpc-web/server.rs 与 examples/src/grpc-web/client.rs。结语tonic-web用约两千行的精简实现把浏览器直连 gRPC 服务从需要外部代理的繁琐部署收敛为一个 tower Layer 的挂载操作服务端GrpcWebLayer accept_http1(true)一行开启翻译与 CORS客户端GrpcWebClientLayer一行完成协议适配底层则由GrpcWebCall在帧级别完成 base64 编解码与 trailer 帧化。配合本仓库的完整示例与测试用例你可以快速搭建并验证一条浏览器 → gRPC-Web (HTTP/1.1) → tonic 服务的端到端链路并在此基础上继续扩展 CORS 策略、接入 TLS或深入定制协议行为。赞分享后端RPC框架【免费下载链接】grpc-rustA native gRPC client server implementation with async/await support.项目地址https://gitcode.com/GitHub_Trending/to/grpc-rust点击查看免费下载相关推荐Tonic 0.14 入门实战用 Rust 构建第一个 gRPC HelloWorld 客户端与服务端Tonic 0.14 入门实战用 Rust 构建第一个 gRPC HelloWorld 客户端与服务端 本教程以当前仓库 grpc rust 中的官方 Get后端RPC框架Celery 任务体系基石深入解析 celery.app.task 模块的 Task 基类与 Task.request 请求上下文Celery 任务体系基石深入解析 celery.app.task 模块的 Task 基类与 Task.request 请求上下文 本文以 Celery 源码后端RPC框架如何在Tonic项目中正确配置gRPC客户端HTTPS连接如何在Tonic项目中正确配置gRPC客户端HTTPS连接 在使用Tonic框架开发gRPC应用时许多开发者会遇到HTTPS连接失败的问题特别是当服务部署在后端RPC框架上一篇Velero restore delete 命令完全指南从 CLI 用法到源码级删除流程解析下一篇Zephyr RTOS 上的 ESP32-C3-DevKitC 开发板从构建烧录到 QEMU 仿真与调试的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

使用fastapi-mcp改造fastapi服务为MCP服务供智能体使用案例:把Base URL改到TaoToken 2026/10/2 14:57:59

使用fastapi-mcp改造fastapi服务为MCP服务供智能体使用案例:把Base URL改到TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
ABAQUS轮胎仿真全流程:从过盈充气到滚动传涵实操 2026/10/2 14:57:53

ABAQUS轮胎仿真全流程:从过盈充气到滚动传涵实操

玩轮胎仿真不上手?说真的,这话我听了不下几十遍。但每次看到新人卡住,十有八九都不是软件操作不会,而是没搞懂轮胎仿真这套流程到底在算什么:从过盈充气到滚动传涵,中间每一步都环环相扣。今天我就用自家项…

阅读更多 →
VirtualBox 装 Win11 虚拟机:TPM 2.0、增强功能与避坑 2026/10/2 14:57:53

VirtualBox 装 Win11 虚拟机:TPM 2.0、增强功能与避坑

1. 先搞明白:为什么要在 VirtualBox 里装 Win11我平时干活的主力机是 Linux,但手头总有一些绕不开的 Windows 场景:帮朋友验证一个只在 Win11 上出问题的软件、跑某个银行客户端、测试一份文档在 Edge 下的排版、或者干脆想看看某个新版本系统…

阅读更多 →
Pytorch Unet医学图像分割实战:一键训练脚本与预测全流程解析 2026/10/2 14:57:53

Pytorch Unet医学图像分割实战:一键训练脚本与预测全流程解析

简介:一个基于Pytorch与Unet的医学图像分割实战项目,面向有一定深度学习基础的开发者、医学影像研究者,以及需要快速落地分割任务的技术人员,适用于病灶区域提取、器官结构分割等实际场景。项目完整覆盖数据加载、模型搭建、模型训…

阅读更多 →
从零手搓AI工程:数据管道、实验管理与推理服务实战 2026/10/2 14:57:53

从零手搓AI工程:数据管道、实验管理与推理服务实战

1. 从零手搓AI工程:为什么我不建议你直接调包很多人一听到“AI工程”这四个字,第一反应就是打开某个云平台,拖几个组件,调一下API,跑通一个Demo,然后发个朋友圈说“今天又搞定了一个AI项目”。我刚开始也是…

阅读更多 →
Jetson Nano 无显示器远程桌面:网线直连 NoMachine 实战 2026/10/2 14:57:47

Jetson Nano 无显示器远程桌面:网线直连 NoMachine 实战

把 Jetson Nano 从盒子里翻出来的第一天,我干的事情是给它插上 HDMI 线、USB 键盘、USB 鼠标,再拖一个显示器过去。折腾半小时后我意识到一个问题:这块板子最终是要放在设备柜里跑推理任务的,我不可能每次都把整套外设搬过去。于是…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉