如何在无Node.js的浏览器端React应用中使用gRPC反射?
问题:React.js Web应用实现gRPC Server Reflection
- 需求:React.js Web应用连接支持gRPC Server Reflection的服务器,实现动态发现并调用方法,无需提前导入proto文件
- 现状:调研过
grpc-js-reflection-api-client、gprc-reflection-js等库,但它们依赖@grpc/grpc-js,仅支持Node.js运行时,无法在浏览器环境中使用 - 疑问:能否通过grpc-web实现浏览器端的gRPC反射功能?
参考示例代码(来自grpc-js-reflection-api-client):
const { GrpcReflection } = require('grpc-js-reflection-client'); const grpc = require('@grpc/grpc-js'); function getChannelCredentials() { return grpc.ChannelCredentials.createSsl(); } (async()=> { const client = new GrpcReflection( 'grpcb.in:9001', getChannelCredentials(), ); console.log(await client.listServices()); })();
解决方案
可以通过grpc-web实现浏览器端的gRPC反射,但目前没有现成的开箱即用库,需要基于grpc-web手动封装反射客户端,步骤如下:
1. 准备反射协议的客户端代码
gRPC Server Reflection的协议定义在官方的reflection.proto文件中,需要先将其转换为grpc-web支持的客户端代码:
- 下载
reflection.proto文件 - 使用
protoc搭配grpc-web代码生成插件生成JS客户端:
protoc \ --js_out=import_style=commonjs,binary:. \ --grpc-web_out=import_style=commonjs,mode=grpcwebtext:. \ reflection.proto
2. 在React中封装反射客户端
利用生成的代码,在React应用中实现反射接口的调用,比如列出服务、获取服务对应的proto内容:
import { GrpcReflectionClient } from './reflection_grpc_web_pb'; import { ListServiceRequest, FileContainingServiceRequest } from './reflection_pb'; // 初始化反射客户端(替换为你的gRPC代理地址) const reflectionClient = new GrpcReflectionClient('https://your-grpc-proxy-domain', null, null); // 列出所有可用服务 export async function listAllServices() { const request = new ListServiceRequest(); return new Promise((resolve, reject) => { reflectionClient.listServices(request, {}, (err, res) => { err ? reject(err) : resolve(res.getServiceList().map(svc => svc.getName())); }); }); } // 获取指定服务对应的proto文件内容 export async function getServiceProto(serviceName) { const request = new FileContainingServiceRequest(); request.setService(serviceName); return new Promise((resolve, reject) => { reflectionClient.fileContainingService(request, {}, (err, res) => { err ? reject(err) : resolve(res.getFileList().map(file => file.getContent())); }); }); }
3. 动态调用目标gRPC服务
拿到服务元数据和proto内容后,可借助protobuf.js动态解析proto并构建请求,调用目标服务:
import * as protobuf from 'protobufjs'; import { GenericClient } from 'grpc-web'; export async function callDynamicService(serviceName, methodName, requestData) { // 获取服务对应的proto内容 const protoContents = await getServiceProto(serviceName); // 动态加载并解析proto const root = protobuf.parse(protoContents.join('\n')).root; const service = root.lookupService(serviceName); const method = service.methods[methodName]; // 编码请求数据 const request = method.requestType.create(requestData); const requestBytes = method.requestType.encode(request).finish(); // 用GenericClient发送请求 const genericClient = new GenericClient('https://your-grpc-proxy-domain'); return new Promise((resolve, reject) => { genericClient.unary( `${serviceName}/${methodName}`, requestBytes, 'application/protobuf', method.responseType.decode, {}, (err, res) => err ? reject(err) : resolve(res) ); }); }
注意事项
- 你的gRPC服务器需要通过Envoy等代理支持grpc-web协议转发
- 确保服务器已开启gRPC Server Reflection功能
- 动态解析proto会有一定性能开销,适合调试或动态服务场景,生产环境若服务稳定,建议提前生成客户端代码
内容的提问来源于stack exchange,提问作者mic
相关产品推荐
相关产品推荐

