在NextJS+TypeScript中无需.proto文件发送gRPC请求的实现方法
无需本地.proto文件连接gRPC服务器的实现方案
方案一:利用Gitlab API动态加载.proto内容
你已经能通过Gitlab API获取.proto原始内容,可在运行时动态加载并生成gRPC客户端,无需将文件存到项目目录中。
服务端组件/API路由(使用@grpc/grpc-js)
适用于NextJS的服务端代码(如API路由、Server Components),直接用官方gRPC库:
import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; // 动态创建gRPC客户端 async function createGrpcClient(serverAddr: string, serviceFullName: string) { // 从Gitlab API拉取.proto原始文本 const protoRes = await fetch('你的Gitlab API地址'); const protoContent = await protoRes.text(); // 将proto字符串加载为package定义 const packageDef = protoLoader.loadFromString(protoContent, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); // 加载package并创建客户端 const protoPackage = grpc.loadPackageDefinition(packageDef); // 注意替换为你的服务全限定名(如com.example.YourService) return new protoPackage[serviceFullName](serverAddr, grpc.credentials.createInsecure()); } // 发送请求示例 async function callRpcMethod() { const client = await createGrpcClient('grpc-server:50051', 'com.example.YourService'); return new Promise((resolve, reject) => { // 替换为你的RPC方法名和请求参数 client.getResource({ id: 123 }, (err, resp) => { if (err) reject(err); else resolve(resp); }); }); }
客户端组件(使用grpc-web)
如果需要在浏览器端(Client Components)调用,改用grpc-web配合protobufjs动态解析:
import { grpc } from '@improbable-eng/grpc-web'; import * as protobuf from 'protobufjs'; async function sendClientSideRequest() { // 拉取proto内容 const protoRes = await fetch('你的Gitlab API地址'); const protoContent = await protoRes.text(); // 解析proto字符串为protobuf根对象 const root = await protobuf.load(protobuf.parse(protoContent)); // 替换为你的请求/响应消息全限定名 const RequestMsg = root.lookupType('com.example.GetResourceRequest'); const ResponseMsg = root.lookupType('com.example.GetResourceResponse'); // 构造并验证请求消息 const requestData = RequestMsg.create({ id: 123 }); const verifyErr = RequestMsg.verify(requestData); if (verifyErr) throw new Error(`请求参数错误: ${verifyErr}`); // 编码请求并发送 return new Promise((resolve, reject) => { grpc.invoke( // 格式:服务全限定名/RPC方法名 'com.example.YourService/GetResource', { request: RequestMsg.encode(requestData).finish(), host: 'https://grpc-server-proxy', // 需配置grpc-web代理 onMessage: (msgBytes) => resolve(ResponseMsg.decode(msgBytes)), onEnd: (code, msg) => { if (code !== grpc.Code.OK) reject(new Error(`请求失败: ${msg}`)); } } ); }); }
方案二:利用gRPC反射服务动态获取定义
如果服务器开启了反射功能,可通过反射客户端获取服务的.proto定义,再动态生成客户端:
import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; import { ServerReflectionClient } from '@grpc/grpc-js/build/src/reflection'; import * as protobuf from 'protobufjs'; // 从反射服务获取proto内容 async function fetchProtoFromReflection(serverAddr: string, serviceFullName: string) { const reflectionClient = new ServerReflectionClient( serverAddr, grpc.credentials.createInsecure() ); return new Promise<string>((resolve, reject) => { const stream = reflectionClient.serverReflectionInfo((err) => { if (err) reject(err); }); // 请求指定服务的proto定义 stream.write({ fileByService: { service: serviceFullName } }); let fullProtoContent = ''; stream.on('data', (resp) => { if (resp.fileDescriptorResponse) { resp.fileDescriptorResponse.fileDescriptorProto.forEach((fd) => { const root = protobuf.Root.fromDescriptor(fd); fullProtoContent += root.toString(); }); } }); stream.on('end', () => resolve(fullProtoContent)); stream.on('error', reject); }); } // 基于反射内容创建客户端并调用 async function callRpcViaReflection() { const protoContent = await fetchProtoFromReflection('grpc-server:50051', 'com.example.YourService'); const packageDef = protoLoader.loadFromString(protoContent); const protoPackage = grpc.loadPackageDefinition(packageDef); const client = new protoPackage['com.example.YourService']('grpc-server:50051', grpc.credentials.createInsecure()); return new Promise((resolve, reject) => { client.getResource({ id: 123 }, (err, resp) => { if (err) reject(err); else resolve(resp); }); }); }
关键注意事项
- NextJS环境区分:服务端可直接用
@grpc/grpc-js,客户端必须用grpc-web(浏览器不支持原生gRPC)。 - 安全配置:生产环境不要用
createInsecure(),改用credentials.createSsl()配置证书。 - 性能优化:可缓存拉取到的proto内容,避免每次请求都重复拉取解析。
内容的提问来源于stack exchange,提问作者Sebastian Sole
相关产品推荐
相关产品推荐

