OriginalUri is an extractor that provides the original request URI, even when the service is nested or the URI has been modified by middleware.
Basic usage
use axum::{
Router,
routing::get,
extract::OriginalUri,
http::Uri,
};
let api_routes = Router::new().route(
"/users",
get(|uri: Uri, OriginalUri(original_uri): OriginalUri| async move {
// uri is "/users"
// original_uri is "/api/users"
format!("URI: {}, Original: {}", uri, original_uri)
})
);
let app = Router::new().nest("/api", api_routes);
Difference from Uri
When using nested services, the regular Uri extractor has the nesting prefix stripped, while OriginalUri preserves it:
use axum::{Router, routing::get, extract::OriginalUri, http::Uri};
async fn show_uris(uri: Uri, OriginalUri(original): OriginalUri) -> String {
format!("Nested URI: {}\nOriginal URI: {}", uri.path(), original.path())
}
let api = Router::new().route("/users", get(show_uris));
let app = Router::new().nest("/api/v1", api);
// Request to /api/v1/users:
// Nested URI: /users
// Original URI: /api/v1/users
Using in middleware
OriginalUri can be accessed from middleware via request extensions:
use axum::{
Router,
routing::get,
extract::OriginalUri,
http::Request,
};
use tower_http::trace::TraceLayer;
let api_routes = Router::new()
.route("/users/{id}", get(|| async { /* ... */ }))
.layer(
TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
let path = if let Some(original_uri) = req.extensions().get::<OriginalUri>() {
// This will include /api prefix
original_uri.0.path().to_owned()
} else {
req.uri().path().to_owned()
};
tracing::info_span!("http-request", %path)
})
);
let app = Router::new().nest("/api", api_routes);
When to use OriginalUri
Use OriginalUri when you need the full path as it was originally requested, especially in nested services or when middleware modifies the URI.
Use cases
- Logging the full request path: Track complete URLs in nested services
- Redirects: Generate redirect URLs based on the original request
- Authentication: Check the full path against access control lists
- Middleware that modifies URIs: Access the pre-modification URI
Type signature
OriginalUri is a tuple struct containing a Uri:
The original HTTP URI from the request.
Deref behavior
OriginalUri implements Deref<Target = Uri>, so you can call Uri methods directly:
use axum::{Router, routing::get, extract::OriginalUri};
async fn handler(original: OriginalUri) -> String {
// Can call Uri methods directly
let path = original.path();
let query = original.query();
format!("Path: {}, Query: {:?}", path, query)
}
let app = Router::new().route("/", get(handler));
Rejection
OriginalUri is infallible - it will always succeed. If no OriginalUri extension exists, it falls back to the current request URI.
The OriginalUri extension is automatically added by Axum’s router for all requests. It will only be missing if another extractor or middleware has removed it.
Example: Full path logging in nested routers
use axum::{
Router,
routing::get,
extract::OriginalUri,
middleware::{self, Next},
extract::Request,
response::Response,
};
async fn log_request(req: Request, next: Next) -> Response {
if let Some(OriginalUri(uri)) = req.extensions().get::<OriginalUri>() {
println!("Incoming request: {} {}", req.method(), uri);
}
next.run(req).await
}
let v1_routes = Router::new()
.route("/users", get(|| async { "V1 users" }))
.layer(middleware::from_fn(log_request));
let v2_routes = Router::new()
.route("/users", get(|| async { "V2 users" }))
.layer(middleware::from_fn(log_request));
let app = Router::new()
.nest("/api/v1", v1_routes)
.nest("/api/v2", v2_routes);
// Request to /api/v1/users logs: "Incoming request: GET /api/v1/users"
// Without OriginalUri, it would log: "Incoming request: GET /users"
See also
- Uri - Extract the current request URI (may be modified by nesting)
- MatchedPath - Extract the route pattern that matched