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::trace;
11use thiserror::Error;
12
13use crate::{
14    coroutine::*,
15    rfc1945::version::HTTP_10,
16    rfc9110::{
17        headers::CONNECTION,
18        response::{HttpResponse, ResponseBuilder},
19        status::StatusCode,
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 Http11ReadHeadersError {
27    #[error("HTTP/1.X read headers failed: reached unexpected EOF before headers were complete")]
28    Eof,
29    #[error("HTTP/1.X read headers failed: parse response headers: {0}")]
30    ParseResponseHeaders(HttparseError),
31}
32
33/// Terminal output of [`Http11ReadHeaders`]; `response.body` is empty.
34#[derive(Debug)]
35pub struct Http11ReadHeadersOutput {
36    pub response: HttpResponse,
37    pub remaining: Vec<u8>,
38    pub keep_alive: bool,
39}
40
41/// I/O-free coroutine to read and parse an HTTP/1.X response head.
42#[derive(Debug, Default)]
43pub struct Http11ReadHeaders {
44    buf: Vec<u8>,
45}
46
47impl HttpCoroutine for Http11ReadHeaders {
48    type Yield = HttpYield;
49    type Return = Result<Http11ReadHeadersOutput, Http11ReadHeadersError>;
50
51    fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
52        match arg {
53            Some(&[]) => {
54                return HttpCoroutineState::Complete(Err(Http11ReadHeadersError::Eof));
55            }
56            Some(data) => self.buf.extend_from_slice(data),
57            None => {}
58        }
59
60        let mut headers = [EMPTY_HEADER; 64];
61        let mut parsed = Response::new(&mut headers);
62
63        let header_end = match parsed.parse(&self.buf) {
64            Ok(Status::Complete(n)) => n,
65            Ok(Status::Partial) => {
66                trace!("received incomplete headers");
67                return HttpCoroutineState::Yielded(HttpYield::WantsRead);
68            }
69            Err(err) => {
70                return HttpCoroutineState::Complete(Err(
71                    Http11ReadHeadersError::ParseResponseHeaders(err),
72                ));
73            }
74        };
75
76        let mut builder = ResponseBuilder::default();
77        let is_http10 = matches!(parsed.version, Some(0));
78        builder.version = if is_http10 { HTTP_10 } else { HTTP_11 }.into();
79        if let Some(code) = parsed.code {
80            builder.status = Some(StatusCode(code));
81        }
82        for header in parsed.headers.iter() {
83            builder.header(header.name, header.value);
84        }
85
86        let keep_alive = match builder.get_header(CONNECTION) {
87            Some(conn) => !conn.eq_ignore_ascii_case("close"),
88            None => !is_http10,
89        };
90
91        trace!("received complete headers: {builder:?}");
92
93        let response = builder.build(Vec::new());
94        let remaining = self.buf.split_off(header_end);
95
96        HttpCoroutineState::Complete(Ok(Http11ReadHeadersOutput {
97            response,
98            remaining,
99            keep_alive,
100        }))
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn parses_complete_head() {
110        let mut coroutine = Http11ReadHeaders::default();
111        let reply = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nbody";
112
113        let out = expect_complete_ok(&mut coroutine, Some(reply));
114        assert_eq!(*out.response.status, 200);
115        assert_eq!(out.response.version, "HTTP/1.1");
116        assert_eq!(out.response.header("content-type"), Some("text/plain"));
117        assert_eq!(out.remaining, b"body");
118    }
119
120    #[test]
121    fn incomplete_head_wants_read() {
122        let mut coroutine = Http11ReadHeaders::default();
123        expect_wants_read(&mut coroutine, Some(b"HTTP/1.1 200 OK\r\n"));
124    }
125
126    #[test]
127    fn eof_returns_eof_error() {
128        let mut coroutine = Http11ReadHeaders::default();
129        let err = expect_complete_err(&mut coroutine, Some(b""));
130        assert!(matches!(err, Http11ReadHeadersError::Eof));
131    }
132
133    #[test]
134    fn http10_keep_alive_defaults_false() {
135        let mut coroutine = Http11ReadHeaders::default();
136        let reply = b"HTTP/1.0 200 OK\r\n\r\n";
137        let out = expect_complete_ok(&mut coroutine, Some(reply));
138        assert!(!out.keep_alive);
139        assert_eq!(out.response.version, "HTTP/1.0");
140    }
141
142    #[test]
143    fn http11_keep_alive_defaults_true() {
144        let mut coroutine = Http11ReadHeaders::default();
145        let reply = b"HTTP/1.1 200 OK\r\n\r\n";
146        let out = expect_complete_ok(&mut coroutine, Some(reply));
147        assert!(out.keep_alive);
148    }
149
150    #[test]
151    fn connection_close_overrides_default() {
152        let mut coroutine = Http11ReadHeaders::default();
153        let reply = b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
154        let out = expect_complete_ok(&mut coroutine, Some(reply));
155        assert!(!out.keep_alive);
156    }
157
158    // --- utils
159
160    fn expect_wants_read(cor: &mut Http11ReadHeaders, arg: Option<&[u8]>) {
161        match cor.resume(arg) {
162            HttpCoroutineState::Yielded(HttpYield::WantsRead) => {}
163            state => panic!("expected WantsRead, got {state:?}"),
164        }
165    }
166
167    fn expect_complete_ok(
168        cor: &mut Http11ReadHeaders,
169        arg: Option<&[u8]>,
170    ) -> Http11ReadHeadersOutput {
171        match cor.resume(arg) {
172            HttpCoroutineState::Complete(Ok(out)) => out,
173            state => panic!("expected Complete(Ok), got {state:?}"),
174        }
175    }
176
177    fn expect_complete_err(
178        cor: &mut Http11ReadHeaders,
179        arg: Option<&[u8]>,
180    ) -> Http11ReadHeadersError {
181        match cor.resume(arg) {
182            HttpCoroutineState::Complete(Err(err)) => err,
183            state => panic!("expected Complete(Err), got {state:?}"),
184        }
185    }
186}