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_protosrefactor函数。以下示例与上面的示例几乎相同,只是您不会自动获得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服务项目,包括协议定义、代码生成、服务实现和客户端使用。


1 回复

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"

基本使用方法

  1. 创建build.rs文件:
fn main() -> Result<(), Box<dyn std::error::Error>> {
    grpc_build::compile_protos(&["proto/helloworld.proto"])?;
    Ok(())
}
  1. 项目结构示例:
my-project/
├── Cargo.toml
├── build.rs
└── proto/
    └── helloworld.proto
  1. 在代码中使用生成的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"

build.rs:

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(())
}

运行示例:

  1. 启动服务器:
cargo run -- --mode server
  1. 启动客户端(在另一个终端):
cargo run -- --mode client

这个完整示例展示了如何使用grpc-build构建完整的gRPC应用,包括proto文件定义、代码生成、服务器和客户端实现。

回到顶部