乐趣区

关于后端:译集成Axum-Hyper-Tonic-and-Tower-做webgRPC混合应用03

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

  • 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 那样用不同的状态代表不同的谬误吗?(并不是),所以,在下一节中,咱们有很多围绕使谬误对立的根底工作要做。

浏览原文

退出移动版