Skip to content

Commit 2d3bfe3

Browse files
authored
guide: add server/middleware guide (#116)
1 parent 92fa178 commit 2d3bfe3

File tree

5 files changed

+257
-1
lines changed

5 files changed

+257
-1
lines changed

.github/workflows/check_guides.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ EOF
2323
tokio = { version = "1", features = ["full"] }
2424
http-body-util = "0.1"
2525
hyper-util = { version = "0.1", features = ["full"] }
26+
tower = "0.4"
2627
EOF
2728
cargo build --manifest-path "$value/Cargo.toml"
2829
fi

_config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ docs_url: https://docs.rs/hyper
5757
examples_url: https://github.com/hyperium/hyper/tree/master/examples
5858
http_body_util_url: https://docs.rs/http-body-util
5959
hyper_tls_url: https://docs.rs/hyper-tls
60+
hyper_util_url: https://docs.rs/hyper-util
6061

6162
futures_url: https://docs.rs/futures/0.3.*
6263
legacy_docs_url: https://docs.rs/hyper/0.14.*

_data/stable.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
guides:
1818
- hello-world
1919
- echo
20+
- middleware
2021

2122
- title: Client
2223
path: "/client"

_stable/server/middleware.md

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
---
2+
title: Getting Started with a Server Middleware
3+
layout: guide
4+
---
5+
6+
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.
7+
8+
Let's create a Logger middleware in [hello-world server](hello-world) for instance:
9+
10+
Add tower dependency first
11+
12+
```toml
13+
[dependencies]
14+
hyper = { version = "1", features = ["full"] }
15+
tokio = { version = "1", features = ["full"] }
16+
http-body-util = "0.1"
17+
hyper-util = { version = "0.1", features = ["full"] }
18+
tower = "0.4" # here
19+
```
20+
21+
## Option 1: Use hyper Service trait
22+
23+
Implement hyper Logger middleware
24+
25+
```rust
26+
# extern crate hyper;
27+
use hyper::{Request, body::Incoming, service::Service};
28+
29+
#[derive(Debug, Clone)]
30+
pub struct Logger<S> {
31+
inner: S,
32+
}
33+
impl<S> Logger<S> {
34+
pub fn new(inner: S) -> Self {
35+
Logger { inner }
36+
}
37+
}
38+
type Req = Request<Incoming>;
39+
40+
impl<S> Service<Req> for Logger<S>
41+
where
42+
S: Service<Req>,
43+
{
44+
type Response = S::Response;
45+
type Error = S::Error;
46+
type Future = S::Future;
47+
fn call(&self, req: Req) -> Self::Future {
48+
println!("processing request: {} {}", req.method(), req.uri().path());
49+
self.inner.call(req)
50+
}
51+
}
52+
# fn main() {}
53+
```
54+
55+
Then this can be used in server:
56+
57+
```rust
58+
# extern crate tower;
59+
# extern crate hyper;
60+
# extern crate http_body_util;
61+
# extern crate tokio;
62+
# extern crate hyper_util;
63+
# mod no_run {
64+
use std::{convert::Infallible, net::SocketAddr};
65+
use hyper::{
66+
service::Service,
67+
body::{Bytes, Incoming},
68+
server::conn::http1,
69+
Request, Response,
70+
};
71+
use http_body_util::Full;
72+
use hyper_util::rt::TokioIo;
73+
use tokio::net::TcpListener;
74+
use tower::ServiceBuilder;
75+
76+
# #[derive(Debug, Clone)]
77+
# pub struct Logger<S> {
78+
# inner: S,
79+
# }
80+
# impl<S> Logger<S> {
81+
# pub fn new(inner: S) -> Self {
82+
# Logger { inner }
83+
# }
84+
# }
85+
# type Req = Request<Incoming>;
86+
87+
# impl<S> Service<Req> for Logger<S>
88+
# where
89+
# S: Service<Req>,
90+
# {
91+
# type Response = S::Response;
92+
# type Error = S::Error;
93+
# type Future = S::Future;
94+
# fn call(&self, req: Req) -> Self::Future {
95+
# println!("processing request: {} {}", req.method(), req.uri().path());
96+
# self.inner.call(req)
97+
# }
98+
# }
99+
async fn hello(_: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
100+
Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
101+
}
102+
#[tokio::main]
103+
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
104+
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
105+
let listener = TcpListener::bind(addr).await?;
106+
loop {
107+
let (stream, _) = listener.accept().await?;
108+
let io = TokioIo::new(stream);
109+
tokio::spawn(async move {
110+
// N.B. should use hyper service_fn here, since it's required to be implemented hyper Service trait!
111+
let svc = hyper::service::service_fn(hello);
112+
let svc = ServiceBuilder::new().layer_fn(Logger::new).service(svc);
113+
if let Err(err) = http1::Builder::new().serve_connection(io, svc).await {
114+
eprintln!("server error: {}", err);
115+
}
116+
});
117+
}
118+
}
119+
# }
120+
# fn main() {}
121+
```
122+
123+
## Option 2: use hyper TowerToHyperService trait
124+
125+
[hyper_util::service::TowerToHyperService](adapter-trait) trait is an adapter to convert tower Service to hyper Service.
126+
127+
Now implement a tower Logger middleware
128+
129+
```rust
130+
# extern crate tower;
131+
# extern crate hyper;
132+
use hyper::{Request, body::Incoming};
133+
use tower::Service;
134+
135+
#[derive(Debug, Clone)]
136+
pub struct Logger<S> {
137+
inner: S,
138+
}
139+
impl<S> Logger<S> {
140+
pub fn new(inner: S) -> Self {
141+
Logger { inner }
142+
}
143+
}
144+
type Req = Request<Incoming>;
145+
impl<S> Service<Req> for Logger<S>
146+
where
147+
S: Service<Req> + Clone,
148+
{
149+
type Response = S::Response;
150+
151+
type Error = S::Error;
152+
153+
type Future = S::Future;
154+
155+
fn poll_ready(
156+
&mut self,
157+
cx: &mut std::task::Context<'_>,
158+
) -> std::task::Poll<Result<(), Self::Error>> {
159+
self.inner.poll_ready(cx)
160+
}
161+
162+
fn call(&mut self, req: Req) -> Self::Future {
163+
println!("processing request: {} {}", req.method(), req.uri().path());
164+
self.inner.call(req)
165+
}
166+
}
167+
# fn main() {}
168+
```
169+
170+
Then use it in the server:
171+
172+
```rust
173+
# extern crate hyper;
174+
# extern crate http_body_util;
175+
# extern crate hyper_util;
176+
# extern crate tokio;
177+
# extern crate tower;
178+
# mod no_run {
179+
use std::{convert::Infallible, net::SocketAddr};
180+
181+
use hyper::{
182+
body::{Bytes, Incoming},
183+
server::conn::http1,
184+
Request, Response,
185+
};
186+
187+
use http_body_util::Full;
188+
use hyper_util::{rt::TokioIo, service::TowerToHyperService};
189+
use tokio::net::TcpListener;
190+
use tower::{ServiceBuilder, Service};
191+
192+
# #[derive(Debug, Clone)]
193+
# pub struct Logger<S> {
194+
# inner: S,
195+
# }
196+
# impl<S> Logger<S> {
197+
# pub fn new(inner: S) -> Self {
198+
# Logger { inner }
199+
# }
200+
# }
201+
# type Req = Request<Incoming>;
202+
# impl<S> Service<Req> for Logger<S>
203+
# where
204+
# S: Service<Req> + Clone,
205+
# {
206+
# type Response = S::Response;
207+
208+
# type Error = S::Error;
209+
210+
# type Future = S::Future;
211+
212+
# fn poll_ready(
213+
# &mut self,
214+
# cx: &mut std::task::Context<'_>,
215+
# ) -> std::task::Poll<Result<(), Self::Error>> {
216+
# self.inner.poll_ready(cx)
217+
# }
218+
219+
# fn call(&mut self, req: Req) -> Self::Future {
220+
# println!("processing request: {} {}", req.method(), req.uri().path());
221+
# self.inner.call(req)
222+
# }
223+
# }
224+
225+
async fn hello(_: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
226+
Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
227+
}
228+
#[tokio::main]
229+
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
230+
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
231+
let listener = TcpListener::bind(addr).await?;
232+
loop {
233+
let (stream, _) = listener.accept().await?;
234+
let io = TokioIo::new(stream);
235+
tokio::spawn(async move {
236+
// N.B. should use tower service_fn here, since it's reuqired to be implemented tower Service trait before convert to hyper Service!
237+
let svc = tower::service_fn(hello);
238+
let svc = ServiceBuilder::new().layer_fn(Logger::new).service(svc);
239+
// Convert it to hyper service
240+
let svc = TowerToHyperService::new(svc);
241+
if let Err(err) = http1::Builder::new().serve_connection(io, svc).await {
242+
eprintln!("server error: {}", err);
243+
}
244+
});
245+
}
246+
}
247+
}
248+
# fn main() {}
249+
```
250+
251+
[hellp-world]: {{ site.url }}/guides/1/server/hello-world/
252+
[upgrading]: {{ site.url }}/guides/1/upgrading/
253+
[adapter-trait]: {{ site.hyper_util_url }}/latest/hyper_util/service/struct.TowerToHyperService.html

_stable/upgrading.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ The listening server acceptor can be replaced with a simple loop.
6868
Previously, hyper depended on `tower` for the `Service` trait. Because
6969
`tower` is not yet 1.0, hyper could not publicly depend on it. So, it's
7070
`Service` trait and the helper function `service_fn` are defined in
71-
`hyper::service`.
71+
`hyper::service`. Check [middleware](https://hyper.rs/guides/1/server/middleware/) for more details.
7272

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

0 commit comments

Comments
 (0)