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