logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::task;

use hyper::Body;

use super::*;

/// The main hyper dispatcher
#[derive(Clone)]
pub struct Dispatcher<'a> {
    /// Map of routes to webmachine resources
    pub routes: BTreeMap<&'a str, Resource<'a>>,
}

impl<'a> Dispatcher<'a> {
    /// Main dispatch function for the Webmachine. This will look for a matching resource
    /// based on the request path. If one is not found, a 404 Not Found response is returned
    pub async fn dispatch(self, req: http::Request<Body>) -> http::Result<http::Response<Body>> {
        let mut context = self.context_from_http_request(req).await;
        self.dispatch_to_resource(&mut context).await;
        self.generate_http_response(&context)        
    }

    async fn context_from_http_request(&self, req: http::Request<Body>) -> Context {
        let request = self.request_from_http_request(req).await;
        Context {
            request,
            response: Response::default(),
            ..Context::default()
        }
    }

    pub(crate) fn match_paths(&self, request: &Request) -> Vec<String> {
        let request_path = sanitise_path(&request.request_path);
        self.routes
            .keys()
            .filter(|k| request_path.starts_with(&sanitise_path(k)))
            .map(|k| k.to_string())
            .collect()
    }

    pub(crate) fn lookup_resource(&self, path: &str) -> Option<&Resource<'a>> {
        self.routes.get(path)
    }

    /// Dispatches to the matching webmachine resource. If there is no matching resource, returns
    /// 404 Not Found response
    pub async fn dispatch_to_resource(&self, context: &mut Context) {
        let matching_paths = self.match_paths(&context.request);
        let ordered_by_length: Vec<String> = matching_paths
            .iter()
            .cloned()
            .sorted_by(|a, b| Ord::cmp(&b.len(), &a.len()))
            .collect();
        match ordered_by_length.first() {
            Some(path) => {
                update_paths_for_resource(&mut context.request, path);
                if let Some(resource) = self.lookup_resource(path) {
                    execute_state_machine(context, &resource).await;
                    finalise_response(context, &resource).await;
                } else {
                    context.response.status = 404;
                }
            }
            None => context.response.status = 404,
        };
    }

    fn generate_http_response(&self, context: &Context) -> http::Result<http::Response<Body>> {
        let mut response = http::Response::builder().status(context.response.status);
    
        for (header, values) in context.response.headers.clone() {
            let header_values = values.iter().map(|h| h.to_string()).join(", ");
            response = response.header(&header, &header_values);
        }
    
        match context.response.body.clone() {
            Some(body) => response.body(body.into()),
            None => response.body(Body::empty()),
        }
    }

    async fn request_from_http_request(&self, req: http::Request<Body>) -> Request {
        let (parts, body) = req.into_parts();
        let request_path = parts.uri.path().to_string();
    
        let req_body = body
            .try_fold(Vec::new(), |mut data, chunk| async move {
                data.extend_from_slice(&chunk);
                Ok(data)
            })
            .await;
        let body = match req_body {
            Ok(body) => {
                if body.is_empty() {
                    None
                } else {
                    Some(body.clone())
                }
            }
            Err(err) => {
                error!("Failed to read the request body: {}", err);
                None
            }
        };
    
        let query = match parts.uri.query() {
            Some(query) => parse_query(query),
            None => HashMap::new(),
        };
        Request {
            request_path: request_path.clone(),
            base_path: "/".to_string(),
            method: parts.method.as_str().into(),
            headers: headers_from_http_request(&parts),
            body,
            query,
        }
    }
}

impl Service<http::Request<Body>> for Dispatcher<'static> {
    type Response = http::Response<Body>;
    type Error = http::Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

    fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: http::Request<Body>) -> Self::Future {
        Box::pin(self.clone().dispatch(req))
    }
}