⬆⬇ Taming Redirects and Reloads: An Axum Adventure on Fly.io 🔃
Deploying web applications often involves ensuring traffic flows through custom domains. This post details configuring redirects for an Axum-based application on Fly.io, tackling a peculiar Chromium reload issue, and navigating a deployment snag.
Part 1: The Great Redirect - Forcing the Custom Domain
The goal: redirect your-app-name.fly.dev
to https://example.com
, preserving paths and queries. Axum middleware is perfect for this.
// src/main.rs
use axum::{
extract::Host, http::{Request, Uri}, middleware::Next,
response::{IntoResponse, Response, Redirect},
};
async fn redirect_fly_to_custom_domain(
Host(host): Host,
uri: Uri,
request: Request<axum::body::Body>,
next: Next,
) -> Response {
if host.contains("fly.dev") {
let path = uri.path();
let query = uri.query().map(|q| format!("?{}", q)).unwrap_or_default();
let redirect_url = format!("https://example.com{}{}", path, query);
tracing::info!("Redirecting from {} to {}", host, redirect_url);
return Redirect::permanent(&redirect_url).into_response();
}
next.run(request).await
}
Key Learning 1: Middleware Order is King!
Ensure redirect middleware runs before others like authentication for efficiency.
// src/main.rs (Router setup)
// ...
.layer(middleware::from_fn(redirect_fly_to_custom_domain)) // Redirect first!
.layer(middleware::from_fn_with_state(app_state.clone(), auth_middleware)) // Then auth
// ...
Key Learning 2: Don't Interfere with Health Checks!
Fly.io health checks might use the *.fly.dev
domain. Exclude health check paths (e.g., /health
) from redirects.
// src/main.rs (redirect_fly_to_custom_domain function)
// ...
let path = uri.path();
if host.contains("fly.dev") && !path.starts_with("/health") { // Added health check exclusion
// ... redirect logic ...
}
// ...
Part 2: The Mysterious Case of the Chromium Reload & The Graceful Fix
A strange issue can arise: reloading pages in Chromium browsers causes hangs. Firefox might be fine. This hints at connection handling, possibly related to abrupt terminations during reloads combined with redirects.
The solution: Graceful Shutdown for the Axum server. This allows ongoing requests to complete before the server stops, preventing abrupt disconnections.
// src/main.rs
// Helper function to listen for a shutdown signal (Ctrl+C)
async fn shutdown_signal() {
tokio::signal::ctrl_c().await.expect("Failed to install CTRL+C handler");
tracing::info!("Shutdown signal received, starting graceful shutdown");
}
#[tokio::main]
async fn main() {
// ... (existing setup) ...
let app = /* ... router definition ... */
.layer(middleware::from_fn(redirect_fly_to_custom_domain))
// ... other layers ...
// ... (addr setup) ...
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>()
)
.with_graceful_shutdown(shutdown_signal()) // Crucial addition!
.await
.unwrap();
}
This can resolve Chromium reload issues by ensuring cleaner connection handling during server restarts (like Fly.io deployments).
Conclusion
This exploration highlighted several key aspects of web server development:
- Middleware Order: Impacts efficiency and correctness.
- Operational Needs: Account for platform features like health checks.
- Graceful Shutdown: Essential for server stability and a smooth client experience, especially for resolving issues like the Chromium reload problem.
These steps help ensure an application redirects reliably, handles reloads smoothly, and deploys cleanly.