Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add middleware doc for server #116

Merged
merged 4 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/check_guides.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ EOF
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }
tower = "0.4"
EOF
cargo build --manifest-path "$value/Cargo.toml"
fi
Expand Down
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ docs_url: https://docs.rs/hyper
examples_url: https://github.com/hyperium/hyper/tree/master/examples
http_body_util_url: https://docs.rs/http-body-util
hyper_tls_url: https://docs.rs/hyper-tls
hyper_util_url: https://docs.rs/hyper-util

futures_url: https://docs.rs/futures/0.3.*
legacy_docs_url: https://docs.rs/hyper/0.14.*
Expand Down
1 change: 1 addition & 0 deletions _data/stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
guides:
- hello-world
- echo
- middleware
NewbMiao marked this conversation as resolved.
Show resolved Hide resolved

- title: Client
path: "/client"
Expand Down
253 changes: 253 additions & 0 deletions _stable/server/middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
---
title: Getting Started with a Server Middleware
layout: guide
---

As [Upgrade](upgrading) 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

```toml
[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

```rust
# 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:

```rust
# 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)
# }
# }
dswij marked this conversation as resolved.
Show resolved Hide resolved
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](adapter-trait) trait is an adapter to convert tower Service to hyper Service.

Now implement a tower Logger middleware

```rust
# 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:

```rust
# 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 reuqired 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() {}
```

[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
2 changes: 1 addition & 1 deletion _stable/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The listening server acceptor can be replaced with a simple loop.
Previously, hyper depended on `tower` for the `Service` trait. Because
`tower` is not yet 1.0, hyper could not publicly depend on it. So, it's
`Service` trait and the helper function `service_fn` are defined in
`hyper::service`.
`hyper::service`. Check [middleware](https://hyper.rs/guides/1/server/middleware/) for more details.

[changelog]: https://github.com/hyperium/hyper/blob/master/CHANGELOG.md#v100-2023-11-15
[`hyper-util`]: https://crates.io/crates/hyper-util
Expand Down
Loading