churust_core/call.rs
1//! The per-request [`Call`] context — the single object every handler receives.
2
3use crate::error::{Error, Result};
4use crate::response::Response;
5use crate::state::StateMap;
6use bytes::Bytes;
7use http::{HeaderMap, Method, StatusCode, Uri};
8use std::sync::Arc;
9/// Captured path parameters, in the order the route captured them.
10///
11/// Order matters: `Path<(A, B)>` destructures positionally, so a `HashMap`
12/// would make `/users/{id}/posts/{post}` ambiguous.
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct Params(Vec<(String, String)>);
15
16impl Params {
17 /// An empty set.
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 /// Add or replace `name`.
23 pub fn insert(&mut self, name: String, value: String) {
24 match self.0.iter_mut().find(|(k, _)| *k == name) {
25 Some(slot) => slot.1 = value,
26 None => self.0.push((name, value)),
27 }
28 }
29
30 /// Drop `name`, if present.
31 pub fn remove(&mut self, name: &str) {
32 self.0.retain(|(k, _)| k != name);
33 }
34
35 /// The value for `name`.
36 pub fn get(&self, name: &str) -> Option<&str> {
37 self.0
38 .iter()
39 .find(|(k, _)| k == name)
40 .map(|(_, v)| v.as_str())
41 }
42
43 /// Pairs in capture order.
44 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
45 self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
46 }
47
48 /// Whether `name` was captured.
49 pub fn contains_key(&self, name: &str) -> bool {
50 self.get(name).is_some()
51 }
52
53 /// The value at `index`, in capture order.
54 pub fn nth(&self, index: usize) -> Option<&str> {
55 self.0.get(index).map(|(_, v)| v.as_str())
56 }
57
58 /// How many were captured.
59 pub fn len(&self) -> usize {
60 self.0.len()
61 }
62
63 /// Whether none were captured.
64 pub fn is_empty(&self) -> bool {
65 self.0.is_empty()
66 }
67
68 /// Discard every capture.
69 pub fn clear(&mut self) {
70 self.0.clear();
71 }
72}
73
74/// The address the connection came from, seeded by the engine.
75///
76/// A newtype rather than a bare `SocketAddr` so it can live in the call's typed
77/// extension map without colliding with anything else of that type.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct PeerAddr(pub std::net::SocketAddr);
80
81/// A request body still arriving, as a stream of chunks.
82pub type BodyStream =
83 std::pin::Pin<Box<dyn futures_util::Stream<Item = Result<Bytes>> + Send + 'static>>;
84
85/// Where a request body currently is: already in memory, or still arriving.
86enum RequestBody {
87 Buffered(Bytes),
88 /// Behind a mutex so `Call` stays `Sync`. A boxed stream is `Send` but not
89 /// `Sync`, and handler bounds require `Sync`; the mutex is uncontended
90 /// because the stream is taken exactly once.
91 Stream(std::sync::Mutex<Option<BodyStream>>),
92}
93
94impl std::fmt::Debug for RequestBody {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 RequestBody::Buffered(b) => write!(f, "Buffered({} bytes)", b.len()),
98 RequestBody::Stream(_) => f.write_str("Stream"),
99 }
100 }
101}
102
103/// Per-request context: the single object a handler receives (Ktor-style).
104///
105/// A `Call` bundles the request method, URI, headers, captured path
106/// parameters, the (buffered) body, a handle to shared application
107/// [state](crate::StateMap), and a per-call extension map. Handlers receive it
108/// either directly (`|c: Call| async { ... }`) or indirectly through
109/// [extractors](crate::extract) such as [`Path`](crate::Path) and
110/// [`Query`](crate::Query), which read from the `Call` for you.
111///
112/// Read-only accessors ([`method`](Call::method), [`uri`](Call::uri),
113/// [`path`](Call::path), [`header`](Call::header), [`query`](Call::query),
114/// [`param`](Call::param)) borrow `&self`; body consumption
115/// ([`receive_bytes`](Call::receive_bytes), [`receive_text`](Call::receive_text))
116/// takes `&mut self`. The [`insert`](Call::insert)/[`get`](Call::get) pair
117/// passes typed values between middleware and downstream handlers.
118///
119/// Construct one with [`Call::new`] in unit tests; in production the engine and
120/// the [`TestClient`](crate::TestClient) build it for you.
121///
122/// ```
123/// use churust_core::Call;
124/// use http::{HeaderMap, Method};
125/// use bytes::Bytes;
126///
127/// let call = Call::new(
128/// Method::GET,
129/// "/search?q=rust".parse().unwrap(),
130/// HeaderMap::new(),
131/// Bytes::new(),
132/// );
133/// assert_eq!(call.method(), &Method::GET);
134/// assert_eq!(call.path(), "/search");
135/// assert_eq!(call.query("q").as_deref(), Some("rust"));
136/// ```
137#[derive(Debug)]
138pub struct Call {
139 method: Method,
140 uri: Uri,
141 headers: HeaderMap,
142 params: Params,
143 body: RequestBody,
144 state: Arc<StateMap>,
145 extensions: http::Extensions,
146}
147
148impl Call {
149 /// Construct a Call from already-parsed request parts (used by the engine
150 /// and the test harness alike).
151 /// Build a call whose body is already in memory.
152 ///
153 /// The engine instead attaches the body as a stream, so a handler can read
154 /// a large one incrementally — see [`Call::body_stream`].
155 pub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self {
156 Self {
157 method,
158 uri,
159 headers,
160 params: Params::new(),
161 body: RequestBody::Buffered(body),
162 state: Arc::new(StateMap::default()),
163 extensions: http::Extensions::new(),
164 }
165 }
166
167 /// The request HTTP method.
168 pub fn method(&self) -> &Method {
169 &self.method
170 }
171 /// The full request URI (path, query, and any authority).
172 pub fn uri(&self) -> &Uri {
173 &self.uri
174 }
175 /// The request path, without the query string (e.g. `/users/42`).
176 pub fn path(&self) -> &str {
177 self.uri.path()
178 }
179 /// The full request header map.
180 pub fn headers(&self) -> &HeaderMap {
181 &self.headers
182 }
183
184 /// The request's host, without the port.
185 ///
186 /// Reads the URI authority first, falling back to the `Host` header. Both
187 /// spellings matter: HTTP/1.1 in the ordinary origin form carries the host
188 /// in a header and nowhere else, while HTTP/2 removed that header in favour
189 /// of the `:authority` pseudo-header, which lands in the URI. Anything that
190 /// makes a decision about *which site* a request is for must consult both,
191 /// or it silently stops matching on the protocol most clients now
192 /// negotiate.
193 ///
194 /// When a request carries both — an HTTP/2 peer that appends a stray `host`
195 /// field beside `:authority`, or an HTTP/1.1 absolute-form target, where
196 /// `Host` is still mandatory — the authority wins, as RFC 9113 §8.3.1 and
197 /// RFC 9112 §3.2.2 both require.
198 ///
199 /// ```
200 /// use churust_core::Call;
201 /// use http::{HeaderMap, HeaderValue, Method, header::HOST};
202 /// use bytes::Bytes;
203 ///
204 /// let mut headers = HeaderMap::new();
205 /// headers.insert(HOST, HeaderValue::from_static("example.com:8443"));
206 /// let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
207 /// assert_eq!(call.host().as_deref(), Some("example.com"));
208 ///
209 /// // HTTP/2: no Host header, authority in the URI.
210 /// let h2 = Call::new(
211 /// Method::GET,
212 /// "https://example.com/".parse().unwrap(),
213 /// HeaderMap::new(),
214 /// Bytes::new(),
215 /// );
216 /// assert_eq!(h2.host().as_deref(), Some("example.com"));
217 /// ```
218 pub fn host(&self) -> Option<String> {
219 // The authority is consulted first and the header only when the URI has
220 // none. Reading the header first meant a request that carried both was
221 // resolved against the one the wire protocol does *not* treat as the
222 // target: hyper builds the URI authority out of `:authority` alone, and
223 // h2 forwards an ordinary `host` field untouched because it is not one
224 // of the connection-specific fields it rejects, so an HTTP/2 peer can
225 // send `:authority: www.example.com` beside `host: admin.example.com`
226 // and an HTTP/1.1 peer can do the same with an absolute-form target.
227 // Both RFC 9113 §8.3.1 and RFC 9112 §3.2.2 settle that the same way —
228 // the authority is the target and the header is to be ignored — and
229 // agreeing with them is what keeps an intermediary that routed or
230 // authorized on the authority and the origin behind it from resolving
231 // one request to two different sites. On the origin-form HTTP/1.1
232 // request, which is the overwhelmingly common shape, the URI has no
233 // authority and nothing changes: the header is still the only signal.
234 //
235 // A disagreement is not refused outright, only decided. Answering `400`
236 // would be within the letter of RFC 9113, but the reading above already
237 // makes the stray field inert, and rejecting would turn a request that
238 // a lenient intermediary produced by accident into an outage for a
239 // caller who did nothing wrong.
240 let raw = self
241 .uri
242 .authority()
243 .map(|a| a.as_str().to_string())
244 .or_else(|| self.header(http::header::HOST.as_str()).map(str::to_string))?;
245 // Strip any userinfo first.
246 let after_at = raw.rsplit('@').next().unwrap_or(&raw);
247 // An IPv6 literal is bracketed and full of colons, so the port cannot
248 // be found by splitting on `:` — that yielded `"[2001"` and made every
249 // host comparison fail for v6. The brackets are authority syntax, so
250 // they come off with the port.
251 let host = match after_at.strip_prefix('[') {
252 Some(rest) => rest.split(']').next().unwrap_or(rest),
253 None => after_at.split(':').next().unwrap_or(after_at),
254 };
255 (!host.is_empty()).then(|| host.to_string())
256 }
257
258 /// Replace the request URI.
259 ///
260 /// For middleware that rewrites the target — a path normaliser, a rewrite
261 /// rule. Note that routing has already happened by the time middleware
262 /// runs, so this changes what handlers and extractors *read*, not which
263 /// handler was selected.
264 pub fn set_uri(&mut self, uri: Uri) {
265 self.uri = uri;
266 }
267
268 /// Mutable access to the request headers.
269 ///
270 /// Middleware that rewrites the request head needs this — a request-id
271 /// layer that injects a header, or a proxy-header normaliser. Handlers see
272 /// whatever the pipeline left here, so a middleware that edits headers is
273 /// editing what every extractor downstream will read.
274 pub fn headers_mut(&mut self) -> &mut HeaderMap {
275 &mut self.headers
276 }
277
278 /// The value of header `name` as a UTF-8 string, or `None` if the header is
279 /// absent or its value is not valid UTF-8. Header name matching is
280 /// case-insensitive.
281 ///
282 /// ```
283 /// use churust_core::Call;
284 /// use http::{HeaderMap, Method, header::ACCEPT, HeaderValue};
285 /// use bytes::Bytes;
286 ///
287 /// let mut headers = HeaderMap::new();
288 /// headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
289 /// let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
290 /// assert_eq!(call.header("accept"), Some("application/json"));
291 /// assert_eq!(call.header("x-missing"), None);
292 /// ```
293 pub fn header(&self, name: &str) -> Option<&str> {
294 self.headers.get(name).and_then(|v| v.to_str().ok())
295 }
296
297 /// The raw query string with the leading `?` stripped, or `""` if there is
298 /// none. To deserialize the whole query into a struct, prefer the
299 /// [`Query`](crate::Query) extractor.
300 ///
301 /// ```
302 /// use churust_core::Call;
303 /// use http::{HeaderMap, Method};
304 /// use bytes::Bytes;
305 ///
306 /// let call = Call::new(Method::GET, "/s?a=1&b=2".parse().unwrap(), HeaderMap::new(), Bytes::new());
307 /// assert_eq!(call.query_string(), "a=1&b=2");
308 /// ```
309 pub fn query_string(&self) -> &str {
310 self.uri.query().unwrap_or("")
311 }
312
313 /// The first value for query key `key`, percent- and `+`-decoded, or `None`
314 /// if the key is absent.
315 ///
316 /// ```
317 /// use churust_core::Call;
318 /// use http::{HeaderMap, Method};
319 /// use bytes::Bytes;
320 ///
321 /// let call = Call::new(Method::GET, "/s?q=hello+world".parse().unwrap(), HeaderMap::new(), Bytes::new());
322 /// assert_eq!(call.query("q").as_deref(), Some("hello world"));
323 /// assert_eq!(call.query("missing"), None);
324 /// ```
325 pub fn query(&self, key: &str) -> Option<String> {
326 form_urlencoded_first(self.query_string(), key)
327 }
328
329 /// Set by the router after a successful match.
330 pub(crate) fn set_params(&mut self, params: Params) {
331 self.params = params;
332 }
333
334 /// Injected by `App::process` before the pipeline runs.
335 pub(crate) fn set_state(&mut self, state: Arc<StateMap>) {
336 self.state = state;
337 }
338
339 /// The address the connection came from.
340 ///
341 /// `None` when the call did not come from the engine — a `TestClient`
342 /// request, for instance, has no socket behind it.
343 ///
344 /// This is the *socket* peer. Behind a reverse proxy it is the proxy, not
345 /// the client: consult `X-Forwarded-For` only after checking this against
346 /// the addresses you actually trust, since the header is client-supplied
347 /// and trivially forged.
348 pub fn peer_addr(&self) -> Option<std::net::SocketAddr> {
349 self.get::<PeerAddr>().map(|p| p.0)
350 }
351
352 /// The value of request cookie `name`, percent-decoded.
353 ///
354 /// ```
355 /// use churust_core::Call;
356 /// use http::{HeaderMap, HeaderValue, Method};
357 /// use bytes::Bytes;
358 /// let mut headers = HeaderMap::new();
359 /// headers.insert(http::header::COOKIE, HeaderValue::from_static("a=1; b=two"));
360 /// let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
361 /// assert_eq!(call.cookie("b").as_deref(), Some("two"));
362 /// assert_eq!(call.cookie("missing"), None);
363 /// ```
364 pub fn cookie(&self, name: &str) -> Option<String> {
365 // Every `Cookie` field, not just the first. HTTP/2 permits a client to
366 // send cookie crumbs as several separate header fields (RFC 9113
367 // §8.2.3), and an HTTP/1.1 client may send more than one `Cookie` line
368 // too. Reading only the first meant a session cookie that happened to
369 // land in the second field was invisible, so every request looked
370 // freshly anonymous — a silent logout on each request.
371 self.headers
372 .get_all(http::header::COOKIE)
373 .iter()
374 .filter_map(|v| v.to_str().ok())
375 .find_map(|raw| crate::cookie::find(raw, name))
376 .map(crate::cookie::decode)
377 }
378
379 /// A copy of this call carrying `body`.
380 ///
381 /// Used by [`Either`](crate::Either), which must be able to hand the same
382 /// request to a second extractor after the first declines it.
383 pub(crate) fn clone_with_body(&self, body: Bytes) -> Call {
384 let mut c = Call::new(
385 self.method.clone(),
386 self.uri.clone(),
387 self.headers.clone(),
388 body,
389 );
390 c.params = self.params.clone();
391 c.state = self.state.clone();
392 c.extensions = self.extensions.clone();
393 c
394 }
395
396 /// A lightweight copy of the request for an error renderer.
397 ///
398 /// [`on_error`](crate::AppBuilder::on_error) needs to inspect the request,
399 /// but running the rest of the pipeline consumes the call. This keeps the
400 /// parts an error page can reasonably ask about — method, URI and headers —
401 /// and deliberately not the body, which has usually been consumed by then
402 /// and would be misleading to hand back.
403 pub(crate) fn snapshot_for_error(&self) -> Call {
404 Call::new(
405 self.method.clone(),
406 self.uri.clone(),
407 self.headers.clone(),
408 Bytes::new(),
409 )
410 }
411
412 /// Merge externally-built extensions into this call (used by the engine to
413 /// inject per-connection data such as a pending WebSocket upgrade). Existing
414 /// entries of the same type are overwritten.
415 pub(crate) fn seed_extensions(&mut self, ext: http::Extensions) {
416 self.extensions.extend(ext);
417 }
418
419 /// A shared handle to application state of type `T`, or `None` if no value
420 /// of that type was registered with
421 /// [`AppBuilder::state`](crate::AppBuilder::state). For handler arguments,
422 /// the [`State`](crate::State) extractor is usually more convenient.
423 ///
424 /// ```
425 /// use churust_core::{Churust, Call, TestClient};
426 /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
427 /// #[derive(Clone)]
428 /// struct AppName(&'static str);
429 ///
430 /// let app = Churust::server()
431 /// .state(AppName("churust"))
432 /// .routing(|r| {
433 /// r.get("/", |c: Call| async move {
434 /// format!("app={}", c.state::<AppName>().unwrap().0)
435 /// });
436 /// })
437 /// .build();
438 /// let res = TestClient::new(app).get("/").send().await;
439 /// assert_eq!(res.text(), "app=churust");
440 /// # });
441 /// ```
442 pub fn state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
443 self.state.get::<T>()
444 }
445
446 /// Iterate over the captured path parameters as `(name, value)` pairs, in
447 /// capture order.
448 /// Pairs come back in the order the route captured them.
449 pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)> {
450 self.params.iter()
451 }
452
453 /// The captured path parameters, in capture order.
454 pub fn params(&self) -> &Params {
455 &self.params
456 }
457
458 /// The raw, unparsed value of path parameter `name`, or `None` if the route
459 /// has no such parameter. Use [`param`](Call::param) to parse it into a
460 /// typed value.
461 pub fn param_raw(&self, name: &str) -> Option<&str> {
462 self.params.get(name)
463 }
464
465 /// Parse path parameter `name` into `T`.
466 ///
467 /// Returns a `400 Bad Request` [`Error`] if the parameter is
468 /// missing or fails to parse.
469 ///
470 /// ```
471 /// use churust_core::{Churust, Call, TestClient};
472 /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
473 /// let app = Churust::server()
474 /// .routing(|r| {
475 /// r.get("/users/{id}", |c: Call| async move {
476 /// let id: u64 = c.param("id")?;
477 /// Ok::<_, churust_core::Error>(format!("user {id}"))
478 /// });
479 /// })
480 /// .build();
481 /// let res = TestClient::new(app).get("/users/42").send().await;
482 /// assert_eq!(res.text(), "user 42");
483 /// # });
484 /// ```
485 pub fn param<T>(&self, name: &str) -> Result<T>
486 where
487 T: std::str::FromStr,
488 T::Err: std::fmt::Display,
489 {
490 let raw = self
491 .param_raw(name)
492 .ok_or_else(|| Error::bad_request(format!("missing path param `{name}`")))?;
493 raw.parse::<T>()
494 .map_err(|e| Error::bad_request(format!("bad path param `{name}`: {e}")))
495 }
496
497 /// Take the request body as raw [`Bytes`], leaving the call's body empty.
498 ///
499 /// This consumes the body: a second call returns an empty buffer.
500 ///
501 /// # Prefer [`try_receive_bytes`](Call::try_receive_bytes)
502 ///
503 /// **An empty return does not mean an empty body.** The body now arrives as
504 /// a stream, and this method has no error channel, so a read that fails —
505 /// most importantly one that exceeds `max_body_bytes` — is reported as zero
506 /// bytes. A handler built on this answers `200` with whatever an empty body
507 /// produces, where the caller should have seen `413 Payload Too Large`.
508 ///
509 /// Use [`try_receive_bytes`](Call::try_receive_bytes) and let `?` turn the
510 /// failure into the right status. This method is kept for the case where
511 /// the distinction genuinely does not matter.
512 ///
513 /// ```
514 /// use churust_core::Call;
515 /// use http::{HeaderMap, Method};
516 /// use bytes::Bytes;
517 /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
518 /// let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("data"));
519 /// assert_eq!(&call.receive_bytes().await[..], b"data");
520 /// assert!(call.receive_bytes().await.is_empty()); // already consumed
521 /// # });
522 /// ```
523 pub async fn receive_bytes(&mut self) -> Bytes {
524 self.try_receive_bytes().await.unwrap_or_default()
525 }
526
527 /// Read the whole body, surfacing the size limit as an error.
528 ///
529 /// [`receive_bytes`](Call::receive_bytes) exists for callers that cannot
530 /// report failure and yields an empty buffer instead; extractors should
531 /// prefer this so an oversized body becomes `413` rather than a confusing
532 /// deserialization error.
533 pub async fn try_receive_bytes(&mut self) -> Result<Bytes> {
534 // The per-route cap, when one is set, is the ceiling this collection
535 // must respect — see `try_receive_bytes_within`.
536 let cap = self
537 .get::<crate::extract::RouteBodyLimit>()
538 .map(|crate::extract::RouteBodyLimit(n)| n)
539 .unwrap_or(usize::MAX);
540 self.try_receive_bytes_within(cap).await
541 }
542
543 /// Collect the body, refusing it the moment it exceeds `max`.
544 ///
545 /// The distinction from checking afterwards is memory, not status. A body
546 /// gathered first and measured second has already been allocated: a 16 MiB
547 /// upload against a 64 KiB route cap peaked above 32 MiB — `BytesMut`
548 /// doubles — before anything refused it, and N concurrent requests
549 /// multiplied that. The `413` was correct and arrived far too late to be
550 /// the protection it looked like.
551 ///
552 /// Errors with `413 Payload Too Large` as soon as the accumulated length
553 /// crosses `max`, so the peak is bounded by the cap rather than by what the
554 /// client chose to send.
555 pub async fn try_receive_bytes_within(&mut self, max: usize) -> Result<Bytes> {
556 match std::mem::replace(&mut self.body, RequestBody::Buffered(Bytes::new())) {
557 RequestBody::Buffered(b) => {
558 if b.len() > max {
559 return Err(Error::new(
560 StatusCode::PAYLOAD_TOO_LARGE,
561 "request body too large",
562 ));
563 }
564 Ok(b)
565 }
566 RequestBody::Stream(cell) => {
567 let Some(mut s) = cell.lock().ok().and_then(|mut g| g.take()) else {
568 return Ok(Bytes::new());
569 };
570 use futures_util::StreamExt;
571 let mut buf = bytes::BytesMut::new();
572 while let Some(chunk) = s.next().await {
573 let chunk = chunk?;
574 // Check before extending, so the allocation never happens.
575 if buf.len().saturating_add(chunk.len()) > max {
576 return Err(Error::new(
577 StatusCode::PAYLOAD_TOO_LARGE,
578 "request body too large",
579 ));
580 }
581 buf.extend_from_slice(&chunk);
582 }
583 Ok(buf.freeze())
584 }
585 }
586 }
587
588 /// Take the body as a stream, without buffering it.
589 ///
590 /// This is how a large upload is processed without holding it in memory.
591 /// Returns `None` if the body has already been consumed. A body that
592 /// arrived buffered — from [`Call::new`] or a test client — yields a
593 /// single-chunk stream, so a handler need not care which it got.
594 ///
595 /// The server-wide `max_body_bytes` still applies: exceeding it surfaces as
596 /// an error item in the stream rather than a `413`, because the response
597 /// has usually begun by then.
598 pub fn body_stream(&mut self) -> Option<BodyStream> {
599 match std::mem::replace(&mut self.body, RequestBody::Buffered(Bytes::new())) {
600 RequestBody::Stream(cell) => cell.lock().ok().and_then(|mut g| g.take()),
601 RequestBody::Buffered(b) if !b.is_empty() => {
602 Some(Box::pin(futures_util::stream::once(async move { Ok(b) })))
603 }
604 RequestBody::Buffered(_) => None,
605 }
606 }
607
608 /// Attach a streaming body. Used by the engine.
609 pub(crate) fn with_body_stream(mut self, stream: BodyStream) -> Self {
610 self.body = RequestBody::Stream(std::sync::Mutex::new(Some(stream)));
611 self
612 }
613
614 /// Take the request body decoded as UTF-8, consuming it.
615 ///
616 /// Returns a `400 Bad Request` [`Error`] if the body is not
617 /// valid UTF-8.
618 ///
619 /// ```
620 /// use churust_core::Call;
621 /// use http::{HeaderMap, Method};
622 /// use bytes::Bytes;
623 /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
624 /// let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("ping"));
625 /// assert_eq!(call.receive_text().await.unwrap(), "ping");
626 /// # });
627 /// ```
628 pub async fn receive_text(&mut self) -> Result<String> {
629 // `try_receive_bytes`, not `receive_bytes`: the latter swallows a read
630 // error into an empty payload, which would turn an over-limit body into
631 // an empty string rather than a `413`.
632 let bytes = self.try_receive_bytes().await?;
633 crate::extract::check_body_limit(self, bytes.len())?;
634 String::from_utf8(bytes.to_vec())
635 .map_err(|e| Error::bad_request(format!("body is not valid UTF-8: {e}")))
636 }
637
638 /// Insert a per-call typed value into the call's extension map, keyed by its
639 /// type. Typically used by a middleware to attach context (e.g. an
640 /// authenticated principal) that a downstream extractor or handler reads via
641 /// [`get`](Call::get). Inserting a second value of the same type replaces
642 /// the first.
643 ///
644 /// ```
645 /// use churust_core::Call;
646 /// use http::{HeaderMap, Method};
647 /// use bytes::Bytes;
648 ///
649 /// #[derive(Clone, PartialEq, Debug)]
650 /// struct UserId(u32);
651 ///
652 /// let mut call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
653 /// call.insert(UserId(7));
654 /// assert_eq!(call.get::<UserId>(), Some(UserId(7)));
655 /// ```
656 pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
657 self.extensions.insert(value);
658 }
659
660 /// Get a clone of the per-call value of type `T` previously stored with
661 /// [`insert`](Call::insert), or `None` if none was stored.
662 ///
663 /// ```
664 /// use churust_core::Call;
665 /// use http::{HeaderMap, Method};
666 /// use bytes::Bytes;
667 ///
668 /// let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
669 /// assert_eq!(call.get::<u32>(), None);
670 /// ```
671 pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T> {
672 self.extensions.get::<T>().cloned()
673 }
674
675 // ---- response convenience (sync; body is in memory) ----
676
677 /// Build a `200 OK` `text/plain` [`Response`] — a shorthand for
678 /// [`Response::text`].
679 ///
680 /// ```
681 /// use churust_core::Call;
682 /// use http::{HeaderMap, Method};
683 /// use bytes::Bytes;
684 ///
685 /// let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
686 /// let res = call.respond_text("ok");
687 /// assert_eq!(res.body.as_slice(), Some(&b"ok"[..]));
688 /// ```
689 pub fn respond_text(&self, body: impl Into<String>) -> Response {
690 Response::text(body)
691 }
692
693 /// Build an empty-bodied [`Response`] with the given status — a shorthand
694 /// for [`Response::new`].
695 ///
696 /// ```
697 /// use churust_core::Call;
698 /// use http::{HeaderMap, Method, StatusCode};
699 /// use bytes::Bytes;
700 ///
701 /// let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
702 /// assert_eq!(call.respond_status(StatusCode::ACCEPTED).status, StatusCode::ACCEPTED);
703 /// ```
704 pub fn respond_status(&self, status: StatusCode) -> Response {
705 Response::new(status)
706 }
707}
708
709fn form_urlencoded_first(query: &str, key: &str) -> Option<String> {
710 for pair in query.split('&') {
711 let mut it = pair.splitn(2, '=');
712 let k = it.next().unwrap_or("");
713 if k == key {
714 let v = it.next().unwrap_or("");
715 return Some(percent_decode(v));
716 }
717 }
718 None
719}
720
721fn percent_decode(s: &str) -> String {
722 // Minimal `+` and %XX decoding sufficient for v1 query parsing.
723 let bytes = s.as_bytes();
724 let mut out = Vec::with_capacity(bytes.len());
725 let mut i = 0;
726 while i < bytes.len() {
727 match bytes[i] {
728 b'+' => {
729 out.push(b' ');
730 i += 1;
731 }
732 b'%' if i + 2 < bytes.len() => {
733 let hi = (bytes[i + 1] as char).to_digit(16);
734 let lo = (bytes[i + 2] as char).to_digit(16);
735 match (hi, lo) {
736 (Some(h), Some(l)) => {
737 out.push((h * 16 + l) as u8);
738 i += 3;
739 }
740 _ => {
741 out.push(bytes[i]);
742 i += 1;
743 }
744 }
745 }
746 b => {
747 out.push(b);
748 i += 1;
749 }
750 }
751 }
752 String::from_utf8_lossy(&out).into_owned()
753}
754
755#[cfg(test)]
756mod tests {
757 use super::*;
758
759 fn call(path: &str, body: &str) -> Call {
760 Call::new(
761 Method::GET,
762 path.parse::<Uri>().unwrap(),
763 HeaderMap::new(),
764 Bytes::from(body.to_string()),
765 )
766 }
767
768 #[test]
769 fn reads_method_and_path() {
770 let c = call("/a/b?x=1", "");
771 assert_eq!(c.method(), &Method::GET);
772 assert_eq!(c.path(), "/a/b");
773 }
774
775 #[test]
776 fn parses_query() {
777 let c = call("/s?q=hello+world&n=5", "");
778 assert_eq!(c.query("q").as_deref(), Some("hello world"));
779 assert_eq!(c.query("n").as_deref(), Some("5"));
780 assert_eq!(c.query("missing"), None);
781 }
782
783 #[test]
784 fn parses_query_percent_encoded() {
785 let c = call("/s?q=hello%20world", "");
786 assert_eq!(c.query("q").as_deref(), Some("hello world"));
787 }
788
789 #[test]
790 fn parses_path_param() {
791 let mut c = call("/users/42", "");
792 let mut p = crate::call::Params::new();
793 p.insert("id".to_string(), "42".to_string());
794 c.set_params(p);
795 let id: u64 = c.param("id").unwrap();
796 assert_eq!(id, 42);
797 assert!(c.param::<u64>("missing").is_err());
798 }
799
800 #[tokio::test]
801 async fn receives_text_body() {
802 let mut c = call("/", "payload");
803 assert_eq!(c.receive_text().await.unwrap(), "payload");
804 }
805
806 #[test]
807 fn state_round_trips() {
808 use crate::state::StateMap;
809 let mut c = call("/", "");
810 let mut sm = StateMap::default();
811 sm.insert(99u32);
812 c.set_state(std::sync::Arc::new(sm));
813 assert_eq!(*c.state::<u32>().unwrap(), 99);
814 }
815
816 #[test]
817 fn the_uri_authority_outranks_a_disagreeing_host_header() {
818 // Both spellings of the host can arrive at once: over HTTP/2 a peer may
819 // append an ordinary `host` field beside `:authority` (h2 forwards it
820 // untouched, and hyper builds the URI authority from the pseudo-header
821 // alone), and over HTTP/1.1 an absolute-form target carries an
822 // authority beside the mandatory `Host`. Both RFCs say the authority
823 // is the request target and the header is to be ignored, so the guard
824 // and anything else asking "which site is this for" must agree with
825 // whatever an intermediary routed on.
826 let mut headers = HeaderMap::new();
827 headers.insert(
828 http::header::HOST,
829 http::HeaderValue::from_static("admin.example.com"),
830 );
831 let c = Call::new(
832 Method::GET,
833 "https://www.example.com/".parse::<Uri>().unwrap(),
834 headers,
835 Bytes::new(),
836 );
837 assert_eq!(c.host().as_deref(), Some("www.example.com"));
838 }
839
840 #[test]
841 fn the_host_header_is_still_read_when_the_uri_has_no_authority() {
842 // The origin-form HTTP/1.1 request, which is the overwhelmingly common
843 // shape: the header is the only host signal there is.
844 let mut headers = HeaderMap::new();
845 headers.insert(
846 http::header::HOST,
847 http::HeaderValue::from_static("admin.example.com:8443"),
848 );
849 let c = Call::new(
850 Method::GET,
851 "/".parse::<Uri>().unwrap(),
852 headers,
853 Bytes::new(),
854 );
855 assert_eq!(c.host().as_deref(), Some("admin.example.com"));
856 }
857
858 #[test]
859 fn extensions_round_trip() {
860 #[derive(Clone, PartialEq, Debug)]
861 struct User(u32);
862 let mut c = call("/", "");
863 assert!(c.get::<User>().is_none());
864 c.insert(User(7));
865 assert_eq!(c.get::<User>(), Some(User(7)));
866 }
867}