Rust-tonic(gRPC实现)
前言¶
站在巨人的肩膀上你就是巨人
对于生产利器 肯定需要和gRPC打交道, 这里推荐一个 Rust 使用比较方便的库
tonic github 链接: 直达链接
官方GitHub上也有很多example, 可以参照一下, 我这里只是简单的用一下, 工作中用到了我会再去看相关
依赖环境¶
# Cargo.toml
[package]
name = "grpc_stu"
version = "0.1.0"
authors = ["zhangy233"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
prost = "0.6.1"
tokio = { version = "0.2", features = ["macros"] }
tonic = "0.2"
http = "*"
[build-dependencies]
tonic-build = {version = "0.2", features = ["prost"]} # build.rs文件自动执行编译的配置
[[bin]] # 用来编写服务端代码
name = "foobar-server"
path = "src/server.rs"
[[bin]] # 用来编写客户端代码
name = "foobar-client"
path = "src/client.rs"
build.rs文件介绍¶
注意: build.rs 文件需要放到项目根目录,此文件的编译不是由用户手动执行, 而是在编译其他目标时, 自动执行编译
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("./foobar.proto")?; // build时自动生成中间文件 foobar.rs
Ok(())
}
proto文件¶
这里在项目根目录创建一个foobar.proto文件
syntax = "proto3";
package foobar;
service FooBarService {
rpc save_addr(Addr) returns (Save_response);
// rpc get_addr(Get_request) returns (Addr); // 太麻烦了,Demo里暂时就只实现一个吧
}
message Addr{
string name = 2; // 名字
repeated Location location = 1; // 位置
}
message Save_response{
bool accepted = 1; // 是否保存成功
}
message Get_request{
repeated Location location = 1; // 需要获取的 位置
}
message Location{ // 位置的相关坐标
float x = 1;
float y = 2;
}
// repeated: 表示是一个连续的流输入输出
服务端代码实现¶
创建文件 src/server.rs
注意: 这里大小写转换的机制只能自己体会了,,,,自己折腾折腾, proto里面的下划线全部去掉,用驼峰,,,,
use tonic::{transport::Server, Request, Response, Status};
use tokio;
use tonic;
use foobar::foo_bar_service_server::{FooBarService, FooBarServiceServer};
use foobar::{SaveResponse,Addr,GetRequest,Location};
pub mod foobar {
tonic::include_proto!("foobar"); // 这里指定的字符串必须与proto的包名称一致
}
#[derive(Debug, Default)]
pub struct MyServer {}
#[tonic::async_trait]
impl FooBarService for MyServer{
async fn save_addr(&self, request: Request<Addr>, ) -> Result<Response<SaveResponse>, Status> {
println!("request: {:?}", request);
println!("remote_addr: {:?}", request.remote_addr());
println!("metadata: {:?}", request.metadata());
let response = foobar::SaveResponse { accepted:false };
Ok(Response::new(response))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:50051".parse().unwrap();
let my_server = MyServer::default();
println!("GreeterServer listening on {}", addr);
Server::builder()
.add_service(FooBarServiceServer::new(my_server))
.serve(addr)
.await?;
Ok(())
}
客户端代码¶
大小写自己体会
代码中, 我有尝试添加headers, 因为有一些业务场景需要手动添加一些元信息
use foobar::foo_bar_service_client::FooBarServiceClient;
use foobar::{SaveResponse,Addr,GetRequest,Location};
use tonic::metadata::*;
use tonic::client::{Grpc};
use tonic::IntoRequest;
use tonic::codec::ProstCodec;
pub mod foobar {
tonic::include_proto!("foobar"); // 这里指定的字符串必须与proto的包名称一致
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 这里多次尝试改变uri, 这种方式直接修改绑定地址的方式不知道改的有没有有用(创建不同的client???拥有不同的uri???也可以哈,好像也不咋费性能,因为连接是提前建立好的
// 直接改 foobar.rs 生成的源码, 在调用和函数中直接硬改,增加一个参数, 当然下面函数中 client 中就需要传递uri相关的参数了
let mut client = FooBarServiceClient::connect("http://127.0.0.1:50051/HK/IS/").await?;
println!("client: {:?}", client);
let location = Location {
x: 7.62,
y: 5.56,
};
let mut request = tonic::Request::new(Addr {
name: "Tonic".into(),
location: vec![location] // 因为使用了 repeated, 这里传入一个 流
});
// 补充headers(这里直接通过相关requestAPI 取到内部metadata的可变引用,直接操作即可,相关API可以到官方文档中查看)
request.metadata_mut().insert("language", "javascript".parse().unwrap());
println!("REQUEST={:?}", request);
// 补充headers(废弃 相较于上面比较,,,,多谢了很多代码)
// let mut headers = MetadataMap::new();
// headers.insert("language", "javascript".parse().unwrap());
// let metadata_mut = request.metadata_mut();
// *metadata_mut = headers;
// 尝试重写调用方法,以达到修改uri的目的(无法实现)
// use http;
// struct Co;
// impl codec for Co{};
// use http::uri::*;
// let codec = tonic::codec::ProstCodec::default();
// let mut new_c = Grpc::new(client);
// new_c.client_streaming(request.into_request(),
// http::uri::PathAndQuery::from_static("/foobar.FooBarService/save_addr"),
// codec
// ).await;
let response = client.save_addr(request).await?;
// let response = client.inner.unary(request.into_request(), // 尝试修改uri失败
// http::uri::PathAndQuery::from_static("/foobar.FooBarService/save_addr"),
// tonic::codec::ProstCodec::default()).await;
println!("RESPONSE ={:?}", response); // 查看响应
let data:SaveResponse = response.into_inner();
println!("into_inner accepted = {:?}", data.accepted); // 查看内容
Ok(())
}
关于尝试修改调用过程中的请求uri¶
下面的尝试我也咩有进行验证(因为mac没有找到一个可以监听本地网卡的软件), 第二种应该是可以的,因为我在python中也是这样魔改的
这里尝试了很久,也翻阅了相关文档, 该库作者并没有提供相关api, 且一些request的一些方法也是私有的外部无法调用, 最后代码中直接尝试在创建 client 的时候指定其uri , 这样不同的业务不同的uri 不同的client,(当然我没试过这样行不行, 而且如果uri需要分发多个地址,会造成代码冗余), 如
FooBarServiceClient::connect("http://127.0.0.1:50051/HK/IS/").await?;
添加了/HK/IS
还有一种方法, 就是直接改 由 foobar.proto 生成的中间文件 foobar.rs, 强行修改 源文件, 强求修改调用方法,让其可以在外部调用时可以接受一个参数, 注意, 第一次build之后,就会在 target/release 或 debug相关目录中的build文件夹中 以项目名称开头的文件夹, 有两个,找到 opt文件夹中的 foobar.rs 就可以硬改了, 注意每次build 之后都会复原该文件需要注意时效哦, 如下修改了 save_addr的调用过程:
pub async fn save_addr(
&mut self,
request: impl tonic::IntoRequest<super::Addr>,
tp:String // 调用时需要额外传入一个参数哦
) -> Result<tonic::Response<super::SaveResponse>, tonic::Status> {
self.inner.ready().await.map_err(|e| {
tonic::Status::new(
tonic::Code::Unknown,
format!("Service was not ready: {}", e.into()),
)
})?;
println!("TYPE: {}", tp) // 这里就可以接受外部传进来的参数
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/foobar.FooBarService/save_addr");
self.inner.unary(request.into_request(), path, codec).await
}