trillium_http/conn.rs
1use crate::{
2 Body, Buffer, Headers, HttpContext,
3 KnownHeaderName::Host,
4 Method, ProtocolSession, ReceivedBody, Status, Swansong, TypeSet, Version,
5 after_send::{AfterSend, SendStatus},
6 h2::H2Connection,
7 h3::H3Connection,
8 liveness::{CancelOnDisconnect, LivenessFut, PeerGone},
9 received_body::ReceivedBodyState,
10 util::encoding,
11};
12use encoding_rs::Encoding;
13use futures_lite::{
14 future,
15 io::{AsyncRead, AsyncWrite},
16};
17use std::{
18 borrow::Cow,
19 fmt::{self, Debug, Formatter},
20 future::Future,
21 net::IpAddr,
22 pin::pin,
23 str,
24 sync::Arc,
25 time::Instant,
26};
27mod h1;
28#[cfg(test)]
29mod h1_tests;
30mod h2;
31mod h3;
32mod shared;
33pub(crate) use h1::{HeadError, write_headers_or_trailers};
34pub(crate) use h3::H3FirstFrame;
35pub(crate) use shared::ConnParts;
36
37/// An HTTP connection.
38///
39/// This struct represents both the request and the response, and holds the
40/// transport over which the response will be sent.
41#[derive(fieldwork::Fieldwork)]
42pub struct Conn<Transport> {
43 #[field(get)]
44 /// the shared [`HttpContext`]
45 pub(crate) context: Arc<HttpContext>,
46
47 /// request [headers](Headers)
48 #[field(get, get_mut)]
49 pub(crate) request_headers: Headers,
50
51 /// response [headers](Headers)
52 #[field(get, get_mut)]
53 pub(crate) response_headers: Headers,
54
55 pub(crate) path: Cow<'static, str>,
56
57 /// the http method for this conn's request
58 ///
59 /// ```
60 /// # use trillium_http::{Conn, Method};
61 /// let mut conn = Conn::new_synthetic(Method::Get, "/some/path?and&a=query", ());
62 /// assert_eq!(conn.method(), Method::Get);
63 /// ```
64 #[field(get, set, copy)]
65 pub(crate) method: Method,
66
67 /// the http status for this conn, if set
68 #[field(get, copy)]
69 pub(crate) status: Option<Status>,
70
71 /// The HTTP protocol version in use on this connection.
72 ///
73 /// ```
74 /// # use trillium_http::{Conn, Method, Version};
75 /// let conn = Conn::new_synthetic(Method::Get, "/", ());
76 /// assert_eq!(conn.http_version(), Version::Http1_1);
77 /// ```
78 #[field(get = http_version, copy)]
79 pub(crate) version: Version,
80
81 /// the [state typemap](TypeSet) for this conn
82 #[field(get, get_mut)]
83 pub(crate) state: TypeSet,
84
85 /// the response [body](Body)
86 ///
87 /// ```
88 /// # use trillium_testing::HttpTest;
89 /// HttpTest::new(|conn| async move { conn.with_response_body("hello") })
90 /// .get("/")
91 /// .block()
92 /// .assert_body("hello");
93 ///
94 /// HttpTest::new(|conn| async move { conn.with_response_body(String::from("world")) })
95 /// .get("/")
96 /// .block()
97 /// .assert_body("world");
98 ///
99 /// HttpTest::new(|conn| async move { conn.with_response_body(vec![99, 97, 116]) })
100 /// .get("/")
101 /// .block()
102 /// .assert_body("cat");
103 /// ```
104 #[field(get, set, into, option_set_some, take, with)]
105 pub(crate) response_body: Option<Body>,
106
107 /// the transport
108 ///
109 /// This should only be used to call your own custom methods on the transport that do not read
110 /// or write any data. Calling any method that reads from or writes to the transport will
111 /// disrupt the HTTP protocol. If you're looking to transition from HTTP to another protocol,
112 /// use an HTTP upgrade.
113 #[field(get, get_mut)]
114 pub(crate) transport: Transport,
115
116 pub(crate) buffer: Buffer,
117
118 pub(crate) request_body_state: ReceivedBodyState,
119
120 pub(crate) after_send: AfterSend,
121
122 /// whether the connection is secure
123 ///
124 /// note that this does not necessarily indicate that the transport itself is secure, as it may
125 /// indicate that `trillium_http` is behind a trusted reverse proxy that has terminated tls and
126 /// provided appropriate headers to indicate this.
127 #[field(get, set, rename_predicates)]
128 pub(crate) secure: bool,
129
130 /// The [`Instant`] that the first header bytes for this conn were
131 /// received, before any processing or parsing has been performed.
132 #[field(get, copy)]
133 pub(crate) start_time: Instant,
134
135 /// The IP Address for the connection, if available
136 #[field(set, get, copy, into)]
137 pub(crate) peer_ip: Option<IpAddr>,
138
139 /// the `:authority` pseudo-header
140 #[field(set, get, into)]
141 pub(crate) authority: Option<Cow<'static, str>>,
142
143 /// the `:scheme` pseudo-header
144 #[field(set, get, into)]
145 pub(crate) scheme: Option<Cow<'static, str>>,
146
147 /// the [`ProtocolSession`] for this conn — the per-protocol session state
148 /// (h2/h3 connection driver and stream id) bundled into a single enum so the
149 /// "set together" invariant is enforced at the type level. `Http1` for
150 /// h1 / synthetic conns.
151 pub(crate) protocol_session: ProtocolSession,
152
153 /// the `:protocol` pseudo-header (extended CONNECT)
154 #[field(set, get, into)]
155 pub(crate) protocol: Option<Cow<'static, str>>,
156
157 /// request trailers, populated after the request body has been fully read
158 #[field(get, get_mut)]
159 pub(crate) request_trailers: Option<Headers>,
160
161 /// Marker set via [`Conn::upgrade`].
162 pub(crate) upgrade: bool,
163
164 /// Resolves when the peer abandons this stream. HTTP/3 only — h1 detects departure by
165 /// reading and h2 through its connection driver, but h3's QUIC signals are invisible at
166 /// the transport seam, so the runtime adapter supplies this. See [`PeerGone`].
167 #[field = false]
168 pub(crate) peer_gone: Option<PeerGone>,
169}
170
171impl<Transport> Debug for Conn<Transport> {
172 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
173 f.debug_struct("Conn")
174 .field("context", &self.context)
175 .field("request_headers", &self.request_headers)
176 .field("response_headers", &self.response_headers)
177 .field("path", &self.path)
178 .field("method", &self.method)
179 .field("status", &self.status)
180 .field("version", &self.version)
181 .field("state", &self.state)
182 .field("response_body", &self.response_body)
183 .field("transport", &format_args!(".."))
184 .field("buffer", &format_args!(".."))
185 .field("request_body_state", &self.request_body_state)
186 .field("secure", &self.secure)
187 .field("after_send", &format_args!(".."))
188 .field("start_time", &self.start_time)
189 .field("peer_ip", &self.peer_ip)
190 .field("authority", &self.authority)
191 .field("scheme", &self.scheme)
192 .field("protocol", &self.protocol)
193 .field("protocol_session", &self.protocol_session)
194 .field("request_trailers", &self.request_trailers)
195 .field("upgrade", &self.upgrade)
196 .field("peer_gone", &format_args!(".."))
197 .finish()
198 }
199}
200
201impl<Transport> Conn<Transport>
202where
203 Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
204{
205 /// Returns the shared state typemap for this conn.
206 pub fn shared_state(&self) -> &TypeSet {
207 &self.context.shared_state
208 }
209
210 /// sets the http status code from any `TryInto<Status>`.
211 ///
212 /// ```
213 /// # use trillium_http::Status;
214 /// # trillium_testing::HttpTest::new(|mut conn| async move {
215 /// assert!(conn.status().is_none());
216 ///
217 /// conn.set_status(200); // a status can be set as a u16
218 /// assert_eq!(conn.status().unwrap(), Status::Ok);
219 ///
220 /// conn.set_status(Status::ImATeapot); // or as a Status
221 /// assert_eq!(conn.status().unwrap(), Status::ImATeapot);
222 /// conn
223 /// # }).get("/").block().assert_status(Status::ImATeapot);
224 /// ```
225 pub fn set_status(&mut self, status: impl TryInto<Status>) -> &mut Self {
226 self.status = Some(status.try_into().unwrap_or_else(|_| {
227 log::error!("attempted to set an invalid status code");
228 Status::InternalServerError
229 }));
230 self
231 }
232
233 /// sets the http status code from any `TryInto<Status>`, returning Conn
234 #[must_use]
235 pub fn with_status(mut self, status: impl TryInto<Status>) -> Self {
236 self.set_status(status);
237 self
238 }
239
240 /// The status to send on the wire: the explicitly-set status, or a
241 /// method-appropriate default when a handler left it unset. Unhandled
242 /// requests default to `404 Not Found`, except CONNECT, which defaults to
243 /// `501 Not Implemented`: an origin server implements no tunnel, and 404's
244 /// resource model does not apply to CONNECT's authority-form target.
245 pub(crate) fn response_status(&self) -> Status {
246 self.status.unwrap_or(match self.method {
247 Method::Connect => Status::NotImplemented,
248 _ => Status::NotFound,
249 })
250 }
251
252 /// retrieves the path part of the request url, up to and excluding any query component
253 /// ```
254 /// # use trillium_testing::HttpTest;
255 /// HttpTest::new(|mut conn| async move {
256 /// assert_eq!(conn.path(), "/some/path");
257 /// conn.with_status(200)
258 /// })
259 /// .get("/some/path?and&a=query")
260 /// .block()
261 /// .assert_ok();
262 /// ```
263 pub fn path(&self) -> &str {
264 match self.path.split_once('?') {
265 Some((path, _)) => path,
266 None => &self.path,
267 }
268 }
269
270 /// retrieves the combined path and any query
271 pub fn path_and_query(&self) -> &str {
272 &self.path
273 }
274
275 /// retrieves the query component of the path, or an empty &str
276 ///
277 /// ```
278 /// # use trillium_testing::HttpTest;
279 /// let server = HttpTest::new(|conn| async move {
280 /// let querystring = conn.querystring().to_string();
281 /// conn.with_response_body(querystring).with_status(200)
282 /// });
283 ///
284 /// server
285 /// .get("/some/path?and&a=query")
286 /// .block()
287 /// .assert_body("and&a=query");
288 ///
289 /// server.get("/some/path").block().assert_body("");
290 /// ```
291 pub fn querystring(&self) -> &str {
292 self.path
293 .split_once('?')
294 .map(|(_, query)| query)
295 .unwrap_or_default()
296 }
297
298 /// get the host for this conn, if it exists.
299 ///
300 /// On protocol versions where the equivalent of `Host` is `:authority`, this returns
301 /// `:authority`.
302 pub fn host(&self) -> Option<&str> {
303 self.request_headers
304 .get_str(Host)
305 .or_else(|| self.authority())
306 }
307
308 /// set the host for this conn
309 pub fn set_host(&mut self, host: String) -> &mut Self {
310 self.request_headers.insert(Host, host);
311 self
312 }
313
314 /// Cancels and drops the future if the peer abandons this request
315 ///
316 /// If the client disconnects, this function will return None. If the future completes
317 /// without disconnection, this future will return Some containing the output of the future.
318 ///
319 /// See [`is_disconnected`][Self::is_disconnected] for how departure is detected on each
320 /// protocol. On HTTP/1.x, where detection is by reading, any bytes the client sends while
321 /// the future runs — an unread request body, or pipelined requests — are buffered, up to
322 /// 16kb. A client that fills that allowance is considered alive for the remainder of the
323 /// future, even if it disconnects afterwards. If the request has a body, read it before
324 /// calling this.
325 ///
326 /// Note that the inner future cannot borrow conn, so you will need to clone or take any
327 /// information needed to execute the future prior to executing this method.
328 ///
329 /// # Example
330 ///
331 /// ```rust
332 /// # use futures_lite::{AsyncRead, AsyncWrite};
333 /// # use trillium_http::{Conn, Method};
334 /// async fn something_slow_and_cancel_safe() -> String {
335 /// String::from("this was not actually slow")
336 /// }
337 /// async fn handler<T>(mut conn: Conn<T>) -> Conn<T>
338 /// where
339 /// T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
340 /// {
341 /// let Some(returned_body) = conn
342 /// .cancel_on_disconnect(async { something_slow_and_cancel_safe().await })
343 /// .await
344 /// else {
345 /// return conn;
346 /// };
347 /// conn.with_response_body(returned_body).with_status(200)
348 /// }
349 /// ```
350 pub async fn cancel_on_disconnect<'a, Fut>(&'a mut self, fut: Fut) -> Option<Fut::Output>
351 where
352 Fut: Future + Send + 'a,
353 {
354 CancelOnDisconnect(self, pin!(fut)).await
355 }
356
357 /// Check whether the peer has abandoned this request.
358 ///
359 /// How departure becomes observable is protocol-specific. On HTTP/1.x this reads the
360 /// transport: any bytes the client sends — an unread request body, or pipelined requests —
361 /// are buffered, up to 16kb, and count as evidence of liveness. A client that fills that
362 /// allowance is reported as connected until the buffered bytes are read, so read the request
363 /// body before polling this in a long-running handler. On HTTP/2 this observes stream reset
364 /// and connection teardown, and on HTTP/3 stream cancellation and connection loss; neither
365 /// reads the transport, and neither treats the peer's half-close — which both protocols do
366 /// as a matter of course once the request is complete — as a departure.
367 ///
368 /// A client that vanishes without signalling is only reported once the transport notices,
369 /// which on HTTP/3 is QUIC's negotiated idle timeout and on HTTP/1.x and HTTP/2 depends on
370 /// the TCP configuration.
371 ///
372 /// # Example
373 ///
374 /// This is best to use at appropriate points in a long-running handler, like:
375 ///
376 /// ```rust
377 /// # use futures_lite::{AsyncRead, AsyncWrite};
378 /// # use trillium_http::{Conn, Method};
379 /// # async fn something_slow_but_not_cancel_safe() {}
380 /// async fn handler<T>(mut conn: Conn<T>) -> Conn<T>
381 /// where
382 /// T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
383 /// {
384 /// for _ in 0..100 {
385 /// if conn.is_disconnected().await {
386 /// return conn;
387 /// }
388 /// something_slow_but_not_cancel_safe().await;
389 /// }
390 /// conn.with_status(200)
391 /// }
392 /// ```
393 pub async fn is_disconnected(&mut self) -> bool {
394 future::poll_once(LivenessFut::new(self)).await.is_some()
395 }
396
397 /// returns the [`encoding_rs::Encoding`] for this request, as determined from the mime-type
398 /// charset, if available
399 ///
400 /// ```
401 /// # use trillium_testing::HttpTest;
402 /// HttpTest::new(|mut conn| async move {
403 /// assert_eq!(conn.request_encoding(), encoding_rs::UTF_8); // the default
404 ///
405 /// conn.request_headers_mut()
406 /// .insert("content-type", "text/plain;charset=utf-16");
407 /// assert_eq!(conn.request_encoding(), encoding_rs::UTF_16LE);
408 ///
409 /// conn.with_status(200)
410 /// })
411 /// .get("/")
412 /// .block()
413 /// .assert_ok();
414 /// ```
415 pub fn request_encoding(&self) -> &'static Encoding {
416 encoding(&self.request_headers)
417 }
418
419 /// returns the [`encoding_rs::Encoding`] for this response, as
420 /// determined from the mime-type charset, if available
421 ///
422 /// ```
423 /// # use trillium_testing::HttpTest;
424 /// HttpTest::new(|mut conn| async move {
425 /// assert_eq!(conn.response_encoding(), encoding_rs::UTF_8); // the default
426 /// conn.response_headers_mut()
427 /// .insert("content-type", "text/plain;charset=utf-16");
428 ///
429 /// assert_eq!(conn.response_encoding(), encoding_rs::UTF_16LE);
430 ///
431 /// conn.with_status(200)
432 /// })
433 /// .get("/")
434 /// .block()
435 /// .assert_ok();
436 /// ```
437 pub fn response_encoding(&self) -> &'static Encoding {
438 encoding(&self.response_headers)
439 }
440
441 /// returns a [`ReceivedBody`] that references this conn. the conn
442 /// retains all data and holds the singular transport, but the
443 /// `ReceivedBody` provides an interface to read body content.
444 ///
445 /// If the request included an `Expect: 100-continue` header, the 100 Continue response is sent
446 /// lazily on the first read from the returned [`ReceivedBody`].
447 /// ```
448 /// # use trillium_testing::HttpTest;
449 /// let server = HttpTest::new(|mut conn| async move {
450 /// let request_body = conn.request_body();
451 /// assert_eq!(request_body.content_length(), Some(5));
452 /// assert_eq!(request_body.read_string().await.unwrap(), "hello");
453 /// conn.with_status(200)
454 /// });
455 ///
456 /// server.post("/").with_body("hello").block().assert_ok();
457 /// ```
458 pub fn request_body(&mut self) -> ReceivedBody<'_, Transport> {
459 let needs_100_continue = self.needs_100_continue();
460 let body = self.build_request_body();
461 if needs_100_continue {
462 body.with_send_100_continue()
463 } else {
464 body
465 }
466 }
467
468 /// returns a clone of the [`swansong::Swansong`] for this Conn. use
469 /// this to gracefully stop long-running futures and streams
470 /// inside of handler functions
471 pub fn swansong(&self) -> Swansong {
472 self.protocol_session
473 .h3_connection()
474 .map_or_else(|| self.context.swansong.clone(), |h| h.swansong().clone())
475 }
476
477 /// Registers a function to call after the http response has been
478 /// completely transferred.
479 ///
480 /// The callback is guaranteed to fire **exactly once** before the conn is
481 /// dropped. Either the codec's send path invokes it with the real outcome,
482 /// or — if the conn is dropped before send completes (handler panic,
483 /// transport error, mid-write disconnect) — the drop fallback invokes it
484 /// with a `SendStatus` whose `is_success()` returns false. Multiple
485 /// registrations on the same conn chain in registration order.
486 ///
487 /// Because firing is ordered by send-completion rather than handler return,
488 /// this is the right hook for instrumentation that wants to report what the
489 /// peer actually observed.
490 ///
491 /// This is a sync function and should be computationally lightweight. If
492 /// your _application_ needs additional async processing, use your runtime's
493 /// task spawn within this hook. If your _library_ needs additional async
494 /// processing in an `after_send` hook, please open an issue.
495 pub fn after_send<F>(&mut self, after_send: F)
496 where
497 F: FnOnce(SendStatus) + Send + Sync + 'static,
498 {
499 self.after_send.append(after_send);
500 }
501
502 /// applies a mapping function from one transport to another. This
503 /// is particularly useful for boxing the transport. unless you're
504 /// sure this is what you're looking for, you probably don't want
505 /// to be using this
506 pub fn map_transport<NewTransport>(
507 self,
508 f: impl Fn(Transport) -> NewTransport,
509 ) -> Conn<NewTransport>
510 where
511 NewTransport: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
512 {
513 // Manual respread: rustc treats `Conn<Transport>` and `Conn<NewTransport>` as
514 // disjoint types and rejects `..self` without the unstable
515 // `type_changing_struct_update` feature. If a new field is added to `Conn`,
516 // update this respread, `Upgrade::map_transport`, and `From<Conn> for Upgrade`
517 // (`upgrade.rs`) — they share this drift hazard.
518 Conn {
519 context: self.context,
520 request_headers: self.request_headers,
521 response_headers: self.response_headers,
522 method: self.method,
523 response_body: self.response_body,
524 path: self.path,
525 status: self.status,
526 version: self.version,
527 state: self.state,
528 transport: f(self.transport),
529 buffer: self.buffer,
530 request_body_state: self.request_body_state,
531 secure: self.secure,
532 after_send: self.after_send,
533 start_time: self.start_time,
534 peer_ip: self.peer_ip,
535 authority: self.authority,
536 scheme: self.scheme,
537 protocol: self.protocol,
538 protocol_session: self.protocol_session,
539 request_trailers: self.request_trailers,
540 upgrade: self.upgrade,
541 peer_gone: self.peer_gone,
542 }
543 }
544
545 /// whether this conn is suitable for an http upgrade to another protocol
546 pub fn should_upgrade(&self) -> bool {
547 self.upgrade
548 || (self.method() == Method::Connect && self.status == Some(Status::Ok))
549 || self.status == Some(Status::SwitchingProtocols)
550 }
551
552 /// Mark this conn to be handed off as an upgrade once the response headers are sent.
553 /// Set the response status (typically `200`) and any headers describing the upgraded
554 /// byte stream before calling; the handler's `upgrade` method receives an [`Upgrade`]
555 /// with per-protocol framing applied on its `AsyncRead`/`AsyncWrite`.
556 #[doc(hidden)]
557 #[must_use]
558 pub fn upgrade(mut self) -> Self {
559 self.upgrade = true;
560 self
561 }
562
563 #[doc(hidden)]
564 pub fn finalize_headers(&mut self) {
565 if self.version == Version::Http3 {
566 self.finalize_response_headers_h3();
567 } else {
568 self.finalize_response_headers_1x();
569 }
570 }
571
572 /// the [`H2Connection`] driver for this conn, if this is an HTTP/2 request
573 pub fn h2_connection(&self) -> Option<&Arc<H2Connection>> {
574 self.protocol_session.h2_connection()
575 }
576
577 /// the h2 stream id for this conn, if this is an HTTP/2 request
578 pub fn h2_stream_id(&self) -> Option<u32> {
579 self.protocol_session.h2_stream_id()
580 }
581
582 /// the [`H3Connection`] driver for this conn, if this is an HTTP/3 request
583 pub fn h3_connection(&self) -> Option<&Arc<H3Connection>> {
584 self.protocol_session.h3_connection()
585 }
586
587 /// the h3 stream id for this conn, if this is an HTTP/3 request
588 pub fn h3_stream_id(&self) -> Option<u64> {
589 self.protocol_session.h3_stream_id()
590 }
591}