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 created(body: serde_json::Value) -> Self {
41        Self::json(201, "Created", &body)
42    }
43
44    pub fn bad_request(msg: &str) -> Self {
45        Self::json(400, "Bad Request", &serde_json::json!({"error": msg}))
46    }
47
48    pub fn unauthorized(msg: &str) -> Self {
49        Self::json(401, "Unauthorized", &serde_json::json!({"error": msg}))
50    }
51
52    pub fn not_found() -> Self {
53        Self::json(404, "Not Found", &serde_json::json!({"error": "Not found"}))
54    }
55
56    pub fn internal_error(msg: &str) -> Self {
57        Self::json(500, "Internal Server Error", &serde_json::json!({"error": msg}))
58    }
59}
60
61/// Parse an HTTP/1.1 request from a stream.
62pub fn parse_request(stream: &mut dyn Read) -> io::Result<HttpRequest> {
63    let mut reader = BufReader::new(stream);
64
65    // Read request line
66    let mut request_line = String::new();
67    reader.read_line(&mut request_line)?;
68    let request_line = request_line.trim_end();
69
70    if request_line.is_empty() {
71        return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "Empty request"));
72    }
73
74    let parts: Vec<&str> = request_line.splitn(3, ' ').collect();
75    if parts.len() < 2 {
76        return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid request line"));
77    }
78
79    let method = parts[0].to_string();
80    let full_path = parts[1];
81
82    let (path, query) = if let Some(pos) = full_path.find('?') {
83        (full_path[..pos].to_string(), full_path[pos + 1..].to_string())
84    } else {
85        (full_path.to_string(), String::new())
86    };
87
88    // Read headers
89    let mut headers = HashMap::new();
90    loop {
91        let mut line = String::new();
92        reader.read_line(&mut line)?;
93        let line = line.trim_end();
94        if line.is_empty() {
95            break;
96        }
97        if let Some(colon) = line.find(':') {
98            let key = line[..colon].trim().to_lowercase();
99            let value = line[colon + 1..].trim().to_string();
100            headers.insert(key, value);
101        }
102    }
103
104    // Read body based on Content-Length
105    let body = if let Some(len_str) = headers.get("content-length") {
106        if let Ok(len) = len_str.parse::<usize>() {
107            let mut body = vec![0u8; len];
108            reader.read_exact(&mut body)?;
109            body
110        } else {
111            Vec::new()
112        }
113    } else {
114        Vec::new()
115    };
116
117    Ok(HttpRequest {
118        method,
119        path,
120        query,
121        headers,
122        body,
123    })
124}
125
126/// Write an HTTP response to a stream.
127pub fn write_response(stream: &mut dyn Write, response: &HttpResponse) -> io::Result<()> {
128    write!(
129        stream,
130        "HTTP/1.1 {} {}\r\n",
131        response.status, response.status_text
132    )?;
133    for (key, value) in &response.headers {
134        write!(stream, "{}: {}\r\n", key, value)?;
135    }
136    write!(stream, "\r\n")?;
137    stream.write_all(&response.body)?;
138    stream.flush()
139}
140
141/// Parse query string parameters.
142pub fn parse_query(query: &str) -> HashMap<String, String> {
143    let mut params = HashMap::new();
144    if query.is_empty() {
145        return params;
146    }
147    for pair in query.split('&') {
148        if let Some(eq) = pair.find('=') {
149            let key = pair[..eq].to_string();
150            let value = pair[eq + 1..].to_string();
151            params.insert(key, value);
152        } else if !pair.is_empty() {
153            params.insert(pair.to_string(), String::new());
154        }
155    }
156    params
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn parse_query_basic() {
165        let q = parse_query("foo=bar&baz=123&flag");
166        assert_eq!(q.get("foo").unwrap(), "bar");
167        assert_eq!(q.get("baz").unwrap(), "123");
168        assert!(q.contains_key("flag"));
169    }
170
171    #[test]
172    fn parse_query_empty() {
173        let q = parse_query("");
174        assert!(q.is_empty());
175    }
176
177    #[test]
178    fn parse_request_get() {
179        let raw = b"GET /api/info?verbose=true HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer abc\r\n\r\n";
180        let req = parse_request(&mut &raw[..]).unwrap();
181        assert_eq!(req.method, "GET");
182        assert_eq!(req.path, "/api/info");
183        assert_eq!(req.query, "verbose=true");
184        assert_eq!(req.headers.get("authorization").unwrap(), "Bearer abc");
185        assert!(req.body.is_empty());
186    }
187
188    #[test]
189    fn parse_request_post_with_body() {
190        let body = r#"{"key":"value"}"#;
191        let raw = format!(
192            "POST /api/send HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}",
193            body.len(),
194            body
195        );
196        let req = parse_request(&mut raw.as_bytes()).unwrap();
197        assert_eq!(req.method, "POST");
198        assert_eq!(req.path, "/api/send");
199        assert_eq!(req.body, body.as_bytes());
200    }
201
202    #[test]
203    fn response_json() {
204        let resp = HttpResponse::ok(serde_json::json!({"status": "ok"}));
205        assert_eq!(resp.status, 200);
206        let body: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
207        assert_eq!(body["status"], "ok");
208    }
209}