Getting Started with a Server Middleware
As Upgrade mentioned, hyper v1 does not depend on tower for the Service
trait. When we want to add tower-like middleware, there are 2 kinds of approach to make it.
Let’s create a Logger middleware in [hello-world server][hello-world] for instance:
Add tower dependency first
[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" # here
Option 1: Use hyper Service trait
Implement hyper Logger middleware
# 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() {}
Then this can be used in server:
# 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 {
// N.B. should use hyper service_fn here, since it's required to be implemented hyper Service trait!
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() {}
Option 2: use hyper TowerToHyperService trait
hyper_util::service::TowerToHyperService trait is an adapter to convert tower Service to hyper Service.
Now implement a tower Logger middleware
# 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() {}
Then use it in the server:
# 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 {
// N.B. should use tower service_fn here, since it's required to be implemented tower Service trait before convert to hyper Service!
let svc = tower::service_fn(hello);
let svc = ServiceBuilder::new().layer_fn(Logger::new).service(svc);
// Convert it to hyper service
let svc = TowerToHyperService::new(svc);
if let Err(err) = http1::Builder::new().serve_connection(io, svc).await {
eprintln!("server error: {}", err);
}
});
}
}
# fn main() {}