Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Special handling for HEAD requests

For ever GET route axum automatically installs the same HEAD route.

However we can create special handling for HEAD requests.

It rarely needed.

[package]
name = "head-request"
version = "0.1.0"
edition = "2024"
publish = false


[dependencies]
axum = "0.8.8"
tokio = { version = "1.50.0", features = ["full"] }

[dev-dependencies]
headers = "0.4.1"
http-body-util = "0.1.3"
tower = { version = "0.5.3", features = ["util"] }
use axum::response::{Html, IntoResponse, Response};
use axum::{Router, http, routing::get, routing::head};

async fn main_page() -> Response {
    let content = String::from(
        r#"
        <h1>HEAD</h1>
    "#,
    );
    ([("x-my-header", "header of main page")], Html(content)).into_response()
}

async fn just_head() -> Response {
    ([("x-my-header", "header from HEAD of just_head")]).into_response()
}

async fn get_head_handler(method: http::Method) -> Response {
    if method == http::Method::HEAD {
        return ([("x-my-header", "header for HEAD get_head_handler")]).into_response();
    }

    println!("do some heavy computing task in GET");

    (
        [("x-my-header", "header for GET get_head_handler")],
        Html("The content"),
    )
        .into_response()
}

fn create_route() -> Router {
    Router::new()
        .route("/", get(main_page))
        .route("/just-head", head(just_head))
        .route("/get-head", get(get_head_handler))
}

#[tokio::main]
async fn main() {
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, create_route()).await.unwrap();
}

#[cfg(test)]
mod tests;
#![allow(unused)]
fn main() {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;

#[tokio::test]
async fn test_get_main_page() {
    let app = create_route();

    let response = app
        .oneshot(Request::get("/").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/html; charset=utf-8");

    assert_eq!(
        response.headers().get("x-my-header").unwrap(),
        "header of main page"
    );

    let bytes = response.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();
    assert!(html.contains("<h1>HEAD</h1>"));
}

#[tokio::test]
async fn test_head_main_page() {
    let app = create_route();

    let response = app
        .oneshot(Request::head("/").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/html; charset=utf-8");

    assert_eq!(
        response.headers().get("x-my-header").unwrap(),
        "header of main page"
    );

    let bytes = response.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();
    assert_eq!(html, "");
}

#[tokio::test]
async fn test_get() {
    let app = create_route();

    let response = app
        .oneshot(Request::get("/get-head").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(
        response.headers()["x-my-header"],
        "header for GET get_head_handler"
    );

    let bytes = response.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();
    assert_eq!(html, "The content");
}

#[tokio::test]
async fn test_head() {
    let app = create_route();

    let response = app
        .oneshot(Request::head("/get-head").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(
        response.headers()["x-my-header"],
        "header for HEAD get_head_handler"
    );

    let body = response.collect().await.unwrap().to_bytes();
    assert!(body.is_empty());
}

#[tokio::test]
async fn test_head_just_head() {
    let app = create_route();

    let response = app
        .oneshot(Request::head("/just-head").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(
        response.headers()["x-my-header"],
        "header from HEAD of just_head"
    );

    let body = response.collect().await.unwrap().to_bytes();
    assert!(body.is_empty());
}

#[tokio::test]
async fn test_get_just_head() {
    let app = create_route();

    let response = app
        .oneshot(Request::get("/just-head").body(Body::empty()).unwrap())
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);

    let body = response.collect().await.unwrap().to_bytes();
    assert!(body.is_empty());
}
}