尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于 gRPC-Web 的 Hello World 实战:从 .proto 定义、Envoy 代理到浏览器端调用的完整链路指南

基于 gRPC-Web 的 Hello World 实战:从 .proto 定义、Envoy 代理到浏览器端调用的完整链路指南 后端微服务【免费下载链接】grpc-webgRPC for Web Clients项目地址https://gitcode.com/gh_mirrors/gr/grpc-web点击查看免费下载gRPC-Web 让浏览器客户端可以直接以 HTTP/1.1 与 HTTP/2 的方式调用后端 gRPC 服务打破浏览器无法原生使用 gRPC 的壁垒。本指南以grpc-web仓库自带的 Hello World 示例位于 net/grpc/gateway/examples/helloworld为主线从零搭建一套浏览器 → Envoy 代理 → NodeJS gRPC 服务端的最小可运行链路。读完本文你将掌握 gRPC-Web 的完整开发流程编写.proto定义、用protoc生成客户端桩代码、用 webpack 打包浏览器产物、配置 Envoy 的grpc_web过滤器并最终在浏览器控制台看到Hello! World的输出。为什么浏览器需要 gRPC-WebgRPC 的原生协议建立在 HTTP/2 之上而浏览器端 JavaScript 无法直接操控 HTTP/2 帧也无法暴露 gRPC 特有的二进制 wire format。gRPC-Web 项目的解决方案是由浏览器发出基于 HTTP/1.1 的 gRPC-Web 请求Content-Type 为application/grpc-webproto等经过一个代理本项目示例中使用 Envoy将请求转换为标准 gRPC 协议转发给后端服务再把响应转换回浏览器可读的形式。因此一条完整的 gRPC-Web 调用链通常包含三个进程gRPC 后端服务本例为 NodeJS 实现监听:9090代理本例为 Envoy监听:8080接收浏览器请求并转发到:9090静态资源服务器托管打包后的客户端 JS 与 HTML监听:8081。Hello World 示例的全部代码都在当前目录 net/grpc/gateway/examples/helloworld 中可以直接进入该目录开始动手$ cd net/grpc/gateway/examples/helloworld第一步用 Protocol Buffers 定义服务gRPC 服务首先要用 protocol buffersproto3 语法定义接口契约。在helloworld.proto文件中定义一个Greeter服务它包含一个请求消息HelloRequest携带name字段、一个响应消息HelloReply携带message字段以及一个一元 RPC 方法SayHellosyntax proto3; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name 1; } message HelloReply { string message 1; }仓库实际情况当前仓库中的 helloworld.proto 除了SayHello一元调用外还额外定义了一个服务端流式 RPCSayRepeatHello(RepeatHelloRequest) returns (stream HelloReply)对应的RepeatHelloRequest消息增加了count字段。README 为保持入门简单只展示了一元调用实际仓库代码同时演示了两种模式后文会一并讲解。proto3 语法要点字段后面的数字 1、 2是字段在二进制编码中的唯一编号一旦发布不可随意更改package helloworld;决定了生成代码中的命名空间。第二步用 NodeJS 实现 gRPC 服务端接下来实现 gRPC 服务端。示例使用 NodeJS基于grpc/grpc-js与grpc/proto-loader代码放在server.js文件中。核心逻辑是服务端收到客户端请求后通过call.request.name读取消息字段构造响应并通过callback(null, response)返回var PROTO_PATH __dirname /helloworld.proto; var assert require(assert); var grpc require(grpc/grpc-js); var protoLoader require(grpc/proto-loader); var packageDefinition protoLoader.loadSync( PROTO_PATH, {keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); var protoDescriptor grpc.loadPackageDefinition(packageDefinition); var helloworld protoDescriptor.helloworld; function doSayHello(call, callback) { callback(null, { message: Hello! call.request.name }); } function getServer() { var server new grpc.Server(); server.addService(helloworld.Greeter.service, { sayHello: doSayHello, }); return server; } if (require.main module) { var server getServer(); server.bindAsync( 0.0.0.0:9090, grpc.ServerCredentials.createInsecure(), (err, port) { assert.ifError(err); server.start(); }); } exports.getServer getServer;关键点拆解proto 加载protoLoader.loadSync(PROTO_PATH, options)把.proto文件编译为包定义grpc.loadPackageDefinition再将其变成可用的helloworld.Greeter描述符。keepCase: true保留字段原始大小写longs: String、enums: String将 64 位整数与枚举转换为字符串defaults: true为字段填充默认值oneofs: true启用 oneof 支持。服务注册server.addService(helloworld.Greeter.service, { sayHello: doSayHello })将helloworld.proto中定义的sayHello方法映射到具体实现函数。端口绑定server.bindAsync(0.0.0.0:9090, ...)使用明文凭据createInsecure()监听 9090 端口——这是后端 gRPC 服务对 Envoy 暴露的端口。模块导出exports.getServer便于测试或其他入口复用服务实例仓库中 server.js 正是这样组织的。仓库实际情况server.js 中还实现了服务端流式方法doSayRepeatHello根据call.request.count循环call.write(...)逐条下发Hey! name消息每条间隔 500ms最后call.end()结束流。这验证了 gRPC-Web 对服务端流式 RPC 的完整支持。第三步配置 Envoy 代理浏览器无法直接连接后端 gRPC需要 Envoy 做协议转换。把下面内容放入envoy.yamlEnvoy 在:8080监听浏览器请求并通过名为greeter_service的 cluster 转发到:9090的后端static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 8080 } filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: type: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: [*] routes: - match: { prefix: / } route: cluster: greeter_service max_stream_duration: grpc_timeout_header_max: 0s cors: allow_origin_string_match: - prefix: * allow_methods: GET, PUT, DELETE, POST, OPTIONS allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout max_age: 1728000 expose_headers: custom-header-1,grpc-status,grpc-message http_filters: - name: envoy.filters.http.grpc_web typed_config: type: type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb - name: envoy.filters.http.cors typed_config: type: type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors - name: envoy.filters.http.router typed_config: type: type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: greeter_service connect_timeout: 0.25s type: logical_dns # HTTP/2 support typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: type: type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http2_protocol_options: {} lb_policy: round_robin load_assignment: cluster_name: cluster_0 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 0.0.0.0 port_value: 9090配置要点逐项说明HTTP Connection Managerenvoy.filters.network.http_connection_manager网络层过滤器负责把 TCP 连接上的流量解析为 HTTP 请求stat_prefix: ingress_http用于统计数据命名。路由routes中match: { prefix: / }将所有路径的请求路由到greeter_serviceclustermax_stream_duration.grpc_timeout_header_max: 0s允许 gRPC 超时头自行控制流时长避免 Envoy 强制截断长连接。grpc_web 过滤器envoy.filters.http.grpc_web这是整个配置的灵魂它把浏览器发来的 gRPC-Web 请求翻译为原生 gRPC 协议转发给上游并把上游响应再翻译回 gRPC-Web 格式。缺了它浏览器请求无法被后端识别。CORS 过滤器envoy.filters.http.cors浏览器跨域访问的必需配置。allow_origin_string_match: prefix: *放开所有来源allow_headers中必须包含 gRPC-Web 特有的请求头x-grpc-web、grpc-timeout、x-user-agent、x-accept-content-transfer-encoding、x-accept-response-streaming等expose_headers中必须暴露grpc-status与grpc-message否则浏览器拿不到 gRPC 的最终状态码与错误消息。router 过滤器Envoy HTTP 过滤链的收尾负责真正把请求转发给上游 cluster。cluster 定义type: logical_dns做 DNS 解析typed_extension_protocol_options中http2_protocol_options: {}明确启用 HTTP/2 上游连接——gRPC 要求上游必须是 HTTP/2lb_policy: round_robin轮询负载均衡endpoint 指向0.0.0.0:9090的 NodeJS 服务。Docker 平台注意如 issue #436 所述若在 Mac/Windows 上以 Docker 运行 Envoy容器内访问宿主机时不能使用0.0.0.0应把 cluster 中最后一个address: 0.0.0.0改为... socket_address: address: host.docker.internal如果 Mac 上的 Docker 版本早于 v18.03.0则改为... socket_address: address: docker.for.mac.localhost仓库实际情况当前仓库的 envoy.yaml 还额外配置了 Envoy 管理接口admin监听9901端口用于查看代理运行状态cluster 部分也保留了host.docker.internal的替换注释方便跨平台直接使用。第四步编写浏览器客户端代码服务端与代理就绪后编写浏览器端客户端放入client.jsconst {HelloRequest, HelloReply} require(./helloworld_pb.js); const {GreeterClient} require(./helloworld_grpc_web_pb.js); var client new GreeterClient(http://localhost:8080); var request new HelloRequest(); request.setName(World); client.sayHello(request, {}, (err, response) { console.log(response.getMessage()); });这里导入的HelloRequest、HelloReply、GreeterClient类是下一步用protoc代码生成工具从helloworld.proto自动生成的见下文生成桩代码一节。用法非常直观用new GreeterClient(http://localhost:8080)实例化客户端地址指向Envoy 代理不是后端 gRPC 服务用new HelloRequest()构造请求对象request.setName(World)设置字段调用client.sayHello(request, {}, callback)发起 RPC——方法名与.proto中定义的SayHello一一对应回调中response.getMessage()取出服务端返回的问候语。仓库实际情况client.js 将代理地址写为http:// window.location.hostname :8080即跟随页面所在主机动态拼接比 README 中的硬编码localhost更适合跨机器联调同时还演示了服务端流式调用构造RepeatHelloRequest并setCount(5)后通过client.sayRepeatHello(streamRequest, {})返回的流对象监听data与error事件逐条打印服务端推送的Hey! World0 ... Hey! World4。package.jsonserver.js与client.js都需要一个package.json声明依赖{ name: grpc-web-simple-example, version: 0.1.0, description: gRPC-Web simple example, main: server.js, devDependencies: { grpc/grpc-js: ~1.0.5, grpc/proto-loader: ~0.5.4, async: ~1.5.2, google-protobuf: ~3.21.4, grpc-web: ~1.5.0, lodash: ~4.17.0, webpack: ~5.82.1, webpack-cli: ~5.1.1 } }各依赖的职责grpc/grpc-js与grpc/proto-loader用于服务端NodeJS gRPC 实现与 proto 加载google-protobuf是生成代码helloworld_pb.js的运行时依赖grpc-web是浏览器端 gRPC-Web 客户端库webpack/webpack-cli负责把 CommonJS 模块打包成浏览器脚本async、lodash是仓库流式示例的辅助库。仓库实际版本为 package.json 中所列如grpc/grpc-js约~1.1.8。index.html最后需要一个简单的index.html加载打包产物!DOCTYPE html html langen head meta charsetUTF-8 titlegRPC-Web Example/title script src./dist/main.js/script /head body pOpen up the developer console and see the logs for the output./p /body /html./dist/main.js是下一步由 webpack 生成的浏览器产物。第五步用 protoc 生成消息类与客户端服务桩安装插件要从.proto定义生成 protobuf 消息类与客户端服务桩需要三个工具protoc二进制Protocol Buffers 编译器protoc-gen-js二进制生成 protobuf 消息类 JS 代码protoc-gen-grpc-web插件gRPC-Web 官方代码生成插件。如果尚未安装可参考仓库 README 中 Code Generator Plugins 一节的安装指引。生成桩代码三个工具就绪后在helloworld.proto所在目录执行$ protoc -I. helloworld.proto \ --js_outimport_stylecommonjs:. \ --grpc-web_outimport_stylecommonjs,modegrpcwebtext:.命令成功后会生成两个新文件helloworld_pb.js包含HelloRequest、HelloReply消息类由--js_out生成helloworld_grpc_web_pb.js包含GreeterClient客户端类由--grpc-web_out生成。这正是前面client.js中require的两个文件。生成参数说明-I.指定 proto 导入搜索路径为当前目录import_stylecommonjs生成 CommonJS 风格的require()模块便于 webpack 打包modegrpcwebtext指定 gRPC-Web 请求/响应采用Text 格式基于 base64 编码的二进制兼容性最好另一个可选模式是modegrpcweb纯二进制帧格式。默认模式为grpcwebtext入门场景无需改动。第六步用 webpack 编译客户端代码生成桩代码后浏览器仍无法直接加载 CommonJS 模块需要把客户端代码编译打包为浏览器可用的单一脚本$ npm install $ npx webpack ./client.jswebpack 以client.js为入口解析其中所有require()依赖包括grpc-web客户端库与生成的桩文件产出./dist/main.js供index.html中的script src./dist/main.js引用。也可以改用browserify或其他等价打包工具。第七步运行示例代码全部就绪接下来在后台启动三个进程启动 NodeJS gRPC 服务监听:9090$ node server.js 启动 Envoy 代理。envoy.yaml让 Envoy 在:8080接收浏览器请求并转发到:9090配置见上文$ docker run -d -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml:ro \ --networkhost envoyproxy/envoy:v1.22.0Mac/Windows 注意参考 issue #436在 Mac/Windows 上运行 Docker 时应去掉--networkhost改用端口映射$ docker run -d -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml:ro \ -p 8080:8080 -p 9901:9901 envoyproxy/envoy:v1.22.0启动静态文件服务器托管index.html与dist/main.js$ python3 -m http.server 8081 三个进程就绪后在浏览器打开localhost:8081打开开发者控制台DevTools Console即可看到打印结果Hello! World至此一条完整的 gRPC-Web 调用链跑通浏览器中的GreeterClient把HelloRequest序列化后发往 Envoy 的:8080grpc_web过滤器将其转为原生 gRPC 请求经 HTTP/2 转发到 NodeJS 服务的:9090doSayHello返回Hello! World再沿原路翻译回 gRPC-Web 格式最终在回调中打印出来。源码级补充gRPC-Web 浏览器端客户端库是如何工作的从浏览器角度看GreeterClient的所有行为都来自grpc-webnpm 包仓库中对应 javascript/net/grpc/web 目录下的源码。几个与本文直接相关的核心模块grpcwebclientbase.js客户端基类负责把生成的客户端方法绑定到实际的 HTTP 请求上处理请求序列化、超时、错误码到 statuscode.js 状态码的映射grpcwebclientreadablestream.js服务端流式调用的流对象实现——对应上文client.sayRepeatHello(...)返回的streamdata/error/end事件正是在这里发出grpcwebstreamparser.js解析代理返回的 gRPC-Web 帧流把多帧数据还原为完整的 protobuf 消息服务端流式场景尤其依赖它。这些源码连同 grpcwebclientbase_test.js、grpcwebstreamparser_test.js 测试文件共同保证了浏览器端客户端与 Envoy 代理之间的协议兼容性。调试技巧与扩展方向绕开代理直接调试后端若想在不经过 Envoy 的情况下单独调试 NodeJS gRPC 服务端可以使用仓库自带的 debugging/node-client.js。它直接用原生 gRPC 客户端new helloworld.Greeter(localhost:9090, grpc.credentials.createInsecure())连接后端不经过 gRPC-Web 协议适合用来确认服务端逻辑本身是否正确、排查问题是否出在代理层。更多示例与测试仓库中还有功能更完整的 echo 示例同时提供了 CommonJS 与 TypeScriptts-example两种客户端写法可用于对比不同import_style的生成效果仓库 javascript/net/grpc/web 目录下的单元测试覆盖了客户端基类、流解析器与状态码映射是理解 gRPC-Web 协议细节的最佳阅读材料。注意事项小结端口职责不可混淆浏览器只能访问 Envoy 的:8080与静态服务器的:8081永远不要直连后端的:9090CORS 头必须完整x-grpc-web、grpc-timeout等请求头与grpc-status、grpc-message响应头缺一不可否则浏览器会拦截响应上游必须 HTTP/2cluster 的http2_protocol_options不能省略否则原生 gRPC 握手会失败版本一致性grpc-web客户端库、protoc-gen-grpc-web插件与 Envoy 的 grpc_web 过滤器需保持协议兼容升级任一环节前建议参考仓库 CHANGELOG.md 与 README.md 的版本说明。按照本文七个步骤操作你就拥有了一个可复现、可扩展的 gRPC-Web 最小闭环从.proto契约出发实现 NodeJS 后端、配置 Envoy 代理、生成客户端桩、打包并在浏览器中验证调用后续只需把Greeter替换成自己的业务服务即可投入真实项目。赞分享后端微服务【免费下载链接】grpc-webgRPC for Web Clients项目地址https://gitcode.com/gh_mirrors/gr/grpc-web点击查看免费下载相关推荐从Python小白到term2048开发者游戏主循环实现详解从Python小白到term2048开发者游戏主循环实现详解 想要在终端中体验经典的2048游戏吗 term2048 是一个用Python编写的终端版204gRPC Web浏览器端gRPC通信的完整指南gRPC Web浏览器端gRPC通信的完整指南 gRPC Web是Google开源的JavaScript实现专门为浏览器客户端提供gRPC通信能力解决了传后端微服务grpc-gateway 官方示例全解析从 proto 定义到反向代理的完整实战路径grpc gateway 官方示例全解析从 proto 定义到反向代理的完整实战路径 导读 docs/docs/mapping/examples.md 是 g后端API网关开发工具gRPC上一篇Razzle 集成 Hyperapp用 hyperapp/render 打造零配置的服务端渲染SSR通用应用下一篇如何免费为PDF添加真实扫描质感3分钟快速上手终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表