Request metadata extractors
Read URL, path, header, and connection metadata — Query, Path, Params, HeaderMap, Accept, AcceptLanguage, Range, and IpAddr.
Request metadata extractors
These extractors read the URL, path captures, headers, and connection
info — never the body. They implement
FromRequestParts,
so any number of them can be combined in one handler, and they run before
a body extractor.
Query<T>
Deserializes the URL query string into T (any serde::Deserialize).
Optional fields map naturally to Option<_>.
use serde::Deserialize;
use tako::extractors::query::Query;
#[derive(Deserialize)]
struct SearchQuery {
q: String,
page: Option<u32>,
limit: Option<u32>,
}
async fn search(Query(query): Query<SearchQuery>) -> String {
let page = query.page.unwrap_or(1);
format!("searching '{}' (page {page})", query.q)
}QueryError distinguishes MissingQueryString, ParseError, and
DeserializationError. For query strings with repeated keys (?tag=a&tag=b)
reach for QueryMulti<T>.
Path<T>
Typed route path parameters (axum parity). T may be a single primitive
(Path<u64>), a tuple (Path<(u64, String)>), a Vec<_> for repeated
captures, an Option<_> (None when nothing matched), or a struct
deriving serde::Deserialize.
use serde::Deserialize;
use tako::extractors::path::Path;
use tako::responder::Responder;
#[derive(Deserialize)]
struct UserPath { id: u64 }
async fn show_user(Path(p): Path<UserPath>) -> impl Responder {
format!("user_id={}", p.id)
}The route must declare the matching capture, e.g.
router.route(Method::GET, "/users/{id}", show_user) — see
Routing. For the verbatim request path (no captures, no
decoding) use RawPath.
Params<T>
The dynamic path-params extractor. Like Path<T>, it deserializes the
captured segments into T, and it is the form the #[tako::route] macro
family materializes for {name} slots.
use serde::Deserialize;
use tako::extractors::params::Params;
use tako::extractors::query::Query;
#[derive(Deserialize)]
struct UserPath { id: u64 }
#[derive(Deserialize)]
struct Pagination { page: u32, per_page: u32 }
// GET /users/{id}/posts?page=2&per_page=10
async fn list_posts(
Params(user): Params<UserPath>,
Query(p): Query<Pagination>,
) -> String {
format!("user={}, page={}, per_page={}", user.id, p.page, p.per_page)
}ParamsError is either MissingPathParams (an internal routing error) or
DeserializationError.
HeaderMap
Gives a handler the full request header set. HeaderMap(pub http::HeaderMap)
is owned, so you read values without lifetime juggling. Its error type is
Infallible.
use tako::extractors::header_map::HeaderMap;
async fn inspect(HeaderMap(headers): HeaderMap) {
if let Some(ua) = headers.get("user-agent") {
println!("user-agent: {ua:?}");
}
}For a single strongly-typed header, the typed-header feature adds
TypedHeader<H>.
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use tako::Method;
use tako::extractors::header_map::HeaderMap;
use tako::extractors::json::Json;
use tako::router::Router;
use tokio::net::TcpListener;
#[derive(Deserialize)]
struct Input {
name: String,
}
#[derive(Serialize)]
struct Output {
name: String,
user_agent: Option<String>,
}
/// POST /echo
/// Body: {"name": "Alice"}
///
/// Demonstrates using both `Json` and `HeaderMap` extractors in the handler signature.
async fn echo_with_headers(
HeaderMap(headers): HeaderMap,
Json(payload): Json<Input>,
) -> Json<Output> {
let user_agent = headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
Json(Output {
name: payload.name,
user_agent,
})
}
#[tokio::main]
async fn main() -> Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
let mut router = Router::new();
router.route(Method::POST, "/echo", echo_with_headers);
tako::serve(listener, router).await;
Ok(())
}
Accept
Parses the Accept header into a preference-sorted media-type list and
exposes content-negotiation helpers: prefers(mt), accepts(mt),
preferred(), and types(). Wildcards (*/*, image/*) are matched
correctly — image/* matches image/png but not imagezzz.
use tako::extractors::accept::Accept;
use tako::responder::Responder;
async fn negotiate(accept: Accept) -> impl Responder {
if accept.prefers("application/json") {
r#"{"message":"hello"}"#.to_string()
} else {
"hello".to_string()
}
}AcceptLanguage
Parses Accept-Language into a Vec<LanguagePreference> (each a
language tag plus a quality from 0.0 to 1.0), sorted by quality per
RFC 7231.
use tako::extractors::acc_lang::AcceptLanguage;
async fn localize(langs: AcceptLanguage) -> String {
match langs.languages.first() {
Some(top) => format!("preferred language: {}", top.language),
None => "no preference".to_string(),
}
}AcceptLanguageError distinguishes MissingHeader, InvalidHeader, and
ParseError.
Range
Parses an RFC 9110 bytes= Range header into Range { specs: Vec<RangeSpec> }.
Each RangeSpec is one of Inclusive { start, end }, From { start }, or
Suffix { length }, and RangeSpec::resolve(total_size) turns a spec into
a concrete inclusive [start, end] (or None when unsatisfiable).
Multi-range requests populate the full specs list; single-range
responders can take the first entry.
use tako::extractors::range::Range;
async fn serve_partial(range: Range) -> String {
match range.specs.first().and_then(|s| s.resolve(10_000)) {
Some((start, end)) => format!("serving bytes {start}-{end}"),
None => "range not satisfiable".to_string(),
}
}IpAddr
Extracts the client IP. By default it returns the transport-level peer
IP and ignores forwarded headers (X-Forwarded-For, X-Real-IP,
Forwarded, …), because any direct client can forge them. It exposes
inspection helpers like is_private(), is_loopback(), is_ipv4().
use tako::extractors::ipaddr::IpAddr;
async fn whoami(ip: IpAddr) -> String {
if ip.is_private() {
format!("private client: {ip}")
} else {
format!("client: {ip}")
}
}To honor forwarded headers behind a known proxy, set an IpAddrConfig
with trusted_proxies listing your load-balancer fleet via
tako_rs_core::state::set_state. Only when the direct peer is in that
list are headers consulted, in priority order (Forwarded,
X-Forwarded-For, X-Real-IP, X-Client-IP, CF-Connecting-IP,
True-Client-IP).
Related
- Route capture syntax (
{name},{*rest}, typed{name: T}) — Routing. - Body inputs — body extractors.
- Authorization headers and tokens — auth extractors.