Get Started
本页通过四步走完一个完整的请求/响应闭环:项目初始化、loader、服务端、客户端。对 gRPC 与 protobuf 有基本了解会更顺手,但不是前置——下面的代码段都可以直接跑。
项目初始化
创建项目
新建一个 demo 目录并进入:
mkdir demo && cd demo初始化项目并安装 gRPCity(需要 Node.js >= 18):
npm init -y
npm i grpcity在 package.json 里加上 "type": "module",下面的代码段以 ESM 形式运行。
项目目录
最终目录结构如下,其中高亮的几行是接下来要新建的文件:
.
├── client.js
├── loader.js
├── package-lock.json
├── package.json
├── proto
│ └── helloworld
│ ├── model
│ │ └── message.proto
│ └── service.proto
└── server.js加载 proto
定义 proto
两个 service——Greeter 与 Hellor——共用 model 包里的 message。
service.proto:
syntax = "proto3";
package helloworld;
import "helloworld/model/message.proto";
service Greeter {
rpc SayGreet(HelloRequest) returns (HelloReply) {}
}
service Hellor {
rpc SayHello(HelloRequest) returns (HelloReply) {}
}
model/message.proto:
syntax = "proto3";
package helloworld.model;
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
int32 count = 2;
}编写 loader
一份 loader 实例由 server 与 client 共用,proto 文件只解析一次。
./loader.js:
import { ProtoLoader } from 'grpcity'
import path from 'node:path'
// __dirname for esm
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default new ProtoLoader({
location: path.join(__dirname, './proto'),
files: [
'helloworld/service.proto'
]
})location 是 loader 搜索的根目录;files 是该目录下的 proto 文件。
loader 部分到此为止。任何需要构造 client 或注册 service 的模块,引入 ./loader.js 即可。
实现服务端
下面的代码全部写在 ./server.js。
导入 loader
import loader from "./loader.js"实现 Greeter
每个 service 就是一个普通类。方法收到 call(带 request、metadata 和辅助方法),返回响应对象即可。
class Greeter {
constructor() {
this.count = 0
}
async sayGreet(call) {
const { name } = call.request
this.count++
return {
message: `hello ${name || "world"} by Greeter`,
count: this.count
}
}
}实现 Hellor
同样的写法,第二个 service:
class Hellor {
async sayHello(call) {
const { name } = call.request
return { message: `hello ${name || "world"} by Hellor` }
}
}绑定和启动
const start = async (addr) => {
// loader 初始化
await loader.init()
// server 初始化并获取实例
const server = await loader.initServer()
// 类方法与 service 进行绑定
server.add('helloworld.Greeter', new Greeter())
server.add('helloworld.Hellor', new Hellor())
// 监听
await server.listen(addr)
console.log('helloworld server is started: ', addr)
}
// 启动
start('127.0.0.1:9098')实现客户端
导入 loader
import loader from "./loader.js"获取客户端
写一个 start 函数,初始化 loader 与 clients 工厂:
const start = async (addr) => {
await loader.init()
const clients = await loader.initClients({
services: {
'helloworld.Greeter': addr,
'helloworld.Hellor': addr
}
})
}发起调用
在 start 里发起两次 RPC 并打印各自的结果:
const start = async (addr) => {
// ...
// loader init
// clients init
// ....
// greeter client
const greeterClient = clients.get('helloworld.Greeter')
const greeterResult = await greeterClient.sayGreet({ name: 'grpcity' })
console.log('greeterClient.sayGreet', greeterResult.response)
// hellor client
const hellorClient = clients.get('helloworld.Hellor')
const hellorResult = await hellorClient.sayHello({ name: 'grpcity' })
console.log('hellorClient.sayHello', hellorResult.response)
}
// 执行
start('127.0.0.1:9098')每次客户端调用都会拿到 { status, peer, metadata, response } 四个字段。这里只打印 response,其余三个在做日志和追踪时同样有用。
运行起来
开两个终端窗口:一个跑服务端,一个跑客户端。
启动服务端
node ./server.js
helloworld server is started: 127.0.0.1:9098服务端起来之后,客户端就可以拨这个地址了。
启动客户端
把客户端跑两次,观察服务端在内存里维持的计数累加:
第一次:
node ./client.js
greeterClient.sayGreet { message: 'hello grpcity by Greeter', count: 1 }
hellorClient.sayHello { message: 'hello grpcity by Hellor' }第二次:
node ./client.js
greeterClient.sayGreet { message: 'hello grpcity by Greeter', count: 2 }
hellorClient.sayHello { message: 'hello grpcity by Hellor' }count 字段在两次之间累加——状态保留在 Greeter 实例上,正是常驻服务端该有的样子。
整个闭环就到这里。继续阅读 使用指南 了解 streaming、middleware、TLS 等能力。
完整源码见 chakhsu/grpcity-basic-demo 。