• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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