在開始之前,請確保您已經安裝了以下工具和環境:
protoc)cargo 包管理器首先,選擇一個目錄作為項目的工作目錄,并使用以下命令創建一個新的 Rust crate:
cargo new demo
接下來,進入項目目錄,為后續步驟準備好代碼結構。
在本教程中,我們將創建一個服務,用于跟蹤雜貨店的庫存。該服務支持以下功能:
我們將通過 Protobuf 定義服務協議。首先,創建一個 proto/ 目錄,用于存放 .proto 文件:
mkdir proto
然后,在 proto/ 目錄下創建 store.proto 文件,定義服務和消息類型。以下是一個示例:
message WatchRequest {
string filter = 1;
}
package store;
service Inventory {
rpc Add(Item) returns (Response);
rpc Remove(ItemId) returns (Response);
rpc Get(ItemId) returns (Item);
rpc UpdateQuantity(UpdateRequest) returns (Response);
rpc UpdatePrice(UpdateRequest) returns (Response);
rpc Watch(WatchRequest) returns (stream Item);
}
message Item {
string id = 1;
string name = 2;
int32 quantity = 3;
float price = 4;
}
message ItemId {
string id = 1;
}
message UpdateRequest {
string id = 1;
int32 quantity = 2;
float price = 3;
}
message Response {
string message = 1;
}
message WatchRequest {
string filter = 1;
}
定義好服務協議后,我們需要編譯 .proto 文件以生成 Rust 的客戶端和服務器代碼。為此,我們需要添加以下依賴到 Cargo.toml 文件中:
[dependencies]
tonic = "0.7"
prost = "0.11"
[build-dependencies]
tonic-build = "0.7"
接著,創建一個 build.rs 文件,用于在構建時自動編譯 .proto 文件:
fn main() {
tonic_build::configure()
.build_server(true)
.build_client(true)
.out_dir("src/")
.compile(&["proto/store.proto"], &["proto"])
.unwrap();
}
運行以下命令以生成代碼:
cargo build
此時,src/ 目錄下將生成 store.rs 文件,其中包含客戶端和服務器的代碼。
接下來,我們需要實現服務器端邏輯。創建一個新的文件 src/server.rs,并引入必要的模塊:
use tonic::{transport::Server, Request, Response, Status};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
mod store {
tonic::include_proto!("store");
}use store::inventory_server::{Inventory, InventoryServer};
use store::{Item, ItemId, Response as StoreResponse, UpdateRequest};
我們將使用一個線程安全的 HashMap 作為內存中的庫存存儲:
#[derive(Default)]
pub struct StoreInventory {
inventory: Arc<Mutex<HashMap>>,
}
以下是 Inventory 服務的部分方法實現:
#[tonic::async_trait]
impl Inventory for StoreInventory {
async fn add(
&self,
request: Request,
) -> Result<Response, Status> {
let item = request.into_inner();
let mut inventory = self.inventory.lock().unwrap();
if inventory.contains_key(&item.id) {
return Err(Status::already_exists("Item already exists"));
} inventory.insert(item.id.clone(), item);
Ok(Response::new(StoreResponse {
message: "Item added successfully".to_string(),
}))
}
}
async fn remove(
&self,
request: Request,
) -> Result<Response, Status> {
let id = request.into_inner().id;
let mut inventory = self.inventory.lock().unwrap();
if inventory.remove(&id).is_some() {
Ok(Response::new(StoreResponse {
message: "Item removed successfully".to_string(),
}))
} else {
Err(Status::not_found("Item not found"))
}
}
async fn update_quantity(
&self,
request: Request,
) -> Result<Response, Status> {
let update = request.into_inner();
let mut inventory = self.inventory.lock().unwrap();
if let Some(item) = inventory.get_mut(&update.id) {
item.quantity += update.quantity;
Ok(Response::new(StoreResponse {
message: "Quantity updated successfully".to_string(),
}))
} else {
Err(Status::not_found("Item not found"))
}
}
完成所有方法后,啟動服務器:
#[tokio::main]
async fn main() -> Result<(), Box> {
let addr = "[::1]:50051".parse().unwrap();
let inventory = StoreInventory::default();
Server::builder()
.add_service(InventoryServer::new(inventory))
.serve(addr)
.await?; Ok(())
}
為了測試服務器,我們需要實現一個簡單的命令行客戶端。以下是客戶端的主要邏輯:
async fn add_item(client: &mut InventoryClient, id: &str, name: &str, quantity: i32, price: f32) {
let item = Item {
id: id.to_string(),
name: name.to_string(),
quantity,
price,
};
let response = client.add(Request::new(item)).await.unwrap();
println!("Response: {}", response.into_inner().message);
}
#[tokio::main]
async fn main() -> Result<(), Box> {
let mut client = InventoryClient::connect("http://[::1]:50051").await?;
add_item(&mut client, "1", "Apple", 100, 1.5).await; Ok(())
}
運行服務器后,使用客戶端發送請求以測試服務功能。例如:
cargo run --bin server
cargo run --bin client
您可以嘗試添加、刪除、更新商品,并觀察服務器的響應。
通過本教程,您已經學會了如何使用 Rust 構建一個完整的 gRPC API,包括服務協議定義、Protobuf 編譯、服務器實現和客戶端測試。接下來,您可以嘗試添加更多功能,例如 TLS 加密和身份驗證,以提升服務的安全性。
原文鏈接: https://konghq.com/blog/engineering/building-grpc-apis-with-rust