Rust gRPC构建插件grpc-build的使用:高效生成gRPC客户端和服务端代码的Rust工具库
Rust gRPC构建插件grpc-build的使用:高效生成gRPC客户端和服务端代码的Rust工具库
grpc-build
提供了一种灵活的方式来管理protobuf文件并生成由tonic所需的gRPC代码。
它构建在tonic_build之上,并通过编译目录内的所有protobuf文件扩展了其功能。此外,该库还添加了另一个功能:完整的proto名称注解。这在您希望使用完整名称(包+消息名称)来标识protobuf消息的情况下可能很有用。因此,对于每个顶级protobuf消息,该库会为其生成的struct添加一个方法,返回其完整的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内容有效,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许可证授权。
完整示例代码
以下是一个完整的build.rs示例,展示如何使用grpc-build库生成gRPC客户端和服务器代码:
// 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")
// 构建protobuf文件(位于protos目录)
.build("protos")?;
Ok(())
}
对应的Cargo.toml配置:
[package]
name = "my-grpc-project"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.8"
prost = "0.11"
[build-dependencies]
grpc-build = "8.0.0"
tonic-build = "0.8"
项目目录结构:
my-grpc-project/
├── Cargo.toml
├── build.rs
├── protos/
│ └── my_service.proto
└── src/
└── protogen/ (自动生成)
protos/my_service.proto示例:
syntax = "proto3";
package my_service;
service MyService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
完整示例demo
基于上述内容,以下是一个完整的项目示例:
Cargo.toml 配置:
[package]
name = "my-grpc-project"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.8"
prost = "0.11"
[build-dependencies]
grpc-build = "8.0.0"
tonic-build = "0.8"
build.rs 构建脚本:
// 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")
// 构建protobuf文件(位于protos目录)
.build("protos")?;
Ok(())
}
项目目录结构:
my-grpc-project/
├── Cargo.toml
├── build.rs
├── protos/
│ └── my_service.proto
└── src/
└── protogen/ (自动生成)
protos/my_service.proto 协议定义:
syntax = "proto3";
package my_service;
service MyService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
src/main.rs 主程序示例:
// src/main.rs
mod protogen; // 自动生成的模块
use tonic::{transport::Server, Request, Response, Status};
use protogen::my_service::{my_service_server::{MyService, MyServiceServer}, HelloRequest, HelloResponse};
// 实现gRPC服务
#[derive(Default)]
struct MyServiceImpl;
#[tonic::async_trait]
impl MyService for MyServiceImpl {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloResponse>, Status> {
let name = request.into_inner().name;
let reply = HelloResponse {
message: format!("Hello {}!", name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let service = MyServiceImpl::default();
Server::builder()
.add_service(MyServiceServer::new(service))
.serve(addr)
.await?;
Ok(())
}
src/lib.rs 库文件示例:
// src/lib.rs
pub mod protogen; // 导出自动生成的模块
// 重新导出生成的类型以便外部使用
pub use protogen::my_service::{my_service_client::MyServiceClient, HelloRequest, HelloResponse};
这个完整示例展示了如何使用grpc-build库构建一个完整的gRPC服务项目,包括协议定义、代码生成、服务实现和客户端使用。
grpc-build:高效生成gPC客户端和服务端代码的Rust工具库
概述
grpc-build是一个用于简化Rust项目中gRPC客户端和服务端代码生成的构建工具。它基于prost和tonic构建,能够自动处理.proto文件的编译和代码生成过程,让开发者专注于业务逻辑的实现。
主要特性
- 自动编译.proto文件生成Rust代码
- 支持gRPC客户端和服务端代码生成
- 与Cargo构建系统无缝集成
- 支持自定义代码生成选项
- 提供类型安全的API接口
安装方法
在Cargo.toml中添加依赖:
[dependencies]
tonic = "0.8"
prost = "0.11"
[build-dependencies]
grpc-build = "0.8"
基本使用方法
- 创建build.rs文件:
fn main() -> Result<(), Box<dyn std::error::Error>> {
grpc_build::compile_protos(&["proto/helloworld.proto"])?;
Ok(())
}
- 项目结构示例:
my-project/
├── Cargo.toml
├── build.rs
└── proto/
└── helloworld.proto
- 在代码中使用生成的gRPC代码:
mod helloworld {
tonic::include_proto!("helloworld");
}
use helloworld::{HelloRequest, HelloResponse};
use tonic::{Request, Response, Status};
#[tonic::async_trait]
impl helloworld::greeter_server::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))
}
}
高级配置
自定义编译选项:
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut config = grpc_build::Config::new();
config
.build_server(true)
.build_client(true)
.out_dir("src/generated");
config.compile_protos(&["proto/helloworld.proto"], &["proto/"])?;
Ok(())
}
使用建议
- 将.proto文件放在专门的proto目录中
- 在build.rs中处理编译错误
- 使用模块组织生成的代码
- 考虑使用features控制客户端/服务端代码生成
这个工具库大大简化了Rust项目中gRPC的开发流程,让开发者能够快速构建高性能的gRPC应用。
完整示例demo
以下是基于提供内容的完整示例:
项目结构:
grpc-demo/
├── Cargo.toml
├── build.rs
├── proto/
│ └── helloworld.proto
└── src/
├── client.rs
├── server.rs
└── main.rs
proto/helloworld.proto:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
Cargo.toml:
[package]
name = "grpc-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.8"
prost = "0.11"
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
[build-dependencies]
grpc-build = "0.8"
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 编译proto文件生成Rust代码
grpc_build::compile_protos(&["proto/helloworld.proto"])?;
Ok(())
}
src/server.rs:
use tonic::{Request, Response, Status};
// 包含生成的gRPC代码
mod helloworld {
tonic::include_proto!("helloworld");
}
use helloworld::{HelloRequest, HelloResponse};
use helloworld::greeter_server::{Greeter, GreeterServer};
// 实现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> {
println!("收到请求: {:?}", request);
let reply = HelloResponse {
message: format!("你好 {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
// 创建gRPC服务器
pub async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
println!("服务器启动在 {}", addr);
tonic::transport::Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
Ok(())
}
src/client.rs:
use tonic::Request;
mod helloworld {
tonic::include_proto!("helloworld");
}
use helloworld::{HelloRequest};
use helloworld::greeter_client::GreeterClient;
// gRPC客户端实现
pub async fn run_client() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
let request = Request::new(HelloRequest {
name: "世界".into(),
});
let response = client.say_hello(request).await?;
println!("服务器响应: {}", response.into_inner().message);
Ok(())
}
src/main.rs:
mod server;
mod client;
use clap::{Arg, Command};
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = Command::new("gRPC Demo")
.version("0.1.0")
.author("开发者")
.about("gRPC客户端服务器示例")
.arg(Arg::new("mode")
.short('m')
.long("mode")
.value_name("MODE")
.help("运行模式: server 或 client")
.required(true))
.get_matches();
match matches.get_one::<String>("mode").unwrap().as_str() {
"server" => {
println!("启动gRPC服务器...");
server::start_server().await?;
}
"client" => {
println!("启动gRPC客户端...");
client::run_client().await?;
}
_ => {
eprintln!("无效模式,请使用 'server' 或 'client'");
}
}
Ok(())
}
运行示例:
- 启动服务器:
cargo run -- --mode server
- 启动客户端(在另一个终端):
cargo run -- --mode client
这个完整示例展示了如何使用grpc-build构建完整的gRPC应用,包括proto文件定义、代码生成、服务器和客户端实现。