highnoon/
app.rs

1use crate::endpoint::Endpoint;
2use crate::filter::{Filter, Next};
3use crate::router::{RouteTarget, Router};
4use crate::state::State;
5use crate::static_files::StaticFiles;
6use crate::test_client::TestClient;
7use crate::ws::{WebSocketReceiver, WebSocketSender};
8use crate::{Request, Responder, Response, Result};
9use async_trait::async_trait;
10use hyper::server::conn::{AddrIncoming, AddrStream};
11use hyper::server::Builder;
12use hyper::service::{make_service_fn, service_fn};
13use hyper::{Body, Method};
14use std::convert::Infallible;
15use std::future::Future;
16use std::net::SocketAddr;
17use std::path::PathBuf;
18use std::sync::Arc;
19use tokio::net::ToSocketAddrs;
20use tracing::info;
21
22/// The main entry point to highnoon. An `App` can be launched as a server
23/// or mounted into another `App`.
24/// Each `App` has a chain of [`Filters`](Filter)
25/// which are applied to each request.
26pub struct App<S: State> {
27    state: S,
28    routes: Router<S>,
29    filters: Vec<Box<dyn Filter<S> + Send + Sync + 'static>>,
30}
31
32/// Returned by [App::at] and attaches method handlers to a route.
33pub struct Route<'a, 'p, S: State> {
34    path: &'p str,
35    app: &'a mut App<S>,
36}
37
38impl<'a, 'p, S: State> Route<'a, 'p, S> {
39    /// Attach an endpoint for a specific HTTP method
40    pub fn method(self, method: Method, ep: impl Endpoint<S> + Send + Sync + 'static) -> Self {
41        self.app.routes.add(method, self.path, ep);
42        self
43    }
44
45    /// Attach an endpoint for all HTTP methods. These will be checked only if no
46    /// specific endpoint exists for the method.
47    pub fn all(self, ep: impl Endpoint<S> + Send + Sync + 'static) -> Self {
48        self.app.routes.add_all(self.path, ep);
49        self
50    }
51
52    /// Attach an endpoint for GET requests
53    pub fn get(self, ep: impl Endpoint<S> + Send + Sync + 'static) -> Self {
54        self.method(Method::GET, ep)
55    }
56
57    /// Attach an endpoint for POST requests
58    pub fn post(self, ep: impl Endpoint<S> + Send + Sync + 'static) -> Self {
59        self.method(Method::POST, ep)
60    }
61
62    /// Attach an endpoint for PUT requests
63    pub fn put(self, ep: impl Endpoint<S> + Send + Sync + 'static) -> Self {
64        self.method(Method::PUT, ep)
65    }
66
67    /// Attach an endpoint for DELETE requests
68    pub fn delete(self, ep: impl Endpoint<S> + Send + Sync + 'static) -> Self {
69        self.method(Method::DELETE, ep)
70    }
71
72    /// Serve static files located in the path `root`. The path should end with a wildcard segment
73    /// (ie. `/*`). The wildcard portion of the URL will be appended to `root` to form the full
74    /// path. The file extension is used to guess a mime type. Files outside of `root` will return
75    /// a FORBIDDEN error code; `..` and `.` path segments are allowed as long as they do not navigate
76    /// outside of `root`.
77    pub fn static_files(self, root: impl Into<PathBuf>) -> Self {
78        let prefix = self.path.to_owned(); // TODO - borrow issue here
79        self.method(Method::GET, StaticFiles::new(root, prefix))
80    }
81
82    /// Mount an app to handle all requests from this path.
83    /// The path may contain parameters and these will be merged into
84    /// the parameters from individual paths in the inner `App`.
85    /// The App may have a different state type, but its `Context` must implement `From` to perform
86    /// the conversion from the parent state's `Context` - *the inner `App`'s `new_context` won't
87    /// be called*.
88    pub fn mount<S2>(&mut self, app: App<S2>)
89    where
90        S2: State,
91        S2::Context: From<S::Context>,
92    {
93        let path = self.path.to_owned() + "/*-highnoon-path-rest-";
94        let mounted = MountedApp { app: Arc::new(app) };
95        self.app.at(&path).all(mounted);
96    }
97
98    /// Attach a websocket handler to this route
99    pub fn ws<H, F>(self, handler: H)
100    where
101        H: Send + Sync + 'static + Fn(Request<S>, WebSocketSender, WebSocketReceiver) -> F,
102        F: Future<Output = Result<()>> + Send + 'static,
103    {
104        self.method(Method::GET, crate::ws::endpoint(handler));
105    }
106}
107
108impl<S: State> App<S> {
109    /// Create a new `App` with the given state.
110    /// State must be `Send + Sync + 'static` because it gets shared by all route handlers.
111    /// If you need inner mutability use a `Mutex` or similar.
112    pub fn new(state: S) -> Self {
113        Self {
114            state,
115            routes: Router::new(),
116            filters: vec![],
117        }
118    }
119
120    /// Create a test client by consuming this App. The test client can be used to send fake
121    /// requests to the App and receive responses back. This can be used in unit and
122    /// integration tests.
123    pub fn test(self) -> TestClient<S> {
124        TestClient::new(self)
125    }
126
127    /// Get a reference to this App's state
128    pub fn state(&self) -> &S {
129        &self.state
130    }
131
132    /// Append a filter to the chain. Filters are applied to all endpoints in this app, and are
133    /// applied in the order they are registered.
134    pub fn with<F>(&mut self, filter: F)
135    where
136        F: Filter<S> + Send + Sync + 'static,
137    {
138        self.filters.push(Box::new(filter));
139    }
140
141    /// Create a route at the given path. Returns a [Route] object on which you can
142    /// attach handlers for each HTTP method
143    pub fn at<'a, 'p>(&'a mut self, path: &'p str) -> Route<'a, 'p, S> {
144        Route { path, app: self }
145    }
146
147    /// Start a server listening on the given address (See [ToSocketAddrs] from tokio)
148    /// This method only returns if there is an error. (Graceful shutdown is TODO)
149    pub async fn listen(self, host: impl ToSocketAddrs) -> anyhow::Result<()> {
150        let mut addrs = tokio::net::lookup_host(host).await?;
151        let addr = addrs
152            .next()
153            .ok_or_else(|| anyhow::Error::msg("host lookup returned no hosts"))?;
154
155        let builder = hyper::Server::try_bind(&addr)?;
156        self.internal_serve(builder).await
157    }
158
159    /// Start a server listening on the provided [std::net::TcpListener]
160    /// This method only returns if there is an error. (Graceful shutdown is TODO)
161    pub async fn listen_on(self, tcp: std::net::TcpListener) -> anyhow::Result<()> {
162        let builder = hyper::Server::from_tcp(tcp)?;
163        self.internal_serve(builder).await
164    }
165
166    async fn internal_serve(self, builder: Builder<AddrIncoming>) -> anyhow::Result<()> {
167        let app = Arc::new(self);
168
169        let make_svc = make_service_fn(|addr_stream: &AddrStream| {
170            let app = app.clone();
171            let addr = addr_stream.remote_addr();
172
173            async move {
174                Ok::<_, Infallible>(service_fn(move |req: hyper::Request<Body>| {
175                    let app = app.clone();
176                    async move {
177                        App::serve_one_req(app, req, addr)
178                            .await
179                            .map_err(|err| err.into_std())
180                    }
181                }))
182            }
183        });
184
185        let server = builder.serve(make_svc);
186        info!("server listening on {}", server.local_addr());
187        server.await?;
188        Ok(())
189    }
190
191    pub(crate) async fn serve_one_req(
192        app: Arc<App<S>>,
193        req: hyper::Request<Body>,
194        addr: SocketAddr,
195    ) -> Result<hyper::Response<Body>> {
196        let RouteTarget { ep, params } = app.routes.lookup(req.method(), req.uri().path());
197
198        let ctx = app.state.new_context();
199        let req = Request::new(app.clone(), req, params, addr, ctx);
200
201        let next = Next {
202            ep,
203            rest: &*app.filters,
204        };
205
206        next.next(req)
207            .await
208            .or_else(|err| err.into_response())
209            .map(|resp| resp.into_inner())
210    }
211}
212
213struct MountedApp<S: State> {
214    app: Arc<App<S>>,
215}
216
217#[async_trait]
218impl<S: State, S2: State> Endpoint<S> for MountedApp<S2>
219where
220    S2::Context: From<S::Context>,
221{
222    async fn call(&self, req: Request<S>) -> Result<Response> {
223        // deconstruct the request from the outer state
224        let (inner, params, remote_addr, context) = req.into_parts();
225        // get the part of the path still to be routed
226        let path_rest = params
227            .find("-highnoon-path-rest-")
228            .expect("-highnoon-path-rest- is missing!");
229        // lookup the target for the request in the nested app
230        let RouteTarget {
231            ep,
232            params: params2,
233        } = self.app.routes.lookup(inner.method(), path_rest);
234
235        // construct a new request for the inner state type
236        let mut req2 = Request::new(self.app.clone(), inner, params, remote_addr, context.into());
237
238        // merge the inner params
239        req2.merge_params(params2);
240
241        // start the filter chain for the nested app
242        let next = Next {
243            ep,
244            rest: &*self.app.filters,
245        };
246
247        next.next(req2).await
248    }
249}