dbs_uhttp/
router.rs

1// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
2// Copyright © 2019 Intel Corporation
3//
4// SPDX-License-Identifier: Apache-2.0
5
6use std::collections::hash_map::{Entry, HashMap};
7
8use crate::{MediaType, Method, Request, Response, StatusCode, Version};
9
10pub use crate::common::RouteError;
11
12/// An HTTP endpoint handler interface
13pub trait EndpointHandler<T>: Sync + Send {
14    /// Handles an HTTP request.
15    fn handle_request(&self, req: &Request, arg: &T) -> Response;
16}
17
18/// An HTTP routes structure.
19pub struct HttpRoutes<T> {
20    server_id: String,
21    prefix: String,
22    media_type: MediaType,
23    /// routes is a hash table mapping endpoint URIs to their endpoint handlers.
24    routes: HashMap<String, Box<dyn EndpointHandler<T> + Sync + Send>>,
25}
26
27impl<T: Send> HttpRoutes<T> {
28    /// Create a http request router.
29    pub fn new(server_id: String, prefix: String) -> Self {
30        HttpRoutes {
31            server_id,
32            prefix,
33            media_type: MediaType::ApplicationJson,
34            routes: HashMap::new(),
35        }
36    }
37
38    /// Register a request handler for a unique (HTTP_METHOD, HTTP_PATH) tuple.
39    ///
40    /// # Arguments
41    /// * `method`: HTTP method to assoicate with the handler.
42    /// * `path`: HTTP path to associate with the handler.
43    /// * `handler`: HTTP request handler for the (method, path) tuple.
44    pub fn add_route(
45        &mut self,
46        method: Method,
47        path: String,
48        handler: Box<dyn EndpointHandler<T> + Sync + Send>,
49    ) -> Result<(), RouteError> {
50        let full_path = format!("{}:{}{}", method.to_str(), self.prefix, path);
51        match self.routes.entry(full_path.clone()) {
52            Entry::Occupied(_) => Err(RouteError::HandlerExist(full_path)),
53            Entry::Vacant(entry) => {
54                entry.insert(handler);
55                Ok(())
56            }
57        }
58    }
59
60    /// Handle an incoming http request and generate corresponding response.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// extern crate dbs_uhttp;
66    /// use dbs_uhttp::{
67    ///     EndpointHandler, HttpRoutes, Method, StatusCode, Request, Response, Version
68    /// };
69    ///
70    /// struct HandlerArg(bool);
71    /// struct MockHandler {}
72    /// impl EndpointHandler<HandlerArg> for MockHandler {
73    ///     fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response {
74    ///         Response::new(Version::Http11, StatusCode::OK)
75    ///     }
76    /// }
77    ///
78    /// let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
79    /// let handler = MockHandler {};
80    /// router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)).unwrap();
81    ///
82    /// let request_bytes = b"GET http://localhost/api/v1/func1 HTTP/1.1\r\n\r\n";
83    /// let request = Request::try_from(request_bytes, None).unwrap();
84    /// let arg = HandlerArg(true);
85    /// let reply = router.handle_http_request(&request, &arg);
86    /// assert_eq!(reply.status(), StatusCode::OK);
87    /// ```
88    pub fn handle_http_request(&self, request: &Request, argument: &T) -> Response {
89        let path = format!(
90            "{}:{}",
91            request.method().to_str(),
92            request.uri().get_abs_path()
93        );
94        let mut response = match self.routes.get(&path) {
95            Some(route) => route.handle_request(request, argument),
96            None => Response::new(Version::Http11, StatusCode::NotFound),
97        };
98
99        response.set_server(&self.server_id);
100        response.set_content_type(self.media_type);
101        response
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    struct HandlerArg(bool);
110
111    struct MockHandler {}
112
113    impl EndpointHandler<HandlerArg> for MockHandler {
114        fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response {
115            Response::new(Version::Http11, StatusCode::OK)
116        }
117    }
118
119    #[test]
120    fn test_create_router() {
121        let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
122        let handler = MockHandler {};
123        let res = router.add_route(Method::Get, "/func1".to_string(), Box::new(handler));
124        assert!(res.is_ok());
125        assert!(router.routes.contains_key("GET:/api/v1/func1"));
126
127        let handler = MockHandler {};
128        match router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)) {
129            Err(RouteError::HandlerExist(_)) => {}
130            _ => panic!("add_route() should return error for path with existing handler"),
131        }
132
133        let handler = MockHandler {};
134        let res = router.add_route(Method::Put, "/func1".to_string(), Box::new(handler));
135        assert!(res.is_ok());
136
137        let handler = MockHandler {};
138        let res = router.add_route(Method::Get, "/func2".to_string(), Box::new(handler));
139        assert!(res.is_ok());
140    }
141
142    #[test]
143    fn test_handle_http_request() {
144        let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
145        let handler = MockHandler {};
146        router
147            .add_route(Method::Get, "/func1".to_string(), Box::new(handler))
148            .unwrap();
149
150        let request =
151            Request::try_from(b"GET http://localhost/api/v1/func2 HTTP/1.1\r\n\r\n", None).unwrap();
152        let arg = HandlerArg(true);
153        let reply = router.handle_http_request(&request, &arg);
154        assert_eq!(reply.status(), StatusCode::NotFound);
155    }
156}