front_line_router/
router.rs1use crate::http_version::HttpVersion;
2use crate::method::Method;
3use crate::RouterResult;
4use memchr::memmem;
5
6#[derive(thiserror::Error, PartialEq, Debug)]
7pub enum Error {
8 #[error("the http request line was not valid")]
9 InvalidRequestLine,
10}
11
12pub trait Router<'de>: Sized {
22 fn handle_parsed(method: Method, remaining_path: &'de str) -> Option<Self>;
37
38 fn resolve(request: &'de [u8]) -> Result<RouterResult<'de, Self>, Error> {
52 let end = memmem::find(request, b"\r\n\r\n").ok_or(Error::InvalidRequestLine)?;
53 let request_line = &request[..end];
54 let (method, after_method) =
55 Method::parse(request_line).ok_or(Error::InvalidRequestLine)?;
56 let full_path_end = memchr::memchr(b' ', after_method).ok_or(Error::InvalidRequestLine)?;
57 let after_path = &after_method[full_path_end + 1..];
58 let version = HttpVersion::parse(after_path).ok_or(Error::InvalidRequestLine)?;
59 let full_path = &after_method[..full_path_end];
60 let query_start = memchr::memchr(b'?', full_path).unwrap_or(full_path.len());
61 let query_bytes = &full_path[full_path.len().min(query_start + 1)..];
62 let query = std::str::from_utf8(query_bytes).map_err(|_| Error::InvalidRequestLine)?;
63 let path_bytes = &full_path[..query_start];
64 let path = std::str::from_utf8(path_bytes).map_err(|_| Error::InvalidRequestLine)?;
65 let route = Self::handle_parsed(method, path);
66 let head_and_body = &request[end + 4..];
67 let result = RouterResult {
68 route,
69 query,
70 version,
71 head_and_body,
72 };
73 Ok(result)
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use rstest::rstest;
81
82 #[derive(PartialEq, Debug)]
83 enum TestRoute {
84 Test,
85 }
86
87 impl<'de> Router<'de> for TestRoute {
88 fn handle_parsed(method: Method, remaining_path: &'de str) -> Option<Self> {
89 match (method, remaining_path) {
90 (Method::Get, "/test") => Some(TestRoute::Test),
91 _ => None,
92 }
93 }
94 }
95
96 #[rstest]
97 #[case(
98 b"GET /test HTTP/1.1\r\n\r\nSome data",
99 Ok(RouterResult {
100 route: Some(TestRoute::Test),
101 query: "",
102 version: HttpVersion::OneOne,
103 head_and_body: b"Some data",
104 })
105 )]
106 #[case(
107 b"GET /test?query=value HTTP/1.1\r\n\r\n",
108 Ok(RouterResult {
109 route: Some(TestRoute::Test),
110 query: "query=value",
111 version: HttpVersion::OneOne,
112 head_and_body: b"",
113 })
114 )]
115 #[case(
116 b"GET /test HTTP/1.0\r\n\r\n",
117 Ok(RouterResult {
118 route: Some(TestRoute::Test),
119 query: "",
120 version: HttpVersion::OneZero,
121 head_and_body: b"",
122 })
123 )]
124 #[case(
125 b"POST /test HTTP/1.1\r\n\r\n",
126 Ok(RouterResult {
127 route: None,
128 query: "",
129 version: HttpVersion::OneOne,
130 head_and_body: b"",
131 })
132 )]
133 #[case(
134 b"GET /invalid HTTP/1.1\r\n\r\n",
135 Ok(RouterResult {
136 route: None,
137 query: "",
138 version: HttpVersion::OneOne,
139 head_and_body: b"",
140 })
141 )]
142 #[case(
143 b"GET /invalid?key=value HTTP/1.1\r\n\r\n",
144 Ok(RouterResult {
145 route: None,
146 query: "key=value",
147 version: HttpVersion::OneOne,
148 head_and_body: b"",
149 })
150 )]
151 #[case(
152 b"GET /invalid?key=value HTTP/1.1\r\n\r\nheader-section",
153 Ok(RouterResult {
154 route: None,
155 query: "key=value",
156 version: HttpVersion::OneOne,
157 head_and_body: b"header-section",
158 })
159 )]
160 #[case(b"GET /test HTT/1.1\r\n\r\n", Err(Error::InvalidRequestLine))]
161 #[case(b"GET /test", Err(Error::InvalidRequestLine))]
162 #[case(b"GET/test HTTP/1.1\r\n\r\n", Err(Error::InvalidRequestLine))]
163 #[case(b"GET /test HTTP/1.1\r\nSome data", Err(Error::InvalidRequestLine))]
164 fn test_route(
165 #[case] input: &[u8],
166 #[case] expected_result: Result<RouterResult<'_, TestRoute>, Error>,
167 ) {
168 let result = TestRoute::resolve(input);
169 assert_eq!(result, expected_result);
170 }
171}