Coverage Report

Created: 2024-05-21 06:19

/rust/git/checkouts/micro-http-22be4cdcbef12607/ef43cef/src/router.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
2
// Copyright © 2019 Intel Corporation
3
//
4
// SPDX-License-Identifier: Apache-2.0
5
6
use std::collections::hash_map::{Entry, HashMap};
7
8
use crate::{MediaType, Method, Request, Response, StatusCode, Version};
9
10
pub use crate::common::RouteError;
11
12
/// An HTTP endpoint handler interface
13
pub 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.
19
pub 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
27
impl<T: Send> HttpRoutes<T> {
28
    /// Create a http request router.
29
0
    pub fn new(server_id: String, prefix: String) -> Self {
30
0
        HttpRoutes {
31
0
            server_id,
32
0
            prefix,
33
0
            media_type: MediaType::ApplicationJson,
34
0
            routes: HashMap::new(),
35
0
        }
36
0
    }
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
0
    pub fn add_route(
45
0
        &mut self,
46
0
        method: Method,
47
0
        path: String,
48
0
        handler: Box<dyn EndpointHandler<T> + Sync + Send>,
49
0
    ) -> Result<(), RouteError> {
50
0
        let full_path = format!("{}:{}{}", method.to_str(), self.prefix, path);
51
0
        match self.routes.entry(full_path.clone()) {
52
0
            Entry::Occupied(_) => Err(RouteError::HandlerExist(full_path)),
53
0
            Entry::Vacant(entry) => {
54
0
                entry.insert(handler);
55
0
                Ok(())
56
            }
57
        }
58
0
    }
59
60
    /// Handle an incoming http request and generate corresponding response.
61
    ///
62
    /// # Examples
63
    ///
64
    /// ```
65
    /// extern crate micro_http;
66
    /// use micro_http::{EndpointHandler, HttpRoutes, Method, Request, Response, StatusCode, Version};
67
    ///
68
    /// struct HandlerArg(bool);
69
    /// struct MockHandler {}
70
    /// impl EndpointHandler<HandlerArg> for MockHandler {
71
    ///     fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response {
72
    ///         Response::new(Version::Http11, StatusCode::OK)
73
    ///     }
74
    /// }
75
    ///
76
    /// let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
77
    /// let handler = MockHandler {};
78
    /// router
79
    ///     .add_route(Method::Get, "/func1".to_string(), Box::new(handler))
80
    ///     .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
0
    pub fn handle_http_request(&self, request: &Request, argument: &T) -> Response {
89
0
        let path = format!(
90
0
            "{}:{}",
91
0
            request.method().to_str(),
92
0
            request.uri().get_abs_path()
93
0
        );
94
0
        let mut response = match self.routes.get(&path) {
95
0
            Some(route) => route.handle_request(request, argument),
96
0
            None => Response::new(Version::Http11, StatusCode::NotFound),
97
        };
98
99
0
        response.set_server(&self.server_id);
100
0
        response.set_content_type(self.media_type);
101
0
        response
102
0
    }
103
}
104
105
#[cfg(test)]
106
mod 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
}