1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Request router for micro HTTP server.
16 //!
17 //! This module implements a basic request router with matching of URI
18 //! fields. For example
19 //!
20 //! router.add_route("/user/{id})", handle_user);
21 //!
22 //! will register a handler that matches user ids.
23 //!
24 //! This library is only used for serving the netsim client and is not
25 //! meant to implement all aspects of an http router.
26
27 use crate::http_server::server_response::ResponseWritable;
28
29 use http::{Request, Uri};
30
31 type RequestHandler = Box<dyn Fn(&Request<Vec<u8>>, &str, ResponseWritable)>;
32
33 pub struct Router {
34 routes: Vec<(Uri, RequestHandler)>,
35 }
36
37 impl Router {
new() -> Router38 pub fn new() -> Router {
39 Router { routes: Vec::new() }
40 }
41
add_route(&mut self, route: Uri, handler: RequestHandler)42 pub fn add_route(&mut self, route: Uri, handler: RequestHandler) {
43 self.routes.push((route, handler));
44 }
45
handle_request(&self, request: &Request<Vec<u8>>, writer: ResponseWritable)46 pub fn handle_request(&self, request: &Request<Vec<u8>>, writer: ResponseWritable) {
47 for (route, handler) in &self.routes {
48 if let Some(param) = match_route(
49 route.path().to_string().as_str(),
50 request.uri().path().to_string().as_str(),
51 ) {
52 handler(request, param, writer);
53 return;
54 }
55 }
56 let body = format!("404 Not found (netsim): HttpRouter unknown uri {:?}", request.uri());
57 writer.put_error(404, body.as_str());
58 }
59 }
60
61 /// Match the uri against the route and return extracted parameter or
62 /// None.
63 ///
64 /// Example:
65 /// pattern: "/users/{id}/info"
66 /// uri: "/users/33/info"
67 /// result: Some("33")
68 ///
match_route<'a>(route: &str, uri: &'a str) -> Option<&'a str>69 fn match_route<'a>(route: &str, uri: &'a str) -> Option<&'a str> {
70 let open = route.find('{');
71 let close = route.find('}');
72
73 // check for literal routes with no parameter
74 if open.is_none() && close.is_none() {
75 return if route == uri { Some("") } else { None };
76 }
77
78 // check for internal errors in the app's route table.
79 if open.is_none() || close.is_none() || open.unwrap() > close.unwrap() {
80 panic!("Malformed route pattern: {route}");
81 }
82
83 // check for match of route like "/user/{id}/info"
84 let open = open.unwrap();
85 let close = close.unwrap();
86 let prefix = &route[0..open];
87 let suffix = &route[close + 1..];
88
89 if uri.starts_with(prefix) && uri.ends_with(suffix) {
90 Some(&uri[prefix.len()..(uri.len() - suffix.len())])
91 } else {
92 None
93 }
94 }
95
96 #[cfg(test)]
97 mod tests {
98 use super::*;
99 use crate::http_server::server_response::ServerResponseWriter;
100 use http::Version;
101 use std::io::Cursor;
102
handle_index(_request: &Request<Vec<u8>>, _param: &str, writer: ResponseWritable)103 fn handle_index(_request: &Request<Vec<u8>>, _param: &str, writer: ResponseWritable) {
104 writer.put_ok_with_vec("text/html", b"Hello, world!".to_vec(), vec![]);
105 }
106
handle_user(_request: &Request<Vec<u8>>, user_id: &str, writer: ResponseWritable)107 fn handle_user(_request: &Request<Vec<u8>>, user_id: &str, writer: ResponseWritable) {
108 let body = format!("Hello, {user_id}!");
109 writer.put_ok("application/json", body.as_str(), vec![]);
110 }
111
handle_query(request: &Request<Vec<u8>>, _param: &str, writer: ResponseWritable)112 fn handle_query(request: &Request<Vec<u8>>, _param: &str, writer: ResponseWritable) {
113 let body = format!("The query is '{}'!", request.uri().query().unwrap());
114 writer.put_ok("text/plain", body.as_str(), vec![]);
115 }
116
117 #[test]
test_match_route()118 fn test_match_route() {
119 assert_eq!(match_route("/user/{id}", "/user/1920"), Some("1920"));
120 assert_eq!(match_route("/user/{id}/info", "/user/123/info"), Some("123"));
121 assert_eq!(match_route("{id}/user/info", "123/user/info"), Some("123"));
122 assert_eq!(match_route("/{id}/", "/123/"), Some("123"));
123 assert_eq!(match_route("/user", "/user"), Some(""));
124 assert_eq!(match_route("/", "/"), Some(""));
125 assert_eq!(match_route("a", "b"), None);
126 assert_eq!(match_route("/{id}", "|123"), None);
127 assert_eq!(match_route("{id}/", "123|"), None);
128 assert_eq!(match_route("/{id}/", "/123|"), None);
129 assert_eq!(match_route("/{id}/", "|123/"), None);
130 }
131
132 #[test]
test_handle_request()133 fn test_handle_request() {
134 let mut router = Router::new();
135 router.add_route(Uri::from_static("/"), Box::new(handle_index));
136 router.add_route(Uri::from_static("/user/{id}"), Box::new(handle_user));
137 let request = Request::builder()
138 .method("GET")
139 .uri("/")
140 .version(Version::HTTP_11)
141 .body(Vec::<u8>::new())
142 .unwrap();
143 let mut stream = Cursor::new(Vec::new());
144 let mut writer = ServerResponseWriter::new(&mut stream);
145 router.handle_request(&request, &mut writer);
146 let written_bytes = stream.get_ref();
147 let expected_bytes =
148 b"HTTP/1.1 200 OK\r\ncontent-type: text/html\r\ncontent-length: 13\r\n\r\nHello, world!";
149 assert_eq!(written_bytes, expected_bytes);
150
151 let request = Request::builder()
152 .method("GET")
153 .uri("/user/1920")
154 .version(Version::HTTP_11)
155 .body(Vec::<u8>::new())
156 .unwrap();
157 let mut stream = Cursor::new(Vec::new());
158 let mut writer = ServerResponseWriter::new(&mut stream);
159 router.handle_request(&request, &mut writer);
160 let written_bytes = stream.get_ref();
161 let expected_bytes =
162 b"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: 12\r\n\r\nHello, 1920!";
163 assert_eq!(written_bytes, expected_bytes);
164 }
165
166 #[test]
test_mismatch_uri()167 fn test_mismatch_uri() {
168 let mut router = Router::new();
169 router.add_route(Uri::from_static("/user/{id}"), Box::new(handle_user));
170 let request = Request::builder()
171 .method("GET")
172 .uri("/player/1920")
173 .version(Version::HTTP_11)
174 .body(Vec::<u8>::new())
175 .unwrap();
176 let mut stream = Cursor::new(Vec::new());
177 let mut writer = ServerResponseWriter::new(&mut stream);
178 router.handle_request(&request, &mut writer);
179 let written_bytes = stream.get_ref();
180 let expected_bytes =
181 b"HTTP/1.1 404 Not Found\r\ncontent-type: text/plain\r\ncontent-length: 59\r\n\r\n404 Not found (netsim): HttpRouter unknown uri /player/1920";
182 assert_eq!(written_bytes, expected_bytes);
183 }
184
185 #[test]
test_handle_query()186 fn test_handle_query() {
187 let mut router = Router::new();
188 router.add_route(Uri::from_static("/user"), Box::new(handle_query));
189 let request = Request::builder()
190 .method("GET")
191 .uri("/user?name=hello")
192 .version(Version::HTTP_11)
193 .body(Vec::<u8>::new())
194 .unwrap();
195 let mut stream = Cursor::new(Vec::new());
196 let mut writer = ServerResponseWriter::new(&mut stream);
197 router.handle_request(&request, &mut writer);
198 let written_bytes = stream.get_ref();
199 let expected_bytes =
200 b"HTTP/1.1 200 OK\r\ncontent-type: text/plain\r\ncontent-length: 26\r\n\r\nThe query is 'name=hello'!";
201 assert_eq!(written_bytes, expected_bytes);
202 }
203 }
204