Rust gRPC构建工具grpc-build-core的使用:高效生成gRPC客户端和服务端代码的Rust插件库
Rust gRPC构建工具grpc-build-core的使用:高效生成gRPC客户端和服务端代码的Rust插件库
grpc-build
提供了一种灵活的方式来管理 protobuf 文件并生成 tonic 所需的 gRPC 代码。
它构建在 tonic_build 之上,并通过编译目录中的所有 protobuf 文件来扩展其功能。此外,该库还添加了另一个功能:完整 proto 名称注解。这在您想要使用完整名称(包 + 消息名称)来标识 protobuf 消息的情况下可能很有用。因此,对于每个顶级 protobuf 消息,该库会为其生成的结构体添加一个方法,返回其完整的 proto 名称。
给定以下 protobuf 定义:
// my_message.proto
package grpc_build;
message Message {}
该库将生成标准的 Rust 代码以及每个消息的额外实现。
// Message.rs (generated)
struct Message {}
impl NamedMessage for Message {
/// This returns package (grpc-build) + message name (Message).
const NAME: &'static str = "grpc_build.MyMessage"
}
如果 protobuf 内容有效(值得进行 lint 检查),grpc-build
将处理 protobuf 导入,并生成 mod.rs
文件以允许编译器找到生成的代码。该文件将放置在输出目录中。
它既可以作为库直接在项目中使用,也可以作为二进制文件在 CI 管道中使用。
入门指南
作为二进制文件使用
获取最新的二进制版本并在您的 CI 管道中使用它。
grpc-build build --in-dir="<protobuf directory>" --out-dir="<codegen>"
根据需求,您可以使用 --build-client
(-c
) 和 --build-server
(-s
) 标志生成 gRPC 客户端和/或服务器。
要覆盖输出目录的内容,请使用 --force
(-f
) 标志。
// both client and server, overwriting the existing protogen
grpc-build build -c -s --in-dir="<protobuf directory>" --out-dir="<codegen>" -f
作为库使用
使用 grpc_build
作为库最方便的方式是利用 Rust 的 build.rs
文件。不要忘记将 grpc_build
添加到构建依赖项列表中。
// build.rs
use grpc_build::Builder;
fn main() {
Builder::new()
.build_client(true)
.build_server(true)
.force(true)
.out_dir("src/protogen")
.build("protos")
.unwrap();
}
如果您想设置高级编译选项(如为生成的类型添加额外的 #[derive]
),请使用 build_with_config
函数,该函数暴露了底层的 tonic_build::Builder
。
更高级的用法是自行使用 get_protos
和 refactor
函数。以下示例与上面的示例几乎相同,只是您不会自动获得 NamedMessage
特性。
fn main() {
let proto_src_dir = "protos";
let proto_out_dir = "src/protogen";
let protos: Vec<_> = crate::base::get_protos(proto_src_dir).collect();
grpc_build::prepare_out_dir(proto_out_dir).unwrap();
tonic_build::configure()
.out_dir(proto_out_dir)
.build_server(true)
.build_client(true)
.compile(&protos, &["."])
.unwrap();
grpc_build::refactor(proto_out_dir).unwrap();
}
许可证
该项目根据 MIT 许可证进行许可。
完整示例代码
以下是使用 grpc-build-core 的完整示例:
// Cargo.toml 依赖配置
// [build-dependencies]
// grpc-build-core = "0.5.0"
// build.rs 文件示例
use grpc_build::Builder;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 配置构建器生成客户端和服务器代码
Builder::new()
.build_client(true) // 生成客户端代码
.build_server(true) // 生成服务器代码
.force(true) // 强制覆盖现有文件
.out_dir("src/protogen") // 输出目录
.build("protos") // protobuf 文件目录
.unwrap();
Ok(())
}
// 高级配置示例
fn advanced_build() -> Result<(), Box<dyn std::error::Error>> {
let proto_src_dir = "protos";
let proto_out_dir = "src/protogen";
// 获取所有 protobuf 文件
let protos: Vec<_> = grpc_build::get_protos(proto_src_dir).collect();
// 准备输出目录
grpc_build::prepare_out_dir(proto_out_dir)?;
// 使用 tonic_build 配置进行编译
tonic_build::configure()
.out_dir(proto_out_dir)
.build_server(true)
.build_client(true)
.compile(&protos, &["."])?;
// 重构生成的代码
grpc_build::refactor(proto_out_dir)?;
Ok(())
}
// protos/example.proto
syntax = "proto3";
package example;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
使用此配置后,grpc-build-core 会自动生成相应的 Rust 代码,包括带有完整 proto 名称注解的消息结构体。
完整示例demo
基于上述内容,以下是一个完整的项目示例:
Cargo.toml 配置:
[package]
name = "grpc-example"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.8"
prost = "0.11"
[build-dependencies]
grpc-build-core = "0.5.0"
tonic-build = "0.8"
build.rs 文件:
use grpc_build::Builder;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 基础配置:生成客户端和服务器代码
Builder::new()
.build_client(true) // 启用客户端代码生成
.build_server(true) // 启用服务器代码生成
.force(true) // 强制覆盖现有文件
.out_dir("src/protogen") // 指定输出目录
.build("protos") // protobuf 文件所在目录
.unwrap();
Ok(())
}
protobuf 文件 (protos/example.proto):
syntax = "proto3";
package example;
// 定义 Greeter 服务
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc SayGoodbye (GoodbyeRequest) returns (GoodbyeResponse);
}
// 请求消息
message HelloRequest {
string name = 1;
int32 age = 2;
}
// 响应消息
message HelloResponse {
string message = 1;
bool success = 2;
}
// 另一个请求消息
message GoodbyeRequest {
string name = 1;
string reason = 2;
}
// 另一个响应消息
message GoodbyeResponse {
string farewell = 1;
int32 status_code = 2;
}
src/main.rs 使用示例:
mod protogen;
use protogen::example::{greeter_client::GreeterClient, HelloRequest};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建 gRPC 客户端
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
// 创建请求消息
let request = tonic::Request::new(HelloRequest {
name: "World".to_string(),
age: 25,
});
// 发送请求
let response = client.say_hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
src/lib.rs 服务器示例:
mod protogen;
use tonic::{Request, Response, Status};
use protogen::example::{
greeter_server::{Greeter, GreeterServer},
HelloRequest, HelloResponse, GoodbyeRequest, GoodbyeResponse
};
// 实现 Greeter 服务
#[derive(Debug, Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloResponse>, Status> {
let req = request.into_inner();
let reply = HelloResponse {
message: format!("Hello {}! Age: {}", req.name, req.age),
success: true,
};
Ok(Response::new(reply))
}
async fn say_goodbye(
&self,
request: Request<GoodbyeRequest>,
) -> Result<Response<GoodbyeResponse>, Status> {
let req = request.into_inner();
let reply = GoodbyeResponse {
farewell: format!("Goodbye {}! Reason: {}", req.name, req.reason),
status_code: 200,
};
Ok(Response::new(reply))
}
}
// 启动服务器函数
pub async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
tonic::transport::Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
这个完整示例展示了如何使用 grpc-build-core 从 protobuf 定义生成 Rust 代码,并创建完整的 gRPC 客户端和服务器实现。生成的代码会自动包含 NamedMessage 特性,为每个消息提供完整的 proto 名称标识。
grpc-build-core:Rust gRPC构建工具使用指南
介绍
grpc-build-core是一个专为Rust设计的gRPC构建工具,能够高效生成gRPC客户端和服务端代码。该库作为prost-build的扩展,提供了更简洁的配置方式和更强大的代码生成能力,特别适合需要高性能gRPC通信的Rust项目。
主要特性
- 基于prost和tonic构建
- 支持proto3语法
- 自动生成异步客户端和服务端代码
- 可配置的代码生成选项
- 与Cargo构建系统无缝集成
安装方法
在Cargo.toml中添加依赖:
[dependencies]
grpc-build-core = "0.1"
prost = "0.11"
tonic = "0.8"
[build-dependencies]
grpc-build-core = "0.1"
使用方法
1. 创建build.rs构建脚本
// build.rs
use grpc_build_core::Builder;
fn main() -> Result<(), Box<dyn std::error::Error>> {
Builder::new()
.build_server(true)
.build_client(true)
.out_dir("src/generated")
.compile(&["proto/hello.proto"], &["proto/"])?;
Ok(())
}
2. 定义Proto文件
// proto/hello.proto
syntax = "proto3";
package hello;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
3. 使用生成的代码
服务端实现示例:
// src/main.rs
mod generated;
use generated::hello::{greeter_server::{Greeter, GreeterServer}, HelloRequest, HelloResponse};
use tonic::{transport::Server, Request, Response, Status};
#[derive(Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloResponse>, Status> {
let reply = HelloResponse {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
客户端使用示例:
// src/client.rs
mod generated;
use generated::hello::{greeter_client::GreeterClient, HelloRequest};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
let request = tonic::Request::new(HelloRequest {
name: "World".into(),
});
let response = client.say_hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
配置选项
Builder::new()
.build_server(true) // 生成服务端代码
.build_client(true) // 生成客户端代码
.out_dir("src/generated") // 输出目录
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") // 添加属性
.compile_protos(&["proto/hello.proto"], &["proto/"])?;
注意事项
- 确保protobuf编译器(protoc)已安装并在PATH中
- 生成的代码会在每次构建时更新
- 建议将生成的文件加入.gitignore
- 支持自定义代码生成选项和扩展
这个工具极大简化了Rust项目中gRPC代码的生成和使用过程,让开发者能够更专注于业务逻辑的实现。
完整示例demo
以下是一个完整的gRPC服务示例,包含服务端和客户端的完整实现:
项目结构:
my-grpc-project/
├── Cargo.toml
├── build.rs
├── proto/
│ └── hello.proto
└── src/
├── main.rs
└── client.rs
Cargo.toml:
[package]
name = "my-grpc-project"
version = "0.1.0"
edition = "2021"
[dependencies]
grpc-build-core = "0.1"
prost = "0.11"
tonic = "0.8"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
[build-dependencies]
grpc-build-core = "0.1"
use grpc_build_core::Builder;
fn main() -> Result<(), Box<dyn std::error::Error>> {
Builder::new()
.build_server(true)
.build_client(true)
.out_dir("src/generated")
.compile(&["proto/hello.proto"], &["proto/"])?;
Ok(())
}
proto/hello.proto:
syntax = "proto3";
package hello;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc SayHelloStream (HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
string name = 1;
int32 count = 2;
}
message HelloResponse {
string message = 1;
int32 sequence = 2;
}
src/main.rs (服务端):
// 引入生成的gRPC代码
mod generated;
use generated::hello::{
greeter_server::{Greeter, GreeterServer},
HelloRequest, HelloResponse
};
use tonic::{transport::Server, Request, Response, Status};
use tokio_stream::wrappers::ReceiverStream;
use tokio::sync::mpsc;
// 实现Greeter服务
#[derive(Default)]
pub struct MyGreeter {}
#[tonic::async_trait]
impl Greeter for MyGreeter {
// 简单的SayHello方法
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloResponse>, Status> {
let req = request.into_inner();
let reply = HelloResponse {
message: format!("Hello {}!", req.name),
sequence: 0,
};
Ok(Response::new(reply))
}
// 流式响应的SayHelloStream方法
async fn say_hello_stream(
&self,
request: Request<HelloRequest>,
) -> Result<Response<Self::SayHelloStreamStream>, Status> {
let req = request.into_inner();
let (tx, rx) = mpsc::channel(4);
// 在后台任务中生成流式响应
tokio::spawn(async move {
for i in 0..req.count {
let response = HelloResponse {
message: format!("Hello {} - message {}", req.name, i + 1),
sequence: i as i32 + 1,
};
if tx.send(Ok(response)).await.is_err() {
break;
}
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
});
Ok(Response::new(ReceiverStream::new(rx)))
}
}
type SayHelloStreamStream =
tonic::codec::Streaming<Result<HelloResponse, Status>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
println!("gRPC server starting on {}", addr);
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
src/client.rs (客户端):
// 引入生成的gRPC代码
mod generated;
use generated::hello::{greeter_client::GreeterClient, HelloRequest};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 连接到gRPC服务器
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
// 测试普通RPC调用
println!("Testing simple RPC call...");
let request = tonic::Request::new(HelloRequest {
name: "World".into(),
count: 0,
});
let response = client.say_hello(request).await?;
println!("Simple response: {:?}", response.into_inner());
// 测试流式RPC调用
println!("\nTesting streaming RPC call...");
let stream_request = tonic::Request::new(HelloRequest {
name: "Streaming".into(),
count: 5,
});
let mut stream = client.say_hello_stream(stream_request).await?.into_inner();
while let Some(item) = stream.next().await {
match item {
Ok(response) => println!("Stream response: {:?}", response),
Err(e) => eprintln!("Error in stream: {}", e),
}
}
Ok(())
}
运行说明:
- 首先确保安装了protobuf编译器:
# Ubuntu/Debian
sudo apt-get install protobuf-compiler
# macOS
brew install protobuf
- 启动服务端:
cargo run
- 在另一个终端运行客户端:
cargo run --bin client
这个完整示例展示了如何使用grpc-build-core创建一个支持普通RPC调用和流式RPC调用的完整gRPC服务,包含了服务端和客户端的完整实现。