Skip to main content

humblegen_rt/
server.rs

1//! `GEN` Generic parts of the humblegen HTTP service server implementation, based on [`hyper`](https://hyper.rs).
2
3use crate::handler::HandlerResponse;
4use crate::regexset_map;
5use crate::regexset_map::RegexSetMap;
6use crate::service_protocol::{self, RuntimeError, ToErrorResponse};
7use derivative::Derivative;
8use tracing_futures::Instrument;
9
10use anyhow::Context;
11use hyper::Body;
12use hyper::Request;
13use hyper::Response;
14
15use std::convert::Infallible;
16use std::net::SocketAddr;
17use std::sync::Arc;
18
19use rand::Rng;
20
21/// Serve `services` via HTTP, binding to the given `addr`.
22/// Invokes `handle_request`.
23///
24/// Invoked by generated code.
25pub async fn listen_and_run_forever(
26    services: RegexSetMap<Request<Body>, Service>,
27    addr: &SocketAddr,
28) -> anyhow::Result<()> {
29    // Note: this is the standard (noisy) dance for handling hyper requests.
30    let services = Arc::new(services);
31    let server = hyper::Server::bind(addr).serve(hyper::service::make_service_fn(
32        move |_sock: &hyper::server::conn::AddrStream| {
33            let services = Arc::clone(&services);
34            async move {
35                Ok::<_, Infallible>(hyper::service::service_fn(
36                    move |req: hyper::Request<hyper::Body>| {
37                        let services = Arc::clone(&services);
38                        async move {
39                            let resp = handle_request(services, req).await;
40                            Ok::<Response<hyper::Body>, Infallible>(resp)
41                        }
42                    },
43                ))
44            }
45        },
46    ));
47
48    server.await.context("server error")?;
49    Ok(())
50}
51
52const REQUEST_ID_HEADER_NAME: &'static str = "Request-ID";
53
54/// The routine that maps an incoming hyper request to a service in `services`,
55/// and invokes the service's dispatcher.
56pub async fn handle_request(
57    services: Arc<RegexSetMap<Request<Body>, Service>>,
58    req: Request<Body>,
59) -> Response<Body> {
60    let request_id: String = rand::thread_rng()
61        .sample_iter(&rand::distributions::Alphanumeric)
62        .take(30)
63        .collect();
64    let span = tracing::error_span!("handle_request", request_id = ?request_id);
65    handle_request_impl(services, req, request_id)
66        .instrument(span)
67        .await
68}
69
70pub async fn handle_request_impl(
71    services: Arc<RegexSetMap<Request<Body>, Service>>,
72    req: Request<Body>,
73    request_id: String,
74) -> Response<Body> {
75    let path = req.uri().path().to_string(); // necessary because we need to move req into dispatcher, but also need to move captures into dispatcher
76
77    let mut response = match services.get(&path, &req) {
78        regexset_map::GetResult::None => RuntimeError::NoServiceMounted
79            .to_error_response()
80            .to_hyper_response(),
81        regexset_map::GetResult::Ambiguous => RuntimeError::ServiceMountsAmbiguous
82            .to_error_response()
83            .to_hyper_response(),
84        regexset_map::GetResult::One(service) => {
85            tracing::debug!(service_regex = (service.0).0.as_str(), "service matched");
86            let tuple = &service.0;
87            let service_regex_captures = tuple.0.captures(&path).unwrap();
88            let service = service_regex_captures["root"].to_string();
89            let suffix = &service_regex_captures["suffix"];
90            match tuple.1.get(&suffix, &req) {
91                regexset_map::GetResult::None => RuntimeError::NoRouteMountedInService { service }
92                    .to_error_response()
93                    .to_hyper_response(),
94                regexset_map::GetResult::Ambiguous => {
95                    RuntimeError::RouteMountsAmbiguous { service }
96                        .to_error_response()
97                        .to_hyper_response()
98                }
99                regexset_map::GetResult::One(route) => {
100                    tracing::debug!(route_regex = route.regex.as_str(), "route matched");
101                    let captures = route.regex.captures(suffix).unwrap();
102                    let dispatcher = &route.dispatcher;
103
104                    let dispatcher_result = {
105                        let dispatcher_span = tracing::error_span!("invoke_dispatcher");
106                        dispatcher(req, captures).instrument(dispatcher_span).await
107                    };
108                    match dispatcher_result {
109                        Ok(r) => {
110                            tracing::debug!("handler returned Ok");
111                            r
112                        }
113                        Err(e) => {
114                            tracing::error!(err = ?e, "handler returned error");
115                            e.to_hyper_response()
116                        }
117                    }
118                }
119            }
120        }
121    };
122
123    response.headers_mut().insert(
124        REQUEST_ID_HEADER_NAME,
125        hyper::header::HeaderValue::from_str(&request_id)
126            .expect("request ID is expected to be valid header value"),
127    );
128
129    response.headers_mut().insert(
130        hyper::header::CONTENT_TYPE,
131        hyper::header::HeaderValue::from_static("application/json"),
132    );
133
134    tracing::debug!(http_status = ?response.status(), "finished request");
135
136    response
137}
138
139/// A service is a collection of Routes that share a common `prefix`.
140///
141/// Instantiated by generated code.
142#[derive(Debug)]
143pub struct Service(pub (regex::Regex, RegexSetMap<Request<Body>, Route>));
144
145// helper type that avoids bloating the type signature of `DispatcherClosure`.
146type BoxSyncFuture<Output> =
147    std::pin::Pin<Box<dyn Send + Sync + std::future::Future<Output = Output>>>;
148
149/// Closure with an internal reference to the handler trait object that implements a humblegen service trait.
150/// It decodes request into the arguments required to invoke the trait function and then does the call.
151type DispatcherClosure = dyn Fn(
152        Request<Body>,
153        regex::Captures,
154    ) -> BoxSyncFuture<Result<Response<Body>, service_protocol::ErrorResponse>>
155    + Send
156    + Sync;
157
158/// A route associates an HTTP method + URL path regex with a `DispatcherClosure`.
159/// It implements `regexset_map::Entry` and only makes sense within a `Service`.
160///
161/// Instantiated by generated code.
162#[derive(Derivative)]
163#[derivative(Debug)]
164pub struct Route {
165    pub method: hyper::Method,
166    pub regex: regex::Regex,
167    #[derivative(Debug = "ignore")]
168    pub dispatcher: Box<DispatcherClosure>,
169}
170
171impl<'a> regexset_map::Entry<Request<Body>> for Route {
172    fn regex(&self) -> &regex::Regex {
173        &self.regex
174    }
175    fn matches_input(&self, req: &Request<Body>) -> bool {
176        self.method == req.method()
177    }
178}
179
180impl<'a> regexset_map::Entry<Request<Body>> for Service {
181    fn regex(&self) -> &regex::Regex {
182        let pair = &self.0;
183        &pair.0
184    }
185    fn matches_input(&self, _req: &Request<Body>) -> bool {
186        true
187    }
188}
189
190/// Conversion of a `HandlerResponse` to a hyper response.
191/// Invoked from generated code within a `DispatcherClosure`.
192pub fn handler_response_to_hyper_response<T>(handler_response: HandlerResponse<T>) -> Response<Body>
193where
194    T: serde::Serialize,
195{
196    match handler_response {
197        Ok(x) => serde_json::to_string(&x)
198            .map(|s| Response::new(Body::from(s)))
199            .unwrap_or_else(|e| {
200                tracing::error!(error = ?e, "cannot serialize handler response");
201                RuntimeError::SerializeHandlerResponse(e.to_string())
202                    .to_error_response()
203                    .to_hyper_response()
204            }),
205        Err(e) => {
206            tracing::error!(error = ?e, "handler returned error");
207            service_protocol::ServiceError::from(e)
208                .to_error_response()
209                .to_hyper_response()
210        }
211    }
212}