-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example showing how to use hyper's low level API
If `axum::serve` doesn't work for people we can point them to this example.
- Loading branch information
1 parent
43b14a5
commit 2cf7242
Showing
3 changed files
with
142 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "example-serve-with-hyper" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
axum = { path = "../../axum" } | ||
tokio = { version = "1.0", features = ["full"] } | ||
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1"] } | ||
hyper = { version = "1.0", features = [] } | ||
tower = { version = "0.4", features = ["util"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
//! Run with | ||
//! | ||
//! ```not_rust | ||
//! cargo run -p example-serve-with-hyper | ||
//! ``` | ||
//! | ||
//! This example shows how to run axum using hyper's low level API. | ||
//! | ||
//! The [hyper-util] crate exists to provide high level utilities but its still in early stages of | ||
//! development. | ||
//! | ||
//! [hyper-util]: https://crates.io/crates/hyper-util | ||
use std::convert::Infallible; | ||
use std::net::SocketAddr; | ||
|
||
use axum::extract::ConnectInfo; | ||
use axum::{extract::Request, routing::get, Router}; | ||
use hyper::body::Incoming; | ||
use hyper_util::rt::{TokioExecutor, TokioIo}; | ||
use hyper_util::server; | ||
use tokio::net::TcpListener; | ||
use tower::{Service, ServiceExt}; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
tokio::join!(serve_plain(), serve_with_connect_info()); | ||
} | ||
|
||
async fn serve_plain() { | ||
// Create a regular axum app. | ||
let app = Router::new().route("/", get(|| async { "Hello!" })); | ||
|
||
// Create a `TcpListener` using tokio. | ||
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); | ||
|
||
// Continuously accept new connections. | ||
loop { | ||
// In this example we discard the remote address. See `fn serve_with_connect_info` for how | ||
// to expose that. | ||
let (socket, _remote_addr) = listener.accept().await.unwrap(); | ||
|
||
// We don't need to call `poll_ready` because `Router` is always ready. | ||
let tower_service = app.clone(); | ||
|
||
// Spawn a task to handle the connection. That way we can multiple connections | ||
// concurrently. | ||
tokio::spawn(async move { | ||
// Hyper has its own `AsyncRead` and `AsyncWrite` traits and doesn't use tokio. | ||
// `TokioIo` converts between them. | ||
let socket = TokioIo::new(socket); | ||
|
||
// Hyper also has its own `Service` trait and doesn't use tower. We can use | ||
// `hyper::service::service_fn` to create a hyper `Service` that calls our app through | ||
// `tower::Service::call`. | ||
let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { | ||
// We have to clone `tower_service` because hyper's `Service` uses `&self` whereas | ||
// tower's `Service` requires `&mut self`. | ||
// | ||
// We don't need to call `poll_ready` since `Router` is always ready. | ||
tower_service.clone().call(request) | ||
}); | ||
|
||
// `server::conn::auto::Builder` supports both http1 and http2. | ||
// | ||
// `TokioExecutor` tells hyper to use `tokio::spawn` to spawn tasks. | ||
if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) | ||
// `serve_connection_with_upgrades` is required for websockets. If you don't need | ||
// that you can use `serve_connection` instead. | ||
.serve_connection_with_upgrades(socket, hyper_service) | ||
.await | ||
{ | ||
eprintln!("failed to serve connection: {err:#}"); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
// Similar setup to `serve_plain` but captures the remote address and exposes it through the | ||
// `ConnectInfo` extractor | ||
async fn serve_with_connect_info() { | ||
let app = Router::new().route( | ||
"/", | ||
get( | ||
|ConnectInfo(remote_addr): ConnectInfo<SocketAddr>| async move { | ||
format!("Hello {remote_addr}") | ||
}, | ||
), | ||
); | ||
|
||
let mut make_service = app.into_make_service_with_connect_info::<SocketAddr>(); | ||
|
||
let listener = TcpListener::bind("0.0.0.0:3001").await.unwrap(); | ||
|
||
loop { | ||
let (socket, remote_addr) = listener.accept().await.unwrap(); | ||
|
||
// We don't need to call `poll_ready` because `IntoMakeServiceWithConnectInfo` is always | ||
// ready. | ||
let tower_service = unwrap_infallible(make_service.call(remote_addr).await); | ||
|
||
tokio::spawn(async move { | ||
let socket = TokioIo::new(socket); | ||
|
||
let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { | ||
tower_service.clone().oneshot(request) | ||
}); | ||
|
||
if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) | ||
.serve_connection_with_upgrades(socket, hyper_service) | ||
.await | ||
{ | ||
eprintln!("failed to serve connection: {err:#}"); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
fn unwrap_infallible<T>(result: Result<T, Infallible>) -> T { | ||
match result { | ||
Ok(value) => value, | ||
Err(err) => match err {}, | ||
} | ||
} |