pub struct Call { /* private fields */ }Expand description
Per-request context: the single object a handler receives (Ktor-style).
A Call bundles the request method, URI, headers, captured path
parameters, the (buffered) body, a handle to shared application
state, and a per-call extension map. Handlers receive it
either directly (|c: Call| async { ... }) or indirectly through
extractors such as Path and
Query, which read from the Call for you.
Read-only accessors (method, uri,
path, header, query,
param) borrow &self; body consumption
(receive_bytes, receive_text)
takes &mut self. The insert/get pair
passes typed values between middleware and downstream handlers.
Construct one with Call::new in unit tests; in production the engine and
the TestClient build it for you.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let call = Call::new(
Method::GET,
"/search?q=rust".parse().unwrap(),
HeaderMap::new(),
Bytes::new(),
);
assert_eq!(call.method(), &Method::GET);
assert_eq!(call.path(), "/search");
assert_eq!(call.query("q").as_deref(), Some("rust"));Implementations§
Source§impl Call
impl Call
Sourcepub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self
pub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self
Construct a Call from already-parsed request parts (used by the engine and the test harness alike). Build a call whose body is already in memory.
The engine instead attaches the body as a stream, so a handler can read
a large one incrementally — see Call::body_stream.
Sourcepub fn host(&self) -> Option<String>
pub fn host(&self) -> Option<String>
The request’s host, without the port.
Reads the URI authority first, falling back to the Host header. Both
spellings matter: HTTP/1.1 in the ordinary origin form carries the host
in a header and nowhere else, while HTTP/2 removed that header in favour
of the :authority pseudo-header, which lands in the URI. Anything that
makes a decision about which site a request is for must consult both,
or it silently stops matching on the protocol most clients now
negotiate.
When a request carries both — an HTTP/2 peer that appends a stray host
field beside :authority, or an HTTP/1.1 absolute-form target, where
Host is still mandatory — the authority wins, as RFC 9113 §8.3.1 and
RFC 9112 §3.2.2 both require.
use churust_core::Call;
use http::{HeaderMap, HeaderValue, Method, header::HOST};
use bytes::Bytes;
let mut headers = HeaderMap::new();
headers.insert(HOST, HeaderValue::from_static("example.com:8443"));
let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
assert_eq!(call.host().as_deref(), Some("example.com"));
// HTTP/2: no Host header, authority in the URI.
let h2 = Call::new(
Method::GET,
"https://example.com/".parse().unwrap(),
HeaderMap::new(),
Bytes::new(),
);
assert_eq!(h2.host().as_deref(), Some("example.com"));Sourcepub fn set_uri(&mut self, uri: Uri)
pub fn set_uri(&mut self, uri: Uri)
Replace the request URI.
For middleware that rewrites the target — a path normaliser, a rewrite rule. Note that routing has already happened by the time middleware runs, so this changes what handlers and extractors read, not which handler was selected.
Sourcepub fn headers_mut(&mut self) -> &mut HeaderMap
pub fn headers_mut(&mut self) -> &mut HeaderMap
Mutable access to the request headers.
Middleware that rewrites the request head needs this — a request-id layer that injects a header, or a proxy-header normaliser. Handlers see whatever the pipeline left here, so a middleware that edits headers is editing what every extractor downstream will read.
Sourcepub fn header(&self, name: &str) -> Option<&str>
pub fn header(&self, name: &str) -> Option<&str>
The value of header name as a UTF-8 string, or None if the header is
absent or its value is not valid UTF-8. Header name matching is
case-insensitive.
use churust_core::Call;
use http::{HeaderMap, Method, header::ACCEPT, HeaderValue};
use bytes::Bytes;
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
assert_eq!(call.header("accept"), Some("application/json"));
assert_eq!(call.header("x-missing"), None);Sourcepub fn query_string(&self) -> &str
pub fn query_string(&self) -> &str
The raw query string with the leading ? stripped, or "" if there is
none. To deserialize the whole query into a struct, prefer the
Query extractor.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let call = Call::new(Method::GET, "/s?a=1&b=2".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.query_string(), "a=1&b=2");Sourcepub fn query(&self, key: &str) -> Option<String>
pub fn query(&self, key: &str) -> Option<String>
The first value for query key key, percent- and +-decoded, or None
if the key is absent.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let call = Call::new(Method::GET, "/s?q=hello+world".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.query("q").as_deref(), Some("hello world"));
assert_eq!(call.query("missing"), None);Sourcepub fn peer_addr(&self) -> Option<SocketAddr>
pub fn peer_addr(&self) -> Option<SocketAddr>
The address the connection came from.
None when the call did not come from the engine — a TestClient
request, for instance, has no socket behind it.
This is the socket peer. Behind a reverse proxy it is the proxy, not
the client: consult X-Forwarded-For only after checking this against
the addresses you actually trust, since the header is client-supplied
and trivially forged.
The value of request cookie name, percent-decoded.
use churust_core::Call;
use http::{HeaderMap, HeaderValue, Method};
use bytes::Bytes;
let mut headers = HeaderMap::new();
headers.insert(http::header::COOKIE, HeaderValue::from_static("a=1; b=two"));
let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
assert_eq!(call.cookie("b").as_deref(), Some("two"));
assert_eq!(call.cookie("missing"), None);Sourcepub fn state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>>
pub fn state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>>
A shared handle to application state of type T, or None if no value
of that type was registered with
AppBuilder::state. For handler arguments,
the State extractor is usually more convenient.
use churust_core::{Churust, Call, TestClient};
#[derive(Clone)]
struct AppName(&'static str);
let app = Churust::server()
.state(AppName("churust"))
.routing(|r| {
r.get("/", |c: Call| async move {
format!("app={}", c.state::<AppName>().unwrap().0)
});
})
.build();
let res = TestClient::new(app).get("/").send().await;
assert_eq!(res.text(), "app=churust");Sourcepub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)>
pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)>
Iterate over the captured path parameters as (name, value) pairs, in
capture order.
Pairs come back in the order the route captured them.
Sourcepub fn param_raw(&self, name: &str) -> Option<&str>
pub fn param_raw(&self, name: &str) -> Option<&str>
The raw, unparsed value of path parameter name, or None if the route
has no such parameter. Use param to parse it into a
typed value.
Sourcepub fn param<T>(&self, name: &str) -> Result<T>
pub fn param<T>(&self, name: &str) -> Result<T>
Parse path parameter name into T.
Returns a 400 Bad Request Error if the parameter is
missing or fails to parse.
use churust_core::{Churust, Call, TestClient};
let app = Churust::server()
.routing(|r| {
r.get("/users/{id}", |c: Call| async move {
let id: u64 = c.param("id")?;
Ok::<_, churust_core::Error>(format!("user {id}"))
});
})
.build();
let res = TestClient::new(app).get("/users/42").send().await;
assert_eq!(res.text(), "user 42");Sourcepub async fn receive_bytes(&mut self) -> Bytes
pub async fn receive_bytes(&mut self) -> Bytes
Take the request body as raw Bytes, leaving the call’s body empty.
This consumes the body: a second call returns an empty buffer.
§Prefer try_receive_bytes
An empty return does not mean an empty body. The body now arrives as
a stream, and this method has no error channel, so a read that fails —
most importantly one that exceeds max_body_bytes — is reported as zero
bytes. A handler built on this answers 200 with whatever an empty body
produces, where the caller should have seen 413 Payload Too Large.
Use try_receive_bytes and let ? turn the
failure into the right status. This method is kept for the case where
the distinction genuinely does not matter.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("data"));
assert_eq!(&call.receive_bytes().await[..], b"data");
assert!(call.receive_bytes().await.is_empty()); // already consumedSourcepub async fn try_receive_bytes(&mut self) -> Result<Bytes>
pub async fn try_receive_bytes(&mut self) -> Result<Bytes>
Read the whole body, surfacing the size limit as an error.
receive_bytes exists for callers that cannot
report failure and yields an empty buffer instead; extractors should
prefer this so an oversized body becomes 413 rather than a confusing
deserialization error.
Sourcepub async fn try_receive_bytes_within(&mut self, max: usize) -> Result<Bytes>
pub async fn try_receive_bytes_within(&mut self, max: usize) -> Result<Bytes>
Collect the body, refusing it the moment it exceeds max.
The distinction from checking afterwards is memory, not status. A body
gathered first and measured second has already been allocated: a 16 MiB
upload against a 64 KiB route cap peaked above 32 MiB — BytesMut
doubles — before anything refused it, and N concurrent requests
multiplied that. The 413 was correct and arrived far too late to be
the protection it looked like.
Errors with 413 Payload Too Large as soon as the accumulated length
crosses max, so the peak is bounded by the cap rather than by what the
client chose to send.
Sourcepub fn body_stream(&mut self) -> Option<BodyStream>
pub fn body_stream(&mut self) -> Option<BodyStream>
Take the body as a stream, without buffering it.
This is how a large upload is processed without holding it in memory.
Returns None if the body has already been consumed. A body that
arrived buffered — from Call::new or a test client — yields a
single-chunk stream, so a handler need not care which it got.
The server-wide max_body_bytes still applies: exceeding it surfaces as
an error item in the stream rather than a 413, because the response
has usually begun by then.
Sourcepub async fn receive_text(&mut self) -> Result<String>
pub async fn receive_text(&mut self) -> Result<String>
Take the request body decoded as UTF-8, consuming it.
Returns a 400 Bad Request Error if the body is not
valid UTF-8.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("ping"));
assert_eq!(call.receive_text().await.unwrap(), "ping");Sourcepub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T)
pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T)
Insert a per-call typed value into the call’s extension map, keyed by its
type. Typically used by a middleware to attach context (e.g. an
authenticated principal) that a downstream extractor or handler reads via
get. Inserting a second value of the same type replaces
the first.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
#[derive(Clone, PartialEq, Debug)]
struct UserId(u32);
let mut call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
call.insert(UserId(7));
assert_eq!(call.get::<UserId>(), Some(UserId(7)));Sourcepub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T>
pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T>
Get a clone of the per-call value of type T previously stored with
insert, or None if none was stored.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.get::<u32>(), None);Sourcepub fn respond_text(&self, body: impl Into<String>) -> Response
pub fn respond_text(&self, body: impl Into<String>) -> Response
Build a 200 OK text/plain Response — a shorthand for
Response::text.
use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
let res = call.respond_text("ok");
assert_eq!(res.body.as_slice(), Some(&b"ok"[..]));Sourcepub fn respond_status(&self, status: StatusCode) -> Response
pub fn respond_status(&self, status: StatusCode) -> Response
Build an empty-bodied Response with the given status — a shorthand
for Response::new.
use churust_core::Call;
use http::{HeaderMap, Method, StatusCode};
use bytes::Bytes;
let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.respond_status(StatusCode::ACCEPTED).status, StatusCode::ACCEPTED);