skip to content
logo 夕舞八弦

Fixing charset issue with tower_http ServeDir in Axum

/ 1 min read

Table of Contents

TL;DR

tower_http’s ServeDir doesn’t automatically add charset to text/plain responses. You need to override the Content-Type header to include charset=utf-8 for proper text file handling.


The Problem

When using tower_http::services::ServeDir to serve static files in a Rust Axum web server, I noticed that text files were being served without the proper charset declaration in the Content-Type header. This can cause issues with text encoding in browsers.

// This serves files but doesn't set charset for text files
let service = ServeDir::new("./static");

The response headers for text files look like:

Content-Type: text/plain

Instead of the preferred:

Content-Type: text/plain; charset=utf-8

The Solution

To fix this, we need to create a custom layer that checks if the response is text/plain and adds the charset if needed.

use axum::{
http::{header::CONTENT_TYPE, HeaderValue, Response},
routing::{get_service, MethodRouter},
};
use std::{path::PathBuf, time::Duration};
use tower::ServiceBuilder;
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer};
fn set_text_plain_charset<B>(response: &Response<B>) -> Option<HeaderValue> {
response
.headers()
.get(CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.filter(|ct| ct.starts_with("text/plain"))
.map(|_| HeaderValue::from_static("text/plain; charset=utf-8"))
}
pub fn serve_dir_with_charset(path: PathBuf) -> MethodRouter {
let header_layer = SetResponseHeaderLayer::overriding(CONTENT_TYPE, set_text_plain_charset);
get_service(
ServiceBuilder::new()
.layer(header_layer)
.service(ServeDir::new(path)),
)
}

Usage Example

Here’s how to use it in your Axum application:

use axum::{routing::get, Router};
use tower_http::services::ServeDir;
let app = Router::new()
.nest_service("/static", serve_dir_with_charset(PathBuf::from("./static")));