Начало работы с Middleware (Промежуточным слоем) сервера
Как упоминалось в разделе [Обновление][upgrading], hyper v1 не зависит от tower в отношении трейта Service. Когда мы хотим добавить middleware, подобный tower, существует 2 подхода для его реализации.
Давайте создадим Logger middleware для [сервера hello-world][hello-world] в качестве примера:
Сначала добавим зависимость tower
[dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }
tower = "0.4" # и это
Вариант 1: Использование трейта Service из hyper
Реализуем Logger middleware для hyper
extern crate hyper; use hyper::{Request, body::Incoming, service::Service}; #[derive(Debug, Clone)] pub struct Logger<S> { inner: S, } impl<S> Logger<S> { pub fn new(inner: S) -> Self { Logger { inner } } } type Req = Request<Incoming>; impl<S> Service<Req> for Logger<S> where S: Service<Req>, { type Response = S::Response; type Error = S::Error; type Future = S::Future; fn call(&self, req: Req) -> Self::Future { println!("processing request: {} {}", req.method(), req.uri().path()); self.inner.call(req) } } fn main() {}
Затем это можно использовать в сервере:
extern crate tower; extern crate hyper; extern crate http_body_util; extern crate tokio; extern crate hyper_util; mod no_run { use std::{convert::Infallible, net::SocketAddr}; use hyper::{ service::Service, body::{Bytes, Incoming}, server::conn::http1, Request, Response, }; use http_body_util::Full; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; use tower::ServiceBuilder; #[derive(Debug, Clone)] pub struct Logger<S> { inner: S, } impl<S> Logger<S> { pub fn new(inner: S) -> Self { Logger { inner } } } type Req = Request<Incoming>; impl<S> Service<Req> for Logger<S> where S: Service<Req>, { type Response = S::Response; type Error = S::Error; type Future = S::Future; fn call(&self, req: Req) -> Self::Future { println!("processing request: {} {}", req.method(), req.uri().path()); self.inner.call(req) } } async fn hello(_: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> { Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).await?; loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); tokio::spawn(async move { // Важно: здесь следует использовать hyper service_fn, так как требуется реализация трейта Service из hyper! let svc = hyper::service::service_fn(hello); let svc = ServiceBuilder::new().layer_fn(Logger::new).service(svc); if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { eprintln!("server error: {}", err); } }); } } } fn main() {}
Вариант 2: Использование трейта TowerToHyperService из hyper
Трейт [hyper_util::service::TowerToHyperService][adapter-trait] — это адаптер для преобразования сервиса tower в сервис hyper.
Теперь реализуем Logger middleware для tower
extern crate tower; extern crate hyper; use hyper::{Request, body::Incoming}; use tower::Service; #[derive(Debug, Clone)] pub struct Logger<S> { inner: S, } impl<S> Logger<S> { pub fn new(inner: S) -> Self { Logger { inner } } } type Req = Request<Incoming>; impl<S> Service<Req> for Logger<S> where S: Service<Req> + Clone, { type Response = S::Response; type Error = S::Error; type Future = S::Future; fn poll_ready( &mut self, cx: &mut std::task::Context<'_>, ) -> std::task::Poll<Result<(), Self::Error>> { self.inner.poll_ready(cx) } fn call(&mut self, req: Req) -> Self::Future { println!("processing request: {} {}", req.method(), req.uri().path()); self.inner.call(req) } } fn main() {}
Затем используем его в сервере:
extern crate hyper; extern crate http_body_util; extern crate hyper_util; extern crate tokio; extern crate tower; mod no_run { use std::{convert::Infallible, net::SocketAddr}; use hyper::{ body::{Bytes, Incoming}, server::conn::http1, Request, Response, }; use http_body_util::Full; use hyper_util::{rt::TokioIo, service::TowerToHyperService}; use tokio::net::TcpListener; use tower::{ServiceBuilder, Service}; #[derive(Debug, Clone)] pub struct Logger<S> { inner: S, } impl<S> Logger<S> { pub fn new(inner: S) -> Self { Logger { inner } } } type Req = Request<Incoming>; impl<S> Service<Req> for Logger<S> where S: Service<Req> + Clone, { type Response = S::Response; type Error = S::Error; type Future = S::Future; fn poll_ready( &mut self, cx: &mut std::task::Context<'_>, ) -> std::task::Poll<Result<(), Self::Error>> { self.inner.poll_ready(cx) } fn call(&mut self, req: Req) -> Self::Future { println!("processing request: {} {}", req.method(), req.uri().path()); self.inner.call(req) } } async fn hello(_: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> { Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).await?; loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); tokio::spawn(async move { // Важно: здесь следует использовать tower service_fn, так как требуется сначала реализовать трейт Service из tower, а затем преобразовать в сервис hyper! let svc = tower::service_fn(hello); let svc = ServiceBuilder::new().layer_fn(Logger::new).service(svc); // Преобразуем его в сервис hyper let svc = TowerToHyperService::new(svc); if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { eprintln!("server error: {}", err); } }); } } fn main() {}
[hellp-world]: {{ site.url }}/guides/1/server/hello-world/ [upgrading]: {{ site.url }}/guides/1/upgrading/ [adapter-trait]: {{ site.hyper_util_url }}/latest/hyper_util/service/struct.TowerToHyperService.html