request_http_parser/
parser.rs1use anyhow::{Context, Result, anyhow};
4use std::collections::HashMap;
5
6#[derive(Debug, PartialEq, Eq)]
7pub enum Method {
8 GET,
9 POST,
10 OPTIONS,
11}
12
13impl TryFrom<&str> for Method {
14 type Error = anyhow::Error;
15 fn try_from(value: &str) -> Result<Self, anyhow::Error> {
16 match value {
17 "GET" => Ok(Method::GET),
18 "POST" => Ok(Method::POST),
19 "OPTIONS" => Ok(Method::OPTIONS),
20 _ => Err(anyhow!("Method not supported")),
21 }
22 }
23}
24
25pub struct Request {
26 pub method: Method,
27 pub path: String,
28 pub params: Option<std::collections::HashMap<String, String>>,
29 pub headers: std::collections::HashMap<String, String>,
30 pub body: Option<String>,
31}
32
33impl Request {
34 pub fn new(request: &str) -> Result<Self> {
52 let mut parts = request.split("\r\n\r\n");
53 let head = parts.next().context("Headline Error")?;
54 let body = parts.next().map(|b| b.to_string());
56
57 let mut head_line = head.lines();
59 let first: &str = head_line.next().context("Empty Request")?;
60 let mut request_parts: std::str::SplitWhitespace<'_> = first.split_whitespace();
61 let method: Method = request_parts
62 .next()
63 .ok_or(anyhow!("missing method"))
64 .and_then(TryInto::try_into)
65 .context("Missing Method")?;
66 let url = request_parts.next().context("No Path")?;
67 let (path, params) = Self::extract_query_param(url);
68
69 let mut headers = HashMap::new();
71 for line in head_line {
72 if let Some((k, v)) = line.split_once(":") {
73 headers.insert(k.trim().to_lowercase(), v.trim().to_string());
74 }
75 }
76 Ok(Request {
77 method,
78 path,
79 headers,
80 body,
81 params,
82 })
83 }
84
85 fn extract_query_param(url: &str) -> (String, Option<HashMap<String, String>>) {
87 if let Some(pos) = url.find('?') {
89 let path = &url[0..pos];
90 let query_string = &url[pos + 1..]; let params: HashMap<_, _> = query_string
94 .split('&')
95 .filter_map(|pair| {
96 let mut kv = pair.split('=');
97 Some((kv.next()?.to_string(), kv.next()?.to_string()))
98 })
99 .collect();
100
101 (path.to_string(), Some(params))
103 } else {
105 (url.to_string(), None)
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use crate::parser::Method;
113
114 use super::Request;
115
116 #[test]
117 fn parser() {
118 let req_str = format!(
119 "POST /login HTTP/1.1\r\n\
120 Content-Type: application/json\r\n\
121 User-Agent: Test\r\n\
122 Content-Length: {}\r\n\
123 \r\n\
124 {{\"username\": \"{}\",\"password\": \"{}\"}}",
125 44, "crisandolin", "rumahorbo"
126 );
127
128 let req = Request::new(&req_str).unwrap();
129
130 assert_eq!(Method::POST, req.method);
131 assert_eq!("/login", req.path);
132 }
133}