From 0d87a94c8e5a76499fbd1e4acb20a5f4406e6ad1 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 23 Mar 2020 21:27:24 +0800 Subject: [PATCH] Route request according to {method, path} tuple Route request according to {method, path} tuple, so we could use only one router for each http server. Signed-off-by: Liu Jiang --- coverage_config.json | 2 +- src/common/mod.rs | 21 ++++++++++++ src/router.rs | 77 +++++++++++++++++++++++++++++++++----------- 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/coverage_config.json b/coverage_config.json index d74cfa3..4f21e4e 100644 --- a/coverage_config.json +++ b/coverage_config.json @@ -1 +1 @@ -{"coverage_score": 93.1, "exclude_path": "", "crate_features": ""} \ No newline at end of file +{"coverage_score": 93.2, "exclude_path": "", "crate_features": ""} diff --git a/src/common/mod.rs b/src/common/mod.rs index 609f801..bb134e1 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -177,6 +177,15 @@ impl Method { Method::Patch => b"PATCH", } } + + /// Returns an &str corresponding to the Method. + pub fn to_str(self) -> &'static str { + match self { + Method::Get => "GET", + Method::Put => "PUT", + Method::Patch => "PATCH", + } + } } /// Supported HTTP Versions. @@ -367,4 +376,16 @@ mod tests { "IO error: Resource temporarily unavailable (os error 11)" ); } + + #[test] + fn test_method_to_str() { + let val = Method::Get; + assert_eq!(val.to_str(), "GET"); + + let val = Method::Put; + assert_eq!(val.to_str(), "PUT"); + + let val = Method::Patch; + assert_eq!(val.to_str(), "PATCH"); + } } diff --git a/src/router.rs b/src/router.rs index e0457dc..05e03bb 100644 --- a/src/router.rs +++ b/src/router.rs @@ -3,9 +3,9 @@ // // SPDX-License-Identifier: Apache-2.0 -use std::collections::hash_map::HashMap; +use std::collections::hash_map::{Entry, HashMap}; -use crate::{MediaType, Request, Response, StatusCode, Version}; +use crate::{MediaType, Method, Request, Response, StatusCode, Version}; pub use crate::common::RouteError; @@ -35,24 +35,62 @@ impl HttpRoutes { } } - /// Register a request handler for a path. + /// Register a request handler for a unique (HTTP_METHOD, HTTP_PATH) tuple. + /// + /// # Arguments + /// * `method`: HTTP method to assoicate with the handler. + /// * `path`: HTTP path to associate with the handler. + /// * `handler`: HTTP request handler for the (method, path) tuple. pub fn add_route( &mut self, + method: Method, path: String, handler: Box + Sync + Send>, ) -> Result<(), RouteError> { - let full = format!("{}{}", self.prefix, path); - if self.routes.contains_key(&full) { - Err(RouteError::HandlerExist(full)) - } else { - self.routes.insert(full, handler); - Ok(()) + let full_path = format!("{}:{}{}", method.to_str(), self.prefix, path); + match self.routes.entry(full_path.clone()) { + Entry::Occupied(_) => Err(RouteError::HandlerExist(full_path)), + Entry::Vacant(entry) => { + entry.insert(handler); + Ok(()) + } } } /// Handle an incoming http request and generate corresponding response. - pub fn handle_http_request(&self, request: &Request, argument: T) -> Response { - let path = request.uri().get_abs_path().to_string(); + /// + /// # Examples + /// + /// ``` + /// extern crate micro_http; + /// use micro_http::{ + /// EndpointHandler, HttpRoutes, Method, StatusCode, Request, Response, Version + /// }; + /// + /// struct HandlerArg(bool); + /// struct MockHandler {} + /// impl EndpointHandler for MockHandler { + /// fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response { + /// Response::new(Version::Http11, StatusCode::OK) + /// } + /// } + /// + /// let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string()); + /// let handler = MockHandler {}; + /// router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)).unwrap(); + /// + /// let request = + /// Request::try_from(b"GET http://localhost/api/v1/func1 HTTP/1.1\r\n\r\n").unwrap(); + /// let arg = HandlerArg(true); + /// let reply = router.handle_http_request(&request, &arg); + /// assert_eq!(reply.status(), StatusCode::OK); + /// ``` + pub fn handle_http_request(&self, request: &Request, argument: &T) -> Response { + let path = format!( + "{}:{}", + request.method().to_str(), + request.uri().get_abs_path() + ); let mut response = match self.routes.get(&path) { Some(route) => route.handle_request(&request, &argument), None => Response::new(Version::Http11, StatusCode::NotFound), @@ -82,19 +120,22 @@ mod tests { fn test_create_router() { let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string()); let handler = MockHandler {}; - let res = router.add_route("/func1".to_string(), Box::new(handler)); + let res = router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)); assert!(res.is_ok()); - let key = format!("{}{}", "/api/v1", "/func1"); - assert!(router.routes.contains_key(&key)); + assert!(router.routes.contains_key("GET:/api/v1/func1")); let handler = MockHandler {}; - match router.add_route("/func1".to_string(), Box::new(handler)) { + match router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)) { Err(RouteError::HandlerExist(_)) => {} _ => panic!("add_route() should return error for path with existing handler"), } let handler = MockHandler {}; - let res = router.add_route("/func2".to_string(), Box::new(handler)); + let res = router.add_route(Method::Put, "/func1".to_string(), Box::new(handler)); + assert!(res.is_ok()); + + let handler = MockHandler {}; + let res = router.add_route(Method::Get, "/func2".to_string(), Box::new(handler)); assert!(res.is_ok()); } @@ -103,13 +144,13 @@ mod tests { let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string()); let handler = MockHandler {}; router - .add_route("/func1".to_string(), Box::new(handler)) + .add_route(Method::Get, "/func1".to_string(), Box::new(handler)) .unwrap(); let request = Request::try_from(b"GET http://localhost/api/v1/func2 HTTP/1.1\r\n\r\n").unwrap(); let arg = HandlerArg(true); - let reply = router.handle_http_request(&request, arg); + let reply = router.handle_http_request(&request, &arg); assert_eq!(reply.status(), StatusCode::NotFound); } }