Axum Server
Install required dependencies
cargo add axum
cargo add hyper --features=full
cargo add tokio --features=full
cargo add toker
Lets create a new function that will return the axum::routing::Router
. This
will be the core of our server.
// src/main.rs
fn app() -> Router {
Router::new().route("/", get(|| async { "Hello, World!" }))
}
Creating a function that returns the Router
will help us later to test our
application.
Add the tokio config to the main function.
// src/main.rs
#[tokio::main]
async fn main() {
// ...
Create the socket address we will listen on.
// src/main.rs fn main()
let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8080);
And bind the address to our application.
// src/main.rs fn main()
axum::Server::bind(&address)
.serve(app().into_make_service())
.await
.unwrap();
Lets test our server out, in one terminal let’s start the server and in another
we will use curl
to call the endpoint we just created.
# one terminal
cargo run
# the other terminal
curl 127.0.0.1:8080 # Hello, world!
Let’s add an automatic test for the /
route. Lets start by adding the test
that will create our application.
// src/main.rs
#[cfg(test)]
mod test {
use super::app;
#[tokio::test]
async fn hello_world() {
let app = app();
}
}
The #[tokio::test]
will let us create and async test. Let’s make the request
for the endpoint inside the test.
// src/main.rs mod test
// dependencies for the response
use axum::{body::Body, http::Request};
use tower::ServiceExt; // for oneshot
#[tokio::test]
async fn hello_world() {
let app = app();
let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
// ...
Finally let’s add the assertions to the test.
// src/main.rs mod test fn hello_world()
let response = ...;
assert_eq!(response.status(), StatusCode::OK);
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"Hello, World!");
Now we have a working and tested web server. Lets add some more useful utilities.
The first is color-eyre to have a better panic handler.
cargo add color_eyre
Then, we have to initialize the panic handler.
// src/main.rs
use color_eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
// ...
Now we can use the ?
in the main function instead of unwrap
for errors. You
can try to run two instances of the program at the same time to see an example
of the eyre errors.
Finally, we want to log what our server is doing. To do that we will use the tracing crate, which is already integrated with axum and eyre.
So let’s install the tracing dependencies.
cargo add tracing
cargo add tracing_subscriber --features=env-filter
cargo add tower_http --features=trace
Then, in our main.rs
after the eyre install we have to add the tracing
subscriber.
// src/main.rs fn main()
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "example_testing=debug,tower_http=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
Where example_testing
is the name of our application. This will try to parse
the env filter or use debug
as a default for either our application and
tower.
Next we add the tracing layer to the tower stack:
// src/main.rs
fn app() -> Router {
Router::new()
.route("/", get(|| async { "Hello, World!" }))
.layer(TraceLayer::new_for_http())
}
Finally we can start tracing, let’s add a debug event for the server address.
// src/main.rs fn main()
let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8080);
tracing::debug!("listing on address {}", address);