gRPC开箱即用-Node.js篇

gRPC是什么

gRPC(gRPC Remote Procedure Calls,gRPC 远程过程调用)最初由谷歌(Google)研发,是一款现代化、开源且高性能的 RPC 框架,具备跨环境运行的特性。该框架核心遵循 "服务定义优先" 的设计理念,采用 Protocol Buffers(简称 ProtoBuf)作为接口定义语言(IDL)------ 开发人员通过编写.proto 文件即可清晰定义服务接口与消息数据结构,再借助 gRPC 编译器自动生成多语言的客户端和服务端代码,大幅简化了跨语言远程调用的开发成本与复杂度。

背景

项目基于 gRPC 框架,为用户提供多语言的客户端调用支持。作为团队唯一的前端,Node.js 版本的开发工作自然责无旁贷。看了一圈node源码,哔哩吧哩、、、那么好!为了无脑搬运、自己封装一圈!!!!

使用

请确保您的系统或容器已经安装'openssl'

vbscript 复制代码
主要包含三种模式
    1、Request -> Response
    2、stream Request -> Response
    3、Request -> stream Response

安装

bash 复制代码
npm install @fxs0819/grpc-client

proto

java 复制代码
// Hello.proto

syntax = "proto3";

package hello;

// The greeting service definition.
service MainGreeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  // Sends a stream greeting
  rpc SayMultiHello (stream HelloRequest) returns (HelloReply) {}

  // Receive a stream greeting
  rpc ReceiveMultiHello (HelloRequest) returns (stream HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

服务端

typescript 复制代码
# server.js
const grpc = require('@grpc/grpc-js');
const path = require('path');
const protoLoader = require('@grpc/proto-loader');
import * as async from 'async';
import * as lodash from 'lodash';

const config = {
  host: '0.0.0.0',
  port: 50051,
  packageName: 'hello',
  serveName: 'MainGreeter'
};

let cnt = 1;

function sayHello(call:any, callback:any) {
  console.log('server', call.request);
  callback(null, { message: `[${cnt++}] echo: ` + call.request.name });
}

function sayMultiHello(call:any, callback:any) {
  const names:string[] = [];
  call.on('data', (data:Record<string, any>) => {
    console.log(`get name: ${data.name}`);
    names.push(data.name);
  });
  call.on('end', ()=>{
    const res = { message: 'Hello ' + names.join(',') };
    callback(null, res);
  });
}

function receiveMultiHello(call:any, callback:any) {
  console.log('server', call.request);
  const senders: any[] = [];
  for (var i = 0; i < 10; i++) {
    senders[i] = (function(i) {
      return (callback:any)=>{
        call.write({ message: 'Hello ' + `name${i}` });
        lodash.delay(callback, 1000);
      };
    }(i));
  }
  async.series(senders, () => {
    call.end();
  });
}

function main() {
  const PROTO_PATH = path.join(__dirname, './protos/Hello.proto');
  const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true });
  const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
  const helloProto = protoDescriptor[config.packageName];

  var server = new grpc.Server();
  server.addService(helloProto[config.serveName].service, { sayHello, sayMultiHello, receiveMultiHello });
  server.bindAsync(`${config.host}:${config.port}`, grpc.ServerCredentials.createInsecure(), () => {
    server.start();
    console.log(`grpc server started: ${config.host}:${config.port}`);        
  });
}

main();

客户端

javascript 复制代码
const path = require('path');
import { Client } from '../src/index';
import { PassThrough } from 'stream';
import * as lodash from 'lodash';
import * as async from 'async';

async function main() {
  const config = {
    uri: '0.0.0.0:50051',
    ssl: {
      isOpen: false
      // rootCertPath: path.join(__dirname, './certs/cacert.pem'),
      // clientStorePath: path.join(__dirname, './certs/client.pfx'),
      // pfxCode: 'balabal.quick.50843197',
      // authority: 'quickservice'
    },
    config: {
      PROTO_PATH: path.join(__dirname, './protos/Hello.proto'),
      PACKAGE_NAME: 'hello',
      SERVE_NAME: 'MainGreeter'
    }
  };
  const client:Client = new Client(config);
  const deadline = new Date().getTime() + 1000;
  await client.waitForReady(deadline);
  // await sayHello(client);
  await sayMultiHello(client);
  // await receiveMultiHello(client);
  client.close();

}

async function sayHello(client:Client) {
  const deadline = new Date().getTime() + 1000;
  const res = await client.myProxy('sayHello', { 'name': 'Tom' }, { deadline });
  console.log(res);
}

async function sayMultiHello(client:Client) {
  const stream = new PassThrough({ objectMode: true });
  const sayMultiHelloCall = client.myProxy('sayMultiHello', stream);
  const senders: any[] = [];
  for (var i = 0; i < 10; i++) {
    senders[i] = (function(i) {
      return (callback:any)=>{
        stream.write({ name: `name${i}` });
        lodash.delay(callback, 1000);
      };
    }(i));
  }
  async.series(senders, () => {
    stream.end();
  });
  const sayMultiHello = await sayMultiHelloCall;
  console.log(sayMultiHello);
}

async function receiveMultiHello(client:Client) {
  const call = client.client?.receiveMultiHello({ 'name': 'Tom' });
  call.on('data', function(data:any) {
    console.log(data);
  });

  call.on('end', ()=>{
    console.log('end');
  });
}
main();

最后

希望能帮助到有需要的掘友、请多指教,勿喷呀!

参考

gRPC文档 gRPC官方文档中文版 github

相关推荐
半个落月9 小时前
用 LangChain 连接远程 MCP:从工具发现到多轮调用闭环
人工智能·后端·node.js
星栈独行13 小时前
Node 框架怎么选?Express、Koa、Egg、NestJS 场景化选型指南
后端·程序人生·node.js
徐小超16 小时前
从一句 Hello World 到完整 RAG 系统:一个 AI 知识库的架构选型实录
langchain·node.js·ai编程
雷工笔记1 天前
如何查看电脑有没有安装node.js及如何安装node.js
node.js
星栈1 天前
Node 框架怎么选?Express、Koa、Egg、NestJS 场景化选型指南
后端·node.js
万敏1 天前
Vue3 全栈实战第三周:组件化开发完整记录 —— Props / Emit / provide-inject / 插槽 / 自定义指令
vue.js·node.js·全栈
Kel1 天前
Node.js 单线程高并发:从内核中断到 JS 回调的完整技术栈
设计模式·架构·node.js
触底反弹2 天前
🔥 保姆级教程|SSE + BFF + 跨域三件套,从零实现 ChatGPT 流式输出(附完整代码)
人工智能·node.js·vite
柒和远方3 天前
V043:BFF 层流式输出:从前端直连到生产级流式网关的架构演进
javascript·架构·node.js
濮水大叔4 天前
在 DTO 中使用 Form Layout 配置复杂表单,支持 Tabs/Groups/Sections
vue.js·typescript·node.js