Skip to main content

rns_ctl/
http.rs

1use std::collections::HashMap;
2use std::io::{self, BufRead, BufReader, Read, Write};
3
4/// Parsed HTTP request.
5pub struct HttpRequest {
6    pub method: String,
7    pub path: String,
8    pub query: String,
9    pub headers: HashMap<String, String>,
10    pub body: Vec<u8>,
11}
12
13/// HTTP response.
14pub struct HttpResponse {
15    pub status: u16,
16    pub status_text: &'static str,
17    pub headers: Vec<(String, String)>,
18    pub body: Vec<u8>,
19}
20
21impl HttpResponse {
22    pub fn json(status: u16, status_text: &'static str, body: &serde_json::Value) -> Self {
23        let body_bytes = serde_json::to_vec(body).unwrap_or_default();
24        HttpResponse {
25            status,
26            status_text,
27            headers: vec![
28                ("Content-Type".into(), "application/json".into()),
29                ("Content-Length".into(), body_bytes.len().to_string()),
30                ("Connection".into(), "close".into()),
31            ],
32            body: body_bytes,
33        }
34    }
35
36    pub fn ok(body: serde_json::Value) -> Self {
37        Self::json(200, "OK", &body)
38    }
39
40    pub fn html(body: &str) -> Self {
41        Self::bytes(
42            200,
43            "OK",
44            "text/html; charset=utf-8",
45            body.as_bytes().to_vec(),
46        )
47    }
48
49    pub fn created(body: serde_json::Value) -> Self {
50        Self::json(201, "Created", &body)
51    }
52
53    pub fn bad_request(msg: &str) -> Self {
54        Self::json(400, "Bad Request", &serde_json::json!({"error": msg}))
55    }
56
57    pub fn unauthorized(msg: &str) -> Self {
58        Self::json(401, "Unauthorized", &serde_json::json!({"error": msg}))
59    }
60
61    pub fn not_found() -> Self {
62        Self::json(404, "Not Found", &serde_json::json!({"error": "Not found"}))
63    }
64
65    pub fn internal_error(msg: &str) -> Self {
66        Self::json(
67            500,
68            "Internal Server Error",
69            &serde_json::json!({"error": msg}),
70        )
71    }
72
73    pub fn bytes(
74        status: u16,
75        status_text: &'static str,
76        content_type: &'static str,
77        body: Vec<u8>,
78    ) -> Self {
79        HttpResponse {
80            status,
81            status_text,
82            headers: vec![
83                ("Content-Type".into(), content_type.into()),
84                ("Content-Length".into(), body.len().to_string()),
85                ("Connection".into(), "close".into()),
86            ],
87            body,
88        }
89    }
90}
91
92/// Parse an HTTP/1.1 request from a stream.
93pub fn parse_request(stream: &mut dyn Read) -> io::Result<HttpRequest> {
94    let mut reader = BufReader::new(stream);
95
96    // Read request line
97    let mut request_line = String::new();
98    reader.read_line(&mut request_line)?;
99    let request_line = request_line.trim_end();
100
101    if request_line.is_empty() {
102        return Err(io::Error::new(
103            io::ErrorKind::UnexpectedEof,
104            "Empty request",
105        ));
106    }
107
108    let parts: Vec<&str> = request_line.splitn(3, ' ').collect();
109    if parts.len() < 2 {
110        return Err(io::Error::new(
111            io::ErrorKind::InvalidData,
112            "Invalid request line",
113        ));
114    }
115
116    let method = parts[0].to_string();
117    let full_path = parts[1];
118
119    let (path, query) = if let Some(pos) = full_path.find('?') {
120        (
121            full_path[..pos].to_string(),
122            full_path[pos + 1..].to_string(),
123        )
124    } else {
125        (full_path.to_string(), String::new())
126    };
127
128    // Read headers
129    let mut headers = HashMap::new();
130    loop {
131        let mut line = String::new();
132        reader.read_line(&mut line)?;
133        let line = line.trim_end();
134        if line.is_empty() {
135            break;
136        }
137        if let Some(colon) = line.find(':') {
138            let key = line[..colon].trim().to_lowercase();
139            let value = line[colon + 1..].trim().to_string();
140            headers.insert(key, value);
141        }
142    }
143
144    // Read body based on Content-Length
145    let body = if let Some(len_str) = headers.get("content-length") {
146        if let Ok(len) = len_str.parse::<usize>() {
147            let mut body = vec![0u8; len];
148            reader.read_exact(&mut body)?;
149            body
150        } else {
151            Vec::new()
152        }
153    } else {
154        Vec::new()
155    };
156
157    Ok(HttpRequest {
158        method,
159        path,
160        query,
161        headers,
162        body,
163    })
164}
165
166/// Write an HTTP response to a stream.
167pub fn write_response(stream: &mut dyn Write, response: &HttpResponse) -> io::Result<()> {
168    write!(
169        stream,
170        "HTTP/1.1 {} {}\r\n",
171        response.status, response.status_text
172    )?;
173    for (key, value) in &response.headers {
174        write!(stream, "{}: {}\r\n", key, value)?;
175    }
176    write!(stream, "\r\n")?;
177    stream.write_all(&response.body)?;
178    stream.flush()
179}
180
181/// Parse query string parameters.
182pub fn parse_query(query: &str) -> HashMap<String, String> {
183    let mut params = HashMap::new();
184    if query.is_empty() {
185        return params;
186    }
187    for pair in query.split('&') {
188        if let Some(eq) = pair.find('=') {
189            let key = pair[..eq].to_string();
190            let value = pair[eq + 1..].to_string();
191            params.insert(key, value);
192        } else if !pair.is_empty() {
193            params.insert(pair.to_string(), String::new());
194        }
195    }
196    params
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn parse_query_basic() {
205        let q = parse_query("foo=bar&baz=123&flag");
206        assert_eq!(q.get("foo").unwrap(), "bar");
207        assert_eq!(q.get("baz").unwrap(), "123");
208        assert!(q.contains_key("flag"));
209    }
210
211    #[test]
212    fn parse_query_empty() {
213        let q = parse_query("");
214        assert!(q.is_empty());
215    }
216
217    #[test]
218    fn parse_request_get() {
219        let raw = b"GET /api/info?verbose=true HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer abc\r\n\r\n";
220        let req = parse_request(&mut &raw[..]).unwrap();
221        assert_eq!(req.method, "GET");
222        assert_eq!(req.path, "/api/info");
223        assert_eq!(req.query, "verbose=true");
224        assert_eq!(req.headers.get("authorization").unwrap(), "Bearer abc");
225        assert!(req.body.is_empty());
226    }
227
228    #[test]
229    fn parse_request_post_with_body() {
230        let body = r#"{"key":"value"}"#;
231        let raw = format!(
232            "POST /api/send HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}",
233            body.len(),
234            body
235        );
236        let req = parse_request(&mut raw.as_bytes()).unwrap();
237        assert_eq!(req.method, "POST");
238        assert_eq!(req.path, "/api/send");
239        assert_eq!(req.body, body.as_bytes());
240    }
241
242    #[test]
243    fn response_json() {
244        let resp = HttpResponse::ok(serde_json::json!({"status": "ok"}));
245        assert_eq!(resp.status, 200);
246        let body: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
247        assert_eq!(body["status"], "ok");
248    }
249}