API
一个API做了两件事
- 客户端发起请求Request
- 服务端作出响应Response
REST是什么
REST(Representational State Transfer):表现层状态传输,是一种设计风格,通常将 HTTP API 称为 RESTful API、RESTful 服务或 REST 服务
- 资源由URL决定
- 通过GET、POST、PUT、DELETE、PATCH、OPTIONS、HEAD、TRACE方法来操作资源
- 操作资源的表现形式是XML、HTML、JSON等格式
- 由客户端保存状态
RPC是什么
RPC(Remote Procedure Call):远程过程调用,像调用本地函数一样调用远程函数,grpc
是RPC的一种实现
Tonic是什么
tonic基于HTTP/2构建,grpc的Rust实现
Protocol Buffers
Protocol Buffers协议缓冲区
服务方法
允许定义四种服务方法
- 一元RPC:客户端向服务端发送单个请求并得到单个响应,就像调用普通函数一样
- 服务端流式RPC:客户端向服务端发送请求并获取流以读取一系列消息,客户端从返回的流中读取直到没有消息
- 客户端流式RPC:客户端使用流编写一系列消息发送到服务端,等待服务端做出响应
- 双向流式RPC:双方使用读写流发送一系列消息,两个流独立运行
Protocol Buffers语法
- 文件名以
.proto
结尾 - 类似于json,体积更小、速度更快,会生成本机语言绑定
message:定义数据传递格式
-
一个proto文件可以定义多个消息,消息可以嵌套
-
required:proto2必填、proto3不需要填
-
optional:可选字段
-
repeate:可重复字段
-
标识号:每个字段必须要有一个唯一的标识号,范围1~2^29 -1(19000-19999保留标识号不能用)
service:RPC服务接口
认证
- SSL/TLS认证
- TLS(Transport layerSecurity)安全传输层,建立在TCP协议上,前身是SSL(Secure Socket Layer)安全套接字层,将应用层的报文进行加密后再交由TCP传输
- 基于Token认证
- 自定义认证
安装protobuf
https://github.com/protocolbuffers/protobuf/releases/tag/v28.2
解压后将路径加入PATH
验证
protoc --version
Tonic实战
- 需要安装protobuf
- 项目地址:https://github.com/VCCICCV/tonic-demo
创建项目
cargo new tonic-server
cargo new tonic-client
两个项目都添加依赖
[dependencies]
tonic = "0.12.2"
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
# grpc编码器
prost = "0.13.2"
# 我们只在构建的时候需要它
[build-dependencies]
tonic-build = "0.12.2"
在两个项目同级新建proto\order.proto
syntax = "proto3";// 指定proto版本
package order;// 指定包名
// 定义服务
service OrderService {
rpc GetOrder (OrderRequest) returns (OrderResponse);
rpc SetOrder (Order) returns (OrderResponse);
}
// 定义message类型
message OrderRequest {
int32 id = 1;
}
message OrderResponse {
int32 id = 1;
string description = 2;
double price = 3;
}
message Order {
int32 id = 1;
string description = 2;
double price = 3;
}
两个项目根目录新建build.rs
,用于编译客户端和服务端的代码
// 告诉tonic-build,需要编译哪些文件
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("../proto/order.proto")?;// 注意导入的是项目外也就是项目同级的文件夹
Ok(())
}
此时两个项目编译
cargo build
生成的rust代码在target\debug\build\tonic-server-****\out\order.rs
server项目新建src\order_service.rs
use tonic::{
Request, Response, Status };
use order::order_service_server::{
OrderService, OrderServiceServer };
use order::{
OrderRequest, OrderResponse, Order };
pub mod order {
tonic::include_proto!("order");// 使用宏引入proto文件
}
#[derive(Debug, Default)] // 实现打印、默认值
pub struct MyOrderService;
#[tonic::async_trait] // 异步方法
impl OrderService for MyOrderService {
// 获取订单
async fn get_order(
&self,
request: Request<OrderRequest>
) -> Result<Response<OrderResponse>, Status> {
let req = request.into_inner();
let response = OrderResponse {
id: req.id,
description: format!("Order {}", req.id),
price: 100.0,
};
Ok(Response::new(response))
}
// 创建订单
async fn set_order(&self, request: Request<Order>) -> Result<Response<OrderResponse>, Status> {
let order = request.into_inner();
let response = OrderResponse {
id: order.id,
description: order.description,
price: order.price,
};
Ok(Response::new(response))
}
}
// 启动grpc服务
pub fn create_server() -> OrderServiceServer<MyOrderService> {
OrderServiceServer::new(MyOrderService::default())
}
server项目main.rs
mod order_service;
use order_service::create_server;
use tonic::transport::Server;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 监听端口
let addr = "[::1]:50051".parse()?;
// 创建grpc服务
let order_service = create_server();
println!("OrderService listening on {}", addr);
// 启动服务
Server::builder().add_service(order_service).serve(addr).await?;
Ok(())
}
此时启动server项目
cargo run
使用Apifox创建grpc请求
设置端口[::1]:50051
导入proto文件
此时调用即可响应请求
{
"id": 0,
"description": "Order 0",
"price": 100
}
client项目main.rs
use order::order_service_client::OrderServiceClient;
use order::{
OrderRequest, Order };
pub mod order {
tonic::include_proto!("order");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 连接到gRPC服务端
let mut client = OrderServiceClient::connect("http://[::1]:50051").await?;
// 创建请求
let request = tonic::Request::new(OrderRequest {
id: 1 });
// 发送请求并等待响应
let response = client.get_order(request).await?;
println!("RESPONSE={:?}", response);
let order = Order {
id: 1,
description: "New Order".to_string(),
price: 150.0,
};
// 创建请求
let request = tonic::Request::new(order);
// 发送请求并等待响应
let response = client.set_order(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
运行即可调用服务端的方法
cargo run
响应数据
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponse { id: 1, description: "Order 1", price: 100.0 }, extensions: Extensions }
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 tent-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponse { id: 1, description: "Order 1", price: 100.0 }, extensions: Extensions }
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponions: Extensions }
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderRespontent-type": "application/grpc", "date": "Fri, 20 Sep 2024 17:35:55 GMT", "grpc-status": "0"} }, message: OrderResponse { id: 1, description: "New Order", price: 150.0 }, extensions: Extensions }
一元RPC、服务端流式RPC、客户端流式RPC、双向流式RPC
example.proto
syntax = "proto3";
package example;
service ExampleService {
// Unary RPC
rpc UnaryCall(RequestMessage) returns (ResponseMessage);
// Server-side streaming RPC
rpc ServerStream(RequestMessage) returns (stream ResponseMessage);
// Client-side streaming RPC
rpc ClientStream(stream RequestMessage) returns (ResponseMessage);
// Bidirectional streaming RPC
rpc BidiStream(stream RequestMessage) returns (stream ResponseMessage);
}
message RequestMessage {
string message = 1;
}
message ResponseMessage {
string message = 1;
}
server项目build.rs
// 告诉tonic-build,需要编译哪些文件
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("../proto/example.proto")?;// 注意导入的是项目外也就是项目同级的文件夹
Ok(())
}
server项目cargo.toml
[dependencies]
tonic = "0.12.2"
tokio = { version = "1.40.0", features = ["full"] }
prost = "0.13.2"
futures-util = "0.3.30"
tokio-stream = "0.1.14"
tonic-reflection = "0.12.2"
[build-dependencies]
tonic-build = "0.12.2"
server项目main.rs
use tonic::{
transport::Server, Request, Response, Status };
use futures_util::Stream; // 使用 futures_util 提供的 Stream trait
use tokio_stream::wrappers::ReceiverStream; // 引入 tokio_stream
use tokio::sync::mpsc;
use std::pin::Pin;
use example::example_service_server::{
ExampleService, ExampleServiceServer };
use example::{
RequestMessage, ResponseMessage };
pub mod example {
tonic::include_proto!("example");
}
#[derive(Default)]
pub struct MyExampleService {
}
#[tonic::async_trait]
impl ExampleService for MyExampleService {
// 1. 一元 RPC 调用
async fn unary_call(
&self,
request: Request<RequestMessage>
) -> Result<Response<ResponseMessage>, Status> {
println!("一元调用");
let message = request.into_inner().message;
Ok(
Response::new(ResponseMessage {
message: format!("Hello from Unary: {}",