Skip to main content

io_http/rfc9112/
read_headers.rs

1//! I/O-free coroutine reading and parsing an HTTP/1.X response head
2//! ([RFC 9112 §6]). Composed by `Http10Send`, `Http11Send`, and the SSE
3//! bootstrap.
4//!
5//! [RFC 9112 §6]: https://www.rfc-editor.org/rfc/rfc9112#section-6
6
7use alloc::vec::Vec;
8
9use httparse::{EMPTY_HEADER, Error as HttparseError, Response, Status};
10use log::{debug, trace};
11use thiserror::Error;
12
13use crate::{
14    coroutine::*,
15    rfc1945::version::HTTP_10,
16    rfc9110::{
17        headers::HTTP_CONNECTION,
18        response::{HttpResponse, HttpResponseBuilder},
19        status::HttpStatusCode,
20    },
21    rfc9112::version::HTTP_11,
22};
23
24/// Failure causes during the HTTP/1.X read-headers flow.
25#[derive(Debug, Error)]
26pub enum Http11HeadersReadError {
27    /// The stream reached EOF before the head was complete.
28    #[error("HTTP/1.X read headers failed: reached unexpected EOF before headers were complete")]
29    Eof,
30    /// The response head could not be parsed.
31    #[error("HTTP/1.X read headers failed: parse response headers: {0}")]
32    ParseResponseHeaders(HttparseError),
33}
34
35/// Terminal output of [`Http11HeadersRead`]; the response body is
36/// empty at this stage.
37#[derive(Debug)]
38pub struct Http11HeadersReadOutput {
39    /// The parsed response, head only.
40    pub response: HttpResponse,
41    /// Bytes already buffered past the end of the head.
42    pub remaining: Vec<u8>,
43    /// Whether the server signalled the connection can be reused.
44    pub keep_alive: bool,
45}
46
47/// I/O-free coroutine to read and parse an HTTP/1.X response head.
48#[derive(Debug, Default)]
49pub struct Http11HeadersRead {
50    buf: Vec<u8>,
51}
52
53impl HttpCoroutine for Http11HeadersRead {
54    type Yield = HttpYield;
55    type Return = Result<Http11HeadersReadOutput, Http11HeadersReadError>;
56
57    fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
58        match arg {
59            Some(&[]) => {
60                return HttpCoroutineState::Complete(Err(Http11HeadersReadError::Eof));
61            }
62            Some(data) => self.buf.extend_from_slice(data),
63            None => {}
64        }
65
66        let mut headers = [EMPTY_HEADER; 64];
67        let mut parsed = Response::new(&mut headers);
68
69        let header_end = match parsed.parse(&self.buf) {
70            Ok(Status::Complete(n)) => n,
71            Ok(Status::Partial) => {
72                trace!("received incomplete headers");
73                return HttpCoroutineState::Yielded(HttpYield::WantsRead);
74            }
75            Err(err) => {
76                return HttpCoroutineState::Complete(Err(
77                    Http11HeadersReadError::ParseResponseHeaders(err),
78                ));
79            }
80        };
81
82        let mut builder = HttpResponseBuilder::default();
83        let is_http10 = matches!(parsed.version, Some(0));
84        builder.version = if is_http10 { HTTP_10 } else { HTTP_11 }.into();
85        if let Some(code) = parsed.code {
86            builder.status = Some(HttpStatusCode(code));
87        }
88        for header in parsed.headers.iter() {
89            builder.header(header.name, header.value);
90        }
91
92        let keep_alive = match builder.get_header(HTTP_CONNECTION) {
93            Some(conn) => !conn.eq_ignore_ascii_case("close"),
94            None => !is_http10,
95        };
96
97        debug!("received complete headers");
98        trace!("{builder:?}");
99
100        let response = builder.build(Vec::new());
101        let remaining = self.buf.split_off(header_end);
102
103        HttpCoroutineState::Complete(Ok(Http11HeadersReadOutput {
104            response,
105            remaining,
106            keep_alive,
107        }))
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use crate::{coroutine::*, rfc9112::read_headers::*};
114
115    #[test]
116    fn parses_complete_head() {
117        let mut coroutine = Http11HeadersRead::default();
118        let reply = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nbody";
119
120        let out = expect_complete_ok(&mut coroutine, Some(reply));
121        assert_eq!(*out.response.status, 200);
122        assert_eq!(out.response.version, "HTTP/1.1");
123        assert_eq!(out.response.header("content-type"), Some("text/plain"));
124        assert_eq!(out.remaining, b"body");
125    }
126
127    #[test]
128    fn incomplete_head_wants_read() {
129        let mut coroutine = Http11HeadersRead::default();
130        expect_wants_read(&mut coroutine, Some(b"HTTP/1.1 200 OK\r\n"));
131    }
132
133    #[test]
134    fn eof_returns_eof_error() {
135        let mut coroutine = Http11HeadersRead::default();
136        let err = expect_complete_err(&mut coroutine, Some(b""));
137        assert!(matches!(err, Http11HeadersReadError::Eof));
138    }
139
140    #[test]
141    fn http10_keep_alive_defaults_false() {
142        let mut coroutine = Http11HeadersRead::default();
143        let reply = b"HTTP/1.0 200 OK\r\n\r\n";
144        let out = expect_complete_ok(&mut coroutine, Some(reply));
145        assert!(!out.keep_alive);
146        assert_eq!(out.response.version, "HTTP/1.0");
147    }
148
149    #[test]
150    fn http11_keep_alive_defaults_true() {
151        let mut coroutine = Http11HeadersRead::default();
152        let reply = b"HTTP/1.1 200 OK\r\n\r\n";
153        let out = expect_complete_ok(&mut coroutine, Some(reply));
154        assert!(out.keep_alive);
155    }
156
157    #[test]
158    fn connection_close_overrides_default() {
159        let mut coroutine = Http11HeadersRead::default();
160        let reply = b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
161        let out = expect_complete_ok(&mut coroutine, Some(reply));
162        assert!(!out.keep_alive);
163    }
164
165    fn expect_wants_read(cor: &mut Http11HeadersRead, arg: Option<&[u8]>) {
166        match cor.resume(arg) {
167            HttpCoroutineState::Yielded(HttpYield::WantsRead) => {}
168            state => panic!("expected WantsRead, got {state:?}"),
169        }
170    }
171
172    fn expect_complete_ok(
173        cor: &mut Http11HeadersRead,
174        arg: Option<&[u8]>,
175    ) -> Http11HeadersReadOutput {
176        match cor.resume(arg) {
177            HttpCoroutineState::Complete(Ok(out)) => out,
178            state => panic!("expected Complete(Ok), got {state:?}"),
179        }
180    }
181
182    fn expect_complete_err(
183        cor: &mut Http11HeadersRead,
184        arg: Option<&[u8]>,
185    ) -> Http11HeadersReadError {
186        match cor.resume(arg) {
187            HttpCoroutineState::Complete(Err(err)) => err,
188            state => panic!("expected Complete(Err), got {state:?}"),
189        }
190    }
191}