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

Set Content-type

axum provide some tools to set the response Content-type to some of the common values, and provides us ways to set the Content-type to any arbitrary string.

A few of the common Content-type values

  • text/plain
  • text/html Html
  • application/json Json
  • text/css
  • application/javascript

See axum responses. The IntoResponse trait.

[package]
name = "set-content-type"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
axum = "0.8.8"
serde_json = "1.0.149"
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"] }

Code

use axum::{
    Json, Router,
    http::{
        StatusCode,
        header::{self, HeaderMap},
    },
    response::{Html, IntoResponse},
    routing::get,
};
use std::time::{SystemTime, UNIX_EPOCH};

async fn handle_main_page() -> Html<&'static str> {
    Html(
        r#"
    <h1>Set Content-Type</h1>
    Main page is static <b>text/html</b><br>
    <a href="/static-plain-text">static <b>text/plain<b/></a><br>
    <a href="/dynamic-plain-text">dynamic <b>text/plain</b></a><br>
    <a href="/js">application/javascript</a><br>
    <a href="/css">css</a><br>
"#,
    )
}

async fn handle_static_plain_text() -> &'static str {
    "<h1>Plain text</h1>"
}

async fn handle_dynamic_plain_text() -> String {
    let now = SystemTime::now();
    let since_the_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
    format!(
        "<h1>Plain text. Time since epoch: {:?}</h1>",
        since_the_epoch
    )
}

async fn handle_json() -> Json<Vec<String>> {
    let planets = vec![
        String::from("Mercury"),
        String::from("Venus"),
        String::from("Earth"),
    ];
    Json(planets)
}

async fn send_style_css() -> impl IntoResponse {
    let css = r#"
h1 {
    color: blue;
}
    "#;
    let mut headers = HeaderMap::new();
    headers.insert(header::CONTENT_TYPE, "text/css".parse().unwrap());
    (headers, css)
}

async fn send_javascript() -> impl IntoResponse {
    let js = r#"
    alert("hi");
    "#;

    (
        StatusCode::OK, // This status code is optional and OK is the default
        [(header::CONTENT_TYPE, "application/javascript")],
        js,
    )
}

fn create_router() -> Router {
    Router::new()
        .route("/", get(handle_main_page))
        .route("/static-plain-text", get(handle_static_plain_text))
        .route("/dynamic-plain-text", get(handle_dynamic_plain_text))
        .route("/css", get(send_style_css))
        .route("/js", get(send_javascript))
        .route("/json", get(handle_json))
}

#[tokio::main]
async fn main() {
    let app = create_router();

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on http://{}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

#[cfg(test)]
mod tests;

Tests

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

use super::*;

#[tokio::test]
async fn test_main_page() {
    let response = create_router()
        .oneshot(Request::builder().uri("/").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");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert!(html.contains("<h1>Set Content-Type</h1>"));
}

#[tokio::test]
async fn test_static_plain_text() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/static-plain-text")
                .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/plain; charset=utf-8");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert_eq!(html, "<h1>Plain text</h1>");
}

#[tokio::test]
async fn test_dynamic_plain_text() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/dynamic-plain-text")
                .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/plain; charset=utf-8");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert!(html.contains("<h1>Plain text. Time since epoch: "));
}

#[tokio::test]
async fn test_css() {
    let response = create_router()
        .oneshot(Request::builder().uri("/css").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/css");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let content = String::from_utf8(bytes.to_vec()).unwrap();

    let expected_css = r#"
h1 {
    color: blue;
}
    "#;

    assert_eq!(content, expected_css);
}

#[tokio::test]
async fn test_js() {
    let response = create_router()
        .oneshot(Request::builder().uri("/js").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(), "application/javascript");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let content = String::from_utf8(bytes.to_vec()).unwrap();

    let expected_js = r#"
    alert("hi");
    "#;

    assert_eq!(content, expected_js);
}

#[tokio::test]
async fn test_json() {
    let response = create_router()
        .oneshot(Request::builder().uri("/json").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(), "application/json");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let content: Vec<String> = serde_json::from_slice(&bytes).unwrap();

    let expected = vec![
        String::from("Mercury"),
        String::from("Venus"),
        String::from("Earth"),
    ];

    assert_eq!(content, expected);
}
}