跳转至

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
        }

项目目录结构

Snipaste_2020-09-15_21-32-42.png