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 && let Some(host) = self.url.host_str() {
42            bytes.extend(HTTP_HOST.as_bytes());
43            bytes.extend(b": ");
44            bytes.extend(host.as_bytes());
45            // NOTE: `Url` drops the port when it is the scheme
46            // default, which is exactly when Host must omit it too.
47            if let Some(port) = self.url.port() {
48                bytes.extend(format!(":{port}").as_bytes());
49            }
50            bytes.extend(CRLF);
51        }
52
53        for (key, val) in &self.headers {
54            if key.eq_ignore_ascii_case(HTTP_CONTENT_LENGTH) {
55                continue;
56            }
57
58            bytes.extend(key.as_bytes());
59            bytes.extend(b": ");
60            bytes.extend(val.as_bytes());
61            bytes.extend(CRLF);
62        }
63
64        let body_len = format!("{}", self.body.len());
65        bytes.extend(HTTP_CONTENT_LENGTH.as_bytes());
66        bytes.extend(b": ");
67        bytes.extend(body_len.as_bytes());
68        bytes.extend(CRLF_CRLF);
69        bytes.extend(&self.body);
70
71        bytes
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use alloc::string::String;
78
79    use url::Url;
80
81    use crate::rfc9110::request::HttpRequest;
82
83    fn serialize(req: &HttpRequest) -> String {
84        String::from_utf8(req.to_http_11_vec()).unwrap()
85    }
86
87    #[test]
88    fn host_generated_from_url() {
89        let req = HttpRequest::get(Url::parse("https://example.com/x").unwrap());
90        assert!(serialize(&req).contains("host: example.com\r\n"));
91    }
92
93    #[test]
94    fn host_keeps_non_default_port() {
95        let req = HttpRequest::get(Url::parse("https://example.com:8843/").unwrap());
96        assert!(serialize(&req).contains("host: example.com:8843\r\n"));
97    }
98
99    #[test]
100    fn host_omits_default_port() {
101        let req = HttpRequest::get(Url::parse("https://example.com:443/").unwrap());
102        assert!(serialize(&req).contains("host: example.com\r\n"));
103    }
104
105    #[test]
106    fn host_not_duplicated_when_set_by_caller() {
107        let req = HttpRequest::get(Url::parse("https://example.com/").unwrap())
108            .header("Host", "override.example");
109        let message = serialize(&req);
110        assert!(message.contains("Host: override.example\r\n"));
111        assert!(!message.contains("host: example.com\r\n"));
112    }
113}