envoy_http 0.2.2

A minimal and pragmatic Rust web application framework built for rapid development
Documentation
//! An HTTP server

use std::net::SocketAddr;
use std::sync::Arc;

use hyper::{Uri, Method};

use crate::middleware::{Middleware, Next};
use crate::router::{Router, Selection};
use crate::{Endpoint, Route};

/// An HTTP server.
///
/// Servers are built up as a combination of *state*, *endpoints* and *middleware*:
///
/// - Server state is user-defined, and is provided via the [`Server::with_state`] function. The
/// state is available as a shared reference to all app endpoints.
///
/// - Endpoints provide the actual application-level code corresponding to
/// particular URLs. The [`Server::at`] method creates a new *route* (using
/// standard router syntax), which can then be used to register endpoints
/// for particular HTTP request types.
///
/// - Middleware extends the base Envoy framework with additional request or
/// response processing, such as compression, default headers, or logging. To
/// add middleware to an app, use the [`Server::with`] method.
pub struct Server {
    router: Arc<Router>,
    /// Holds the middleware stack.
    ///
    /// Note(Fishrock123): We do actually want this structure.
    /// The outer Arc allows us to clone in .respond() without cloning the array.
    /// The Vec allows us to add middleware at runtime.
    /// The inner Arc-s allow MiddlewareEndpoint-s to be cloned internally.
    /// We don't use a Mutex around the Vec here because adding a middleware during execution should be an error.
    #[allow(clippy::rc_buffer)]
    middleware: Arc<Vec<Arc<dyn Middleware>>>,
}

impl Server {
    /// Create a new Envoy server.
    #[must_use]
    pub fn new() -> Self {
        Self {
            router: Arc::new(Router::new()),
            middleware: Arc::new(Vec::new()),
        }
    }
}

impl Default for Server {
    fn default() -> Self {
        Self::new()
    }
}

impl Server {


    /// Add a new route at the given `path`, relative to root.
    ///
    /// Routing means mapping an HTTP request to an endpoint. Here Envoy applies
    /// a "table of contents" approach, which makes it easy to see the overall
    /// app structure. Endpoints are selected solely by the path and HTTP method
    /// of a request: the path determines the resource and the HTTP verb the
    /// respective endpoint of the selected resource.
    ///
    /// A path is comprised of zero or many segments, i.e. non-empty strings
    /// separated by '/'. There are two kinds of segments: concrete and
    /// wildcard. A concrete segment is used to exactly match the respective
    /// part of the path of the incoming request. A wildcard segment on the
    /// other hand extracts and parses the respective part of the path of the
    /// incoming request to pass it along to the endpoint as an argument. A
    /// wildcard segment is written as `:name`, which creates an endpoint
    /// parameter called `name`. It is not possible to define wildcard segments
    /// with different names for otherwise identical paths.
    ///
    /// Alternatively a wildcard definitions can start with a `*`, for example
    /// `*path`, which means that the wildcard will match to the end of given
    /// path, no matter how many segments are left, even nothing.
    ///
    /// The name of the parameter can be omitted to define a path that matches
    /// the required structure, but where the parameters are not required.
    /// `:` will match a segment, and `*` will match an entire path.
    ///
    /// Here are some examples omitting the HTTP verb based endpoint selection:
    ///
    /// ```rust,no_run
    /// # let mut app = envoy::Server::new();
    /// app.at("/");
    /// app.at("/hello");
    /// app.at("add_two/:num");
    /// app.at("files/:user/*");
    /// app.at("static/*path");
    /// app.at("static/:context/:");
    /// ```
    ///
    /// There is no fallback route matching, i.e. either a resource is a full
    /// match or not, which means that the order of adding resources has no
    /// effect.
    pub fn at<'a>(&'a mut self, path: &str) -> Route<'a> {
        let router = Arc::get_mut(&mut self.router)
            .expect("Registering routes is not possible after the Server has started");
        Route::new(router, path.to_owned())
    }

    /// Add middleware to an application.
    ///
    /// Middleware provides customization of the request/response cycle, such as compression,
    /// logging, or header modification. Middleware is invoked when processing a request, and can
    /// either continue processing (possibly modifying the response) or immediately return a
    /// response. See the [`Middleware`] trait for details.
    ///
    /// Middleware can only be added at the "top level" of an application, and is processed in the
    /// order in which it is applied.
    pub fn with(&mut self, middleware: impl Middleware + 'static) -> &mut Self
    {
        tracing::trace!("Adding middleware {}", middleware.name());
        let m = Arc::get_mut(&mut self.middleware)
            .expect("Registering middleware is not possible after the Server has started");
        m.push(Arc::new(middleware));
        self
    }

    /// Respond to a `Request` with a `Response`.
    ///
    /// This method is useful for testing endpoints directly,
    /// or for creating servers over custom transports.
    pub async fn respond<Req, Res>(self, req: Req) -> hyper::Result<Res>
    where
        Req: Into<hyper::Request<hyper::Body>>,
        Res: From<hyper::Response<hyper::Body>>,
    {
        let req: hyper::Request<hyper::Body> = req.into();
        let Self {
            router,
            middleware,
        } = self.clone();

        let method = req.method().to_owned();
        let Selection { endpoint, params } = router.route(req.uri().path(), method);
        let route_params = vec![params];
        let mut ctx = crate::Context::new(req, route_params);

        let next = Next::new(endpoint, middleware);

        match next.run(&mut ctx).await {
            Ok(res) => Ok(res.into()),
            Err(e) => {
                let res = hyper::Response::builder()
                    .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
                    .body(hyper::Body::from(format!("{}", e)))
                    .unwrap();
                Ok(res.into())
            }
        }
    }

    /// Start the server.
    pub async fn listen(self, addr: SocketAddr) -> Result<(), crate::Error> {

        let make_svc = hyper::service::make_service_fn(move |_conn|{
            let server = self.clone();
            async move {
                Ok::<_, hyper::Error>(hyper::service::service_fn(move |req| server.clone().respond(req)))
            }
        });

        let server = hyper::Server::bind(&addr).serve(make_svc);

        server.await?;

        Ok(())
    }

}

impl std::fmt::Debug for Server {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Server").finish()
    }
}

impl Clone for Server {
    fn clone(&self) -> Self {
        Self {
            router: self.router.clone(),
            middleware: self.middleware.clone(),
        }
    }
}

#[async_trait::async_trait]
impl Endpoint for Server
{
    async fn call(&self, ctx: &mut crate::Context) -> crate::Result {
        let path = ctx.borrow::<Uri>().path();
        let method = ctx.borrow::<Method>().to_owned();
        let router = self.router.clone();
        let middleware = self.middleware.clone();

        let Selection { endpoint, params } = router.route(&path, method);
        ctx.params.push(params);

        let next = Next::new(endpoint, middleware);

        next.run(ctx).await
    }
}

#[cfg(test)]
mod test {
    use crate as envoy;

    #[test]
    fn allow_nested_server_with_same_state() {
        let inner = envoy::new();
        let mut outer = envoy::new();
        outer.at("/foo").get(inner);
    }
}