Skip to main content

io_http/rfc9112/
request.rs

1//! HTTP/1.1 request serialisation onto the wire ([RFC 9112 §3]).
2//!
3//! [RFC 9112 §3]: https://www.rfc-editor.org/rfc/rfc9112#section-3
4
5use alloc::{format, vec::Vec};
6
7use crate::{
8    rfc9110::{
9        chars::{CRLF, CRLF_CRLF, SP},
10        headers::{HTTP_CONTENT_LENGTH, HTTP_HOST},
11        request::HttpRequest,
12    },
13    rfc9112::version::HTTP_11,
14};
15
16impl HttpRequest {
17    /// Serialises this request as an HTTP/1.1 message; `Content-Length`
18    /// is regenerated from the body and any existing copy is dropped,
19    /// and a `Host` is generated from the URL (RFC 9112 §3.2) when the
20    /// caller did not set one.
21    pub fn to_http_11_vec(&self) -> Vec<u8> {
22        let mut bytes = Vec::new();
23
24        bytes.extend(self.method.as_bytes());
25        bytes.push(SP);
26        bytes.extend(self.url.path().as_bytes());
27
28        if let Some(q) = self.url.query() {
29            bytes.extend(b"?");
30            bytes.extend(q.as_bytes());
31        }
32
33        bytes.push(SP);
34        bytes.extend(HTTP_11.as_bytes());
35        bytes.extend(CRLF);
36
37        let has_host = self
38            .headers
39            .iter()
40            .any(|(k, _)| k.eq_ignore_ascii_case(HTTP_HOST));
41        if !has_host {
42            if let Some(host) = self.url.host_str() {
43                bytes.extend(HTTP_HOST.as_bytes());
44                bytes.extend(b": ");
45                bytes.extend(host.as_bytes());
46                // NOTE: `Url` drops the port when it is the scheme
47                // default, which is exactly when Host must omit it too.
48                if let Some(port) = self.url.port() {
49                    bytes.extend(format!(":{port}").as_bytes());
50                }
51                bytes.extend(CRLF);
52            }
53        }
54
55        for (key, val) in &self.headers {
56            if key.eq_ignore_ascii_case(HTTP_CONTENT_LENGTH) {
57                continue;
58            }
59
60            bytes.extend(key.as_bytes());
61            bytes.extend(b": ");
62            bytes.extend(val.as_bytes());
63            bytes.extend(CRLF);
64        }
65
66        let body_len = format!("{}", self.body.len());
67        bytes.extend(HTTP_CONTENT_LENGTH.as_bytes());
68        bytes.extend(b": ");
69        bytes.extend(body_len.as_bytes());
70        bytes.extend(CRLF_CRLF);
71        bytes.extend(&self.body);
72
73        bytes
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use alloc::string::String;
80
81    use url::Url;
82
83    use crate::rfc9110::request::HttpRequest;
84
85    fn serialize(req: &HttpRequest) -> String {
86        String::from_utf8(req.to_http_11_vec()).unwrap()
87    }
88
89    #[test]
90    fn host_generated_from_url() {
91        let req = HttpRequest::get(Url::parse("https://example.com/x").unwrap());
92        assert!(serialize(&req).contains("host: example.com\r\n"));
93    }
94
95    #[test]
96    fn host_keeps_non_default_port() {
97        let req = HttpRequest::get(Url::parse("https://example.com:8843/").unwrap());
98        assert!(serialize(&req).contains("host: example.com:8843\r\n"));
99    }
100
101    #[test]
102    fn host_omits_default_port() {
103        let req = HttpRequest::get(Url::parse("https://example.com:443/").unwrap());
104        assert!(serialize(&req).contains("host: example.com\r\n"));
105    }
106
107    #[test]
108    fn host_not_duplicated_when_set_by_caller() {
109        let req = HttpRequest::get(Url::parse("https://example.com/").unwrap())
110            .header("Host", "override.example");
111        let message = serialize(&req);
112        assert!(message.contains("Host: override.example\r\n"));
113        assert!(!message.contains("host: example.com\r\n"));
114    }
115}