rune_axum_redirect_https/lib.rs
1//! HTTP-to-HTTPS redirect middleware for Axum and Tower.
2//!
3//! A [`tower::Layer`] that detects incoming HTTP requests and issues a
4//! permanent redirect to the HTTPS equivalent. HTTPS requests are forwarded
5//! to the inner service unchanged.
6//!
7//! Detection follows this priority order:
8//!
9//! 1. `X-Forwarded-Proto: http` — set by most reverse proxies when the
10//! upstream connection is plain HTTP.
11//! 2. URI scheme of `http` — present when the request line carries an
12//! absolute URI. Plain HTTP/1.1 requests served by `axum::serve` use the
13//! origin form (`GET /path`) and have no scheme, so without a proxy that
14//! sets `X-Forwarded-Proto` most requests are not detected.
15//!
16//! If neither indicator is present the request passes through unchanged,
17//! which is the safe default when the protocol cannot be determined.
18//!
19//! # Features
20//!
21//! - Configurable redirect status: `308 Permanent Redirect` (default,
22//! method-preserving) or `301 Moved Permanently` (legacy compatibility).
23//! - Optional HTTPS port override for non-standard port setups.
24//! - Graceful fallback: if no `Host` header is present the request is
25//! forwarded rather than returning a malformed redirect without `Location`.
26//! - A `Host` header that is not a plain host name or IP literal with an
27//! optional port gets `400 Bad Request`, so a forged host never turns into
28//! an open redirect.
29//!
30//! # Quick Start
31//!
32//! ```rust
33//! use axum::{routing::get, Router};
34//! use rune_axum_redirect_https::RedirectHttpsLayer;
35//!
36//! let app: Router = Router::new()
37//! .route("/", get(|| async { "hello" }))
38//! .layer(RedirectHttpsLayer::default());
39//! ```
40//!
41//! # Custom Configuration
42//!
43//! ```rust
44//! use axum::{routing::get, Router};
45//! use http::StatusCode;
46//! use rune_axum_redirect_https::{RedirectHttps, RedirectHttpsLayer};
47//!
48//! // HTTP on :8080 → HTTPS on :8443, using 301 for legacy clients
49//! let layer = RedirectHttpsLayer::new(
50//! RedirectHttps::new()
51//! .status(StatusCode::MOVED_PERMANENTLY)
52//! .https_port(8443),
53//! );
54//!
55//! let app: Router = Router::new()
56//! .route("/", get(|| async { "hello" }))
57//! .layer(layer);
58//! ```
59
60pub use layer::{RedirectHttps, RedirectHttpsLayer};
61
62mod layer;