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 PUT,
11 PATCH,
12 DELETE,
13 HEAD,
14 OPTIONS,
15}
16
17impl TryFrom<&str> for Method {
18 type Error = anyhow::Error;
19 fn try_from(value: &str) -> Result<Self, anyhow::Error> {
20 match value {
21 "GET" => Ok(Method::GET),
22 "POST" => Ok(Method::POST),
23 "PUT" => Ok(Method::PUT),
24 "PATCH" => Ok(Method::PATCH),
25 "DELETE" => Ok(Method::DELETE),
26 "HEAD" => Ok(Method::HEAD),
27 "OPTIONS" => Ok(Method::OPTIONS),
28 _ => Err(anyhow!("Method not supported")),
29 }
30 }
31}
32
33pub struct Request {
34 pub method: Method,
35 pub path: String,
36 pub params: Option<std::collections::HashMap<String, String>>,
37 pub headers: std::collections::HashMap<String, String>,
38 pub body: Option<String>,
39}
40
41impl Request {
42 pub fn new(request: &str) -> Result<Self> {
60 let mut parts = request.split("\r\n\r\n");
61 let head = parts.next().context("Headline Error")?;
62 let body = parts.next().map(|b| b.to_string());
64
65 let mut head_line = head.lines();
67 let first: &str = head_line.next().context("Empty Request")?;
68 let mut request_parts: std::str::SplitWhitespace<'_> = first.split_whitespace();
69 let method: Method = request_parts
70 .next()
71 .ok_or(anyhow!("missing method"))
72 .and_then(TryInto::try_into)
73 .context("Missing Method")?;
74 let url = request_parts.next().context("No Path")?;
75 let (path, params) = Self::extract_query_param(url);
76
77 let mut headers = HashMap::new();
79 for line in head_line {
80 if let Some((k, v)) = line.split_once(":") {
81 headers.insert(k.trim().to_lowercase(), v.trim().to_string());
82 }
83 }
84 Ok(Request {
85 method,
86 path,
87 headers,
88 body,
89 params,
90 })
91 }
92
93 fn extract_query_param(url: &str) -> (String, Option<HashMap<String, String>>) {
95 if let Some(pos) = url.find('?') {
97 let path = &url[0..pos];
98 let query_string = &url[pos + 1..]; let params: HashMap<_, _> = query_string
102 .split('&')
103 .filter_map(|pair| {
104 let mut kv = pair.split('=');
105 Some((kv.next()?.to_string(), kv.next()?.to_string()))
106 })
107 .collect();
108
109 (path.to_string(), Some(params))
111 } else {
113 (url.to_string(), None)
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use crate::parser::Method;
121
122 use super::Request;
123
124 #[test]
125 fn parser() {
126 let req_str = format!(
127 "POST /login HTTP/1.1\r\n\
128 Content-Type: application/json\r\n\
129 User-Agent: Test\r\n\
130 Content-Length: {}\r\n\
131 \r\n\
132 {{\"username\": \"{}\",\"password\": \"{}\"}}",
133 44, "crisandolin", "rumahorbo"
134 );
135
136 let req = Request::new(&req_str).unwrap();
137
138 assert_eq!(Method::POST, req.method);
139 assert_eq!("/login", req.path);
140 }
141}