这是系列的第四篇,总的四个局部如下:
- Tower 概览
- Hyper原理和Axum初试
- Tonic gRPC客户端和服务端示范
- 如何把Axum和Tonic服务集成到一个利用中
单端口,两个协定
下面题目开个玩笑。Axum站点利用和gRPC服务用的都是同一个HTTP/2协定。可能更正当的是他们说不同的方言。然而,重要的是,查看申请和查看是否与gPRC通信通常是比较简单的。gRPC申请头都包含Content-Type: application/grpc;所以咱们明天要实现的是写一些货色承受gPRC申请和一般Service,返回对立的服务。让咱们开始吧!源码可参考
然咱们从main函数开始,来展现咱们最终要实现的样子:
#[tokio::main]async fn main() { let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); let axum_make_service = axum::Router::new() .route("/", axum::handler::get(|| async { "Hello world!" })) .into_make_service(); let grpc_service = tonic::transport::Server::builder() .add_service(EchoServer::new(MyEcho)) .into_service(); let hybrid_make_service = hybrid(axum_make_service, grpc_service); let server = hyper::Server::bind(&addr).serve(hybrid_make_service); if let Err(e) = server.await { eprintln!("server error: {}", e); }}
咱们创立了一个繁难的axum_make_service和grpc_service值,而后用hybrid函数组合成一个服务。留神不同的办法名称,实际上咱们开始用的是into_make_service,起初用的是into_service,不可否认,这将给咱们带来很多苦楚。
无论如何,只管hybrid函数有待解释,启动一个hybrid 服务是小菜一碟;然而,细节决定成败。
当然:有更简略的形式应用trait对象解决上面代码。我防止任何类型代替技术:1.我认为这样能够使代码逻辑更清晰。2.在我心中,这样能够使教程更丰盛。惟一例外的是,我正在应用谬误trait对象,因为Hyper本身也这样做的。这样更简化了跨服务以雷同的谬误示意的代码。
定义hybrid
咱们的hybrid函数返回HybridMakeService:
fn hybrid<MakeWeb, Grpc>(make_web: MakeWeb, grpc: Grpc) -> HybridMakeService<MakeWeb, Grpc> { HybridMakeService { make_web, grpc }}struct HybridMakeService<MakeWeb, Grpc> { make_web: MakeWeb, grpc: Grpc,}
整个过程中变量名称将保持一致和具体。这里,咱们有MakeWeb和Grpc类型。这反映了Axum和Tonic提供API的区别。咱们须要提供链接信息给Axum's MakeWeb
来取得申请解决的 Service
.gRPC,则不须要这么做。
在任何状况下,咱们筹备实现咱们`HybridMakeService的Service:
impl<ConnInfo, MakeWeb, Grpc> Service<ConnInfo> for HybridMakeService<MakeWeb, Grpc>where MakeWeb: Service<ConnInfo>, Grpc: Clone,{ // ...}
咱们有两个预期的类型变量MakeWeb
和Grpc
,以及ConnInfo
,来示意咱们给出的任何连贯信息。Grpc
基本不在乎,但ConnInfo
必须与MakeWeb
接管的内容相匹配。因而,咱们绑定MakeWeb: Service<ConnInfo>和
Grpc: Clone`很快就会有意义。
当咱们收到传入的连贯时,咱们须要做两件事:
获取新
Service
的MakeWeb
。这样做可能是异步产生,也可能会呈现一些谬误。- 注:如果您还记得 Axum 的理论实现,咱们就会晓得这些都不是真的。
Service
从 Axum获取 aIntoMakeService
总是会胜利,并且永远不会做任何异步工作。然而 Axum 中没有 API 公开这个事实,所以咱们被困在Service
API前面。
- 注:如果您还记得 Axum 的理论实现,咱们就会晓得这些都不是真的。
- 克隆咱们曾经领有的gRPC。
一旦咱们有了新的Web
Service
和克隆的Grpc
,咱们将把它们包装成一个新的HybridService构造体。咱们还须要一些帮忙来执行必要的异步操作,因而咱们将创立一个新的Future
类型助手。将看起来像:
type Response = HybridService<MakeWeb::Response, Grpc>;type Error = MakeWeb::Error;type Future = HybridMakeServiceFuture<MakeWeb::Future, Grpc>;fn poll_ready( &mut self, cx: &mut std::task::Context,) -> std::task::Poll<Result<(), Self::Error>> { self.make_web.poll_ready(cx)}fn call(&mut self, conn_info: ConnInfo) -> Self::Future { HybridMakeServiceFuture { web_future: self.make_web.call(conn_info), grpc: Some(self.grpc.clone()), }}
请留神,咱们推延self.make_web
准备就绪并传递其谬误。让咱们通过查看HybridMakeServiceFuture`:
#[pin_project]struct HybridMakeServiceFuture<WebFuture, Grpc> { #[pin] web_future: WebFuture, grpc: Option<Grpc>,}impl<WebFuture, Web, WebError, Grpc> Future for HybridMakeServiceFuture<WebFuture, Grpc>where WebFuture: Future<Output = Result<Web, WebError>>,{ type Output = Result<HybridService<Web, Grpc>, WebError>; fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll<Self::Output> { let this = self.project(); match this.web_future.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Ready(Ok(web)) => Poll::Ready(Ok(HybridService { web, grpc: this.grpc.take().expect("Cannot poll twice!"), })), } }}
咱们须要将pin_project
引入以容许咱们在异步轮询外部实现中将我的项目中future固定。(如果你不相熟pin_project
,请不要放心,咱们稍后会在HybridFuture中形容)当咱们轮询时web_future
,咱们可能会处于以下三种状态之一:
Pending
:MakeWeb
还没筹备好,所以咱们也没筹备好Ready(Err(e))
:MakeWeb
失败,所以咱们传递谬误Ready(Ok(web))
:MakeWeb
胜利了,所以把新web
值和grpc
值打包
this.grpc.take()
从Option类型值中失去gRPC值过程中有一些乏味的事件. Future
s 有一个不变量,一旦它们返回Ready
,就不能再次轮询。因而,能够平安地假如它take
只会被调用一次。然而,如果 Axum 公开一种into_service
办法,则能够防止这种苦楚。
HybridService
后面最终会产生一个HybridService
类型. 让咱们看看:
struct HybridService<Web, Grpc> { web: Web, grpc: Grpc,}impl<Web, Grpc, WebBody, GrpcBody> Service<Request<Body>> for HybridService<Web, Grpc>where Web: Service<Request<Body>, Response = Response<WebBody>>, Grpc: Service<Request<Body>, Response = Response<GrpcBody>>, Web::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>, Grpc::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,{ // ...}
HybridService
将Request<Body>
作为参数。Web
和Grpc
也将Request<Body>
作为参数,但他们返回值略有不同:前者Response<WebBody>
后者Response<GrpcBody>
。咱们须要以某种形式对立返回值。如上所述,咱们将应用 trait 对象进行错误处理,因而不须要对立。
type Response = Response<HybridBody<WebBody, GrpcBody>>;type Error = Box<dyn std::error::Error + Send + Sync + 'static>;type Future = HybridFuture<Web::Future, Grpc::Future>;
关联Response
类型也是 Response<...>
,但它的主体将是HybridBody<WebBody, GrpcBody>
类型。咱们稍后再谈。相似地,咱们有两个不同的Future
s 可能会被调用,具体取决于申请的类型。咱们须要用一个HybridFuture
类型来对立它。
接下来,咱们来看看poll_ready
。咱们须要查看两者Web
并Grpc
为新申请做好筹备。而且每个查看可能会导致以下三种状况之一:Pending
,Ready(Err)
或Ready(Ok)
。这个函数是对于模式匹配和应用.into()
以下办法对立谬误示意:
fn poll_ready( &mut self, cx: &mut std::task::Context<'_>,) -> std::task::Poll<Result<(), Self::Error>> { match self.web.poll_ready(cx) { Poll::Ready(Ok(())) => match self.grpc.poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), Poll::Pending => Poll::Pending, }, Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), Poll::Pending => Poll::Pending, }}
最初,咱们看看call
实现,咱们试图实现的真正逻辑。这是咱们查看申请并确定将其路由到何处的中央:
fn call(&mut self, req: Request<Body>) -> Self::Future { if req.headers().get("content-type").map(|x| x.as_bytes()) == Some(b"application/grpc") { HybridFuture::Grpc(self.grpc.call(req)) } else { HybridFuture::Web(self.web.call(req)) }}
惊人的。所有这些工作基本上只须要 5 行有意义的代码!
HybridFuture
就这样,咱们到了最初!咱们将在本系列中剖析的最初一种类型是HybridFuture
. (还有一种HybridBody
类型,和HybridFuture很类似,咱们就不解释了。)struct
的定义是:
#[pin_project(project = HybridFutureProj)]enum HybridFuture<WebFuture, GrpcFuture> { Web(#[pin] WebFuture), Grpc(#[pin] GrpcFuture),}
像以前一样,咱们应用pin_project
. 这一次,让咱们来探索一下起因。Future
trait接口须要内存中的固定指针。具体来说,第一个参数poll
是self: Pin<&mut Self>
。Rust 自身从不提供任何对于对象持久性的保障,但这对于编写异步运行时零碎相对至关重要。
HybridFuture的poll
办法上因而将接管Pin<&mut HybridFuture>
类型的参数。问题是WebBody或GrpcBody须要调用poll
。假如咱们有Web
变体,咱们面临的问题是模式匹配HybridFuture
会给咱们一个&WebFuture
or &mut WebFuture
。它不会给咱们一个Pin<&mut WebFuture>
,这正是咱们所须要的!
pin_project
生成固定数据类型并用.project()
在原始数据类型上提供一种办法,为咱们提供固定的可变援用。这使咱们可能正确地实现Future
trait HybridFuture
,如下所示:
impl<WebFuture, GrpcFuture, WebBody, GrpcBody, WebError, GrpcError> Future for HybridFuture<WebFuture, GrpcFuture>where WebFuture: Future<Output = Result<Response<WebBody>, WebError>>, GrpcFuture: Future<Output = Result<Response<GrpcBody>, GrpcError>>, WebError: Into<Box<dyn std::error::Error + Send + Sync + 'static>>, GrpcError: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,{ type Output = Result< Response<HybridBody<WebBody, GrpcBody>>, Box<dyn std::error::Error + Send + Sync + 'static>, >; fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll<Self::Output> { match self.project() { HybridFutureProj::Web(a) => match a.poll(cx) { Poll::Ready(Ok(res)) => Poll::Ready(Ok(res.map(HybridBody::Web))), Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), Poll::Pending => Poll::Pending, }, HybridFutureProj::Grpc(b) => match b.poll(cx) { Poll::Ready(Ok(res)) => Poll::Ready(Ok(res.map(HybridBody::Grpc))), Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), Poll::Pending => Poll::Pending, }, } }}
咱们用HybridBody
enum 和 trait 错误处理对象胜利的对立了申请响应。当初咱们为这两种类型的申请提供一个对立的类型。庆贺!
浏览原文