这是系列的第三篇,总的四个局部如下:

  • Tower 概览
  • Hyper原理和Axum初试
  • Tonic gRPC客户端和服务端示范
  • 如何把Axum和Tonic服务集成到一个利用中
Tonic和gRPC

Tonic是一个包含gRPC客户端和服务端库。gRPC是在HTTP/2之上的协定,所以Tonic建设在Hyper(Tower)之上。我在系列文章之初就提到我最后指标是通过一个端口可能提供hybrid web/gRPC 服务。然而目前为止,让咱们快慰的是相熟了规范的Tonic 客户端/服务端利用。咱们将创立一个Echo服务,提供一个终端响应咱们发送的信息。

查看源码.本仓库构造是一个包,包含三个crates:

  • 一个crate提供protobuf定义、Tonic 主动生成客户端和服务端栏目
  • 一个crate提供简略的客户端工具
  • 一个crate提供可运行的服务端

第一个文件咱们看到咱们服务的protobuf定义,文件地位 proto/echo.proto:

syntax = "proto3";package echo;service Echo {  rpc Echo (EchoRequest) returns (EchoReply) {}}message EchoRequest {  string message = 1;}message EchoReply {  string message = 1;}

即便你对protobuf 并不相熟,下面例子也能高深莫测。咱们须要一个build.rs 文件应用tonic_build 去编译这个文件:

fn main() {    tonic_build::configure()        .compile(&["proto/echo.proto"], &["proto"])        .unwrap();}

最终,咱们失去 src/lib.rs 能够提供咱们实现服务端和客户端所需的所有条目:

tonic::include_proto!("echo");

客户端没有什么乏味的。是应用Tokio和Tonic典型的 clap为根底的Cli工具。你能够查看源码.

让咱们持续重要局部:服务端。

服务端

咱们将tonic代码放进咱们的库来构建咱们的Echo trait。咱们须要在一些类型上实现这个trait 来创立咱们的gRPC 服务。这和咱们明天的话题没有间接关系。而且代码非常简单。目前我感觉应用Tonic 写客户/服务端利用的经验十分愉悦、乏味 因为这种实现非常简单:

use tonic_example::echo_server::{Echo, EchoServer};use tonic_example::{EchoReply, EchoRequest};pub struct MyEcho;#[async_trait]impl Echo for MyEcho {    async fn echo(        &self,        request: tonic::Request<EchoRequest>,    ) -> Result<tonic::Response<EchoReply>, tonic::Status> {        Ok(tonic::Response::new(EchoReply {            message: format!("Echoing back: {}", request.get_ref().message),        }))    }}

如果你查看源码.main函数由两种不同的实现。一种被正文掉,这种是比较简单的形式。所以咱们以这种开始:

#[tokio::main]async fn main() -> anyhow::Result<()> {    let addr = ([0, 0, 0, 0], 3000).into();    tonic::transport::Server::builder()        .add_service(EchoServer::new(MyEcho))        .serve(addr)        .await?;    Ok(())}

应用Server::builder办法创立了一个Server值,而后调用了add_service办法,办法实现如下:

impl<L> Server<L> {    pub fn add_service<S>(&mut self, svc: S) -> Router<S, Unimplemented, L>    where        S: Service<Request<Body>, Response = Response<BoxBody>>            + NamedService            + Clone            + Send            + 'static,        S::Future: Send + 'static,        S::Error: Into<crate::Error> + Send,        L: Clone}

咱们失去了另一个Router,这和Axum中作用类似,然而路由到gRPC调用服务名称的服务。让咱们钻研下这里的参数和traits:

  • L 代表了一个层级或或者中间件嵌入到这个服务中。默认是Identity, 没有中间件。
  • S 是咱们将要增加的服务,在这个例子中是Echo服务。
  • 咱们的服务须要承受以前相熟的 Request<Body> 类型,返回 Response<BoxBody>(前面咱们会独自探讨BoxBody),也要命名的服务(不便路由)。
  • 常规,也绑定了'static、send、Clone和谬误

所有这些看起来都很简单。值得快乐的事在简略的Tonic利用中咱们不须要解决的很深刻。相同,咱们只是简略的调用serve办法,所有魔术般的运行了。

但咱们正试图解脱惯例,更好地了解它与Hyper的交互作用。所以让咱们更深刻一点!

into_service

除了serve 办法,Tonic路由也提供了一个into_service 办法。咱们不会深刻研究所有的作用,因为没有多少可探讨的,反而会减少许多浏览的工夫。相同,如下足以阐明:

  • Into_service 返回一个RouterService<S> 值
  • S必须实现Service<Request<Body>, Response = Response<ResBody>>
  • ResBody是一种Hyper能够作为返回值的类型

OK,是不是很酷?当初咱们能够写咱们比拟简短的main函数了,首先咱们创立RouterService值。

let grpc_service = tonic::transport::Server::builder()    .add_service(EchoServer::new(MyEcho))    .into_service();

然而当初咱们有个小问题,Hyper冀望一个"make service" 或者 "app factory",然而咱们只有一个申请解决服务。所以咱们须要用Hyper中的make_service_fn:

et make_grpc_service = make_service_fn(move |_conn| {    let grpc_service = grpc_service.clone();    async { Ok::<_, Infallible>(grpc_service) }});

留神,咱们须要grpc_service的一个拷贝。整个过程中咱们须要拆分闭包和异步块,包含之前看到的Infallible类型。然而咱们克隆了一份,咱们能够启动咱们的gRPC 服务:

let server = hyper::Server::bind(&addr).serve(make_grpc_service);if let Err(e) = server.await {    eprintln!("server error: {}", e);}

如果你想尝试,你能够克隆源码,而后执行:

  • cargo run --bin server
  • 在另一个终端执行 cargo run --bin client "Hello world"

然而,当尝试拜访http://localhost:3000时候并不能运行失常。这个服务只能解决gRPC申请,失常浏览器申请并不能解决,例如Restful Api。咱们还须要最初一步:写一些能够解决Axum和Tonic的服务,并路由到各自的服务。

BoxBody

让咱们具体的看下BoxBody类型。咱们应用的是tonic::body::BoxBody构造体,定义如下:

pub type BoxBody = http_body::combinators::BoxBody<bytes::Bytes, crate::Status>;

http_body通过data和error参数化为自身提供BoxBody。Tonic在gRPC服务中用Status类型代表不同类型的谬误。对不理解Bytes的,能够通过这个文档让你疾速熟悉起来:

Bytes is an efficient container for storing and operating on contiguous slices of memory. It is intended for use primarily in networking code, but could have applications elsewhere as well.

Bytes values facilitate zero-copy network programming by allowing multiple Bytes objects to point to the same underlying memory. This is managed by using a reference count to track when the memory is no longer needed and can be freed.

当你看到Bytes,你从语义上就会认为是字节切片或字节序列。http_body crate底层包裹的BoxBody代表了多种类型 http_body::Body trait的实现。Body trait 代表了了http 流,包含:

  • 关联类型Data和Error,对应参数BoxBody
  • poll_data异步从body中读取数据
  • 帮忙函数map_data和map_err 可应用Data和Error关联类型
  • 一些类型的boxed办法移除,可让咱们获的BoxBody
  • 大小提醒和 HTTP/2 追加数据的其余一些辅助办法

咱们这里最重要的目标"去除类型"并不是真的类型删除。当咱们用boxed获取一个代表body的trait对象时,咱们还有参数代表Data和Error。所以,如果咱们最终失去Data和Error两种不同的示意,两者是不兼容的。留个问题:你认为Axum也会像 Tonic那样用不同的状态代表不同的谬误吗?(并不是),所以,在下一节中,咱们有很多围绕使谬误对立的根底工作要做。

浏览原文