Skip to main content

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