• 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::http_request::HttpRequest;
28 
29 use crate::http_server::server_response::ResponseWritable;
30 
31 type RequestHandler = Box<dyn Fn(&HttpRequest, &str, ResponseWritable)>;
32 
33 pub struct Router {
34     routes: Vec<(String, RequestHandler)>,
35 }
36 
37 impl Router {
new() -> Router38     pub fn new() -> Router {
39         Router { routes: Vec::new() }
40     }
41 
add_route(&mut self, route: &str, handler: RequestHandler)42     pub fn add_route(&mut self, route: &str, handler: RequestHandler) {
43         self.routes.push((route.to_owned(), handler));
44     }
45 
handle_request(&self, request: &HttpRequest, writer: ResponseWritable)46     pub fn handle_request(&self, request: &HttpRequest, writer: ResponseWritable) {
47         for (route, handler) in &self.routes {
48             if let Some(param) = match_route(route, &request.uri) {
49                 handler(request, param, writer);
50                 return;
51             }
52         }
53         let body = format!("404 Not found (netsim): HttpRouter unknown uri {}", request.uri);
54         writer.put_error(404, body.as_str());
55     }
56 }
57 
58 /// Match the uri against the route and return extracted parameter or
59 /// None.
60 ///
61 /// Example:
62 ///   pattern: "/users/{id}/info"
63 ///   uri: "/users/33/info"
64 ///   result: Some("33")
65 ///
match_route<'a>(route: &str, uri: &'a str) -> Option<&'a str>66 fn match_route<'a>(route: &str, uri: &'a str) -> Option<&'a str> {
67     let open = route.find('{');
68     let close = route.find('}');
69 
70     // check for literal routes with no parameter
71     if open.is_none() && close.is_none() {
72         return if route == uri { Some("") } else { None };
73     }
74 
75     // check for internal errors in the app's route table.
76     if open.is_none() || close.is_none() || open.unwrap() > close.unwrap() {
77         panic!("Malformed route pattern: {route}");
78     }
79 
80     // check for match of route like "/user/{id}/info"
81     let open = open.unwrap();
82     let close = close.unwrap();
83     let prefix = &route[0..open];
84     let suffix = &route[close + 1..];
85 
86     if uri.starts_with(prefix) && uri.ends_with(suffix) {
87         Some(&uri[prefix.len()..(uri.len() - suffix.len())])
88     } else {
89         None
90     }
91 }
92 
93 #[cfg(test)]
94 mod tests {
95     use super::*;
96     use crate::http_server::http_request::HttpHeaders;
97     use crate::http_server::server_response::ServerResponseWriter;
98     use std::io::Cursor;
99 
handle_index(_request: &HttpRequest, _param: &str, writer: ResponseWritable)100     fn handle_index(_request: &HttpRequest, _param: &str, writer: ResponseWritable) {
101         writer.put_ok_with_vec("text/html", b"Hello, world!".to_vec(), &[]);
102     }
103 
handle_user(_request: &HttpRequest, user_id: &str, writer: ResponseWritable)104     fn handle_user(_request: &HttpRequest, user_id: &str, writer: ResponseWritable) {
105         let body = format!("Hello, {user_id}!");
106         writer.put_ok("application/json", body.as_str(), &[]);
107     }
108 
109     #[test]
test_match_route()110     fn test_match_route() {
111         assert_eq!(match_route("/user/{id}", "/user/1920"), Some("1920"));
112         assert_eq!(match_route("/user/{id}/info", "/user/123/info"), Some("123"));
113         assert_eq!(match_route("{id}/user/info", "123/user/info"), Some("123"));
114         assert_eq!(match_route("/{id}/", "/123/"), Some("123"));
115         assert_eq!(match_route("/user", "/user"), Some(""));
116         assert_eq!(match_route("/", "/"), Some(""));
117         assert_eq!(match_route("a", "b"), None);
118         assert_eq!(match_route("/{id}", "|123"), None);
119         assert_eq!(match_route("{id}/", "123|"), None);
120         assert_eq!(match_route("/{id}/", "/123|"), None);
121         assert_eq!(match_route("/{id}/", "|123/"), None);
122     }
123 
124     #[test]
test_handle_request()125     fn test_handle_request() {
126         let mut router = Router::new();
127         router.add_route("/", Box::new(handle_index));
128         router.add_route("/user/{id}", Box::new(handle_user));
129         let request = HttpRequest {
130             method: "GET".to_string(),
131             uri: "/".to_string(),
132             version: "HTTP/1.1".to_string(),
133             headers: HttpHeaders::new(),
134             body: vec![],
135         };
136         let mut stream = Cursor::new(Vec::new());
137         let mut writer = ServerResponseWriter::new(&mut stream);
138         router.handle_request(&request, &mut writer);
139         let written_bytes = stream.get_ref();
140         let expected_bytes =
141             b"HTTP/1.1 200\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\nHello, world!";
142         assert_eq!(written_bytes, expected_bytes);
143 
144         let request = HttpRequest {
145             method: "GET".to_string(),
146             uri: "/user/1920".to_string(),
147             version: "HTTP/1.1".to_string(),
148             headers: HttpHeaders::new(),
149             body: vec![],
150         };
151         let mut stream = Cursor::new(Vec::new());
152         let mut writer = ServerResponseWriter::new(&mut stream);
153         router.handle_request(&request, &mut writer);
154         let written_bytes = stream.get_ref();
155         let expected_bytes =
156             b"HTTP/1.1 200\r\nContent-Type: application/json\r\nContent-Length: 12\r\n\r\nHello, 1920!";
157         assert_eq!(written_bytes, expected_bytes);
158     }
159 
160     #[test]
test_mismatch_uri()161     fn test_mismatch_uri() {
162         let mut router = Router::new();
163         router.add_route("/user/{id}", Box::new(handle_user));
164         let request = HttpRequest {
165             method: "GET".to_string(),
166             uri: "/player/1920".to_string(),
167             version: "HTTP/1.1".to_string(),
168             headers: HttpHeaders::new(),
169             body: vec![],
170         };
171         let mut stream = Cursor::new(Vec::new());
172         let mut writer = ServerResponseWriter::new(&mut stream);
173         router.handle_request(&request, &mut writer);
174         let written_bytes = stream.get_ref();
175         let expected_bytes =
176             b"HTTP/1.1 404\r\nContent-Type: text/plain\r\nContent-Length: 59\r\n\r\n404 Not found (netsim): HttpRouter unknown uri /player/1920";
177         assert_eq!(written_bytes, expected_bytes);
178     }
179 }
180