webframework 0.0.2

An extensible webframework, its main purpose is to be: safe, fun, fast.
Documentation
use crate::error::{ServiceError, ServiceErrorKind};

use webframework_core::WebResult;
use webframework_core::request::Request;
use webframework_core::response::Response;
use webframework_core::router::{Router, RouterResult};

use std::net::SocketAddr;
use std::time::Instant;
use std::collections::HashMap;

use failure::{Compat, Error};
use futures::future::{self, Future, FutureResult};
use uuid::Uuid;
use slog::{Drain, Logger};

struct ServiceCreator<S: Router> {
    logger: Logger,
    router: S,
}

impl<S: Router + 'static + Send, Ctx> hyper::service::MakeService<Ctx> for ServiceCreator<S> {
    type ReqBody = hyper::Body;
    type ResBody = hyper::Body;
    type Error = Compat<ServiceError>;
    type Service = Service<S>;
    type Future = FutureResult<Self::Service, Self::Error>;
    type MakeError = Compat<ServiceError>;

    fn make_service(&mut self, _: Ctx) -> Self::Future {
        future::ok(Service {
            logger: self.logger.new(slog::o!()),
            router: self.router.clone(),
        })
    }
}

struct Service<S: Router> {
    logger: Logger,
    router: S,
}

impl<S: Router + 'static + Send> hyper::service::Service for Service<S> {
    type ReqBody = hyper::Body;
    type ResBody = hyper::Body;
    type Error = Compat<ServiceError>;
    type Future = Box<dyn Future<Item = hyper::Response<Self::ResBody>, Error = Self::Error> + Send>;

    fn call(&mut self, req: hyper::Request<hyper::Body>) -> Self::Future {
        let id = Uuid::new_v4();
        let string_id = id.to_string();
        let new_logger = self.logger.new(slog::o!("path" => req.uri().path().to_string(),
                                                   "id" => string_id.clone()));
        let time_logger = self.logger.new(slog::o!("path" => req.uri().path().to_string(),
                                                   "id" => string_id.clone()));
        let now = Instant::now();
        let request = Request::from_req(id, new_logger, req);
        let router = self.router.clone();

        Box::new(request.and_then(move |req| {
            use futures::future::Either;

            match router.handle(req, None, HashMap::new()) {
                RouterResult::Handled(resp) => {
                    return Either::A(resp.map(Response::as_response).flatten());
                }
                RouterResult::Unhandled(req, _) => {
                    let error = ServiceErrorKind::UnhandledError(req.path().to_string()).into();
                    return Either::B(future::err(error));
                }
            }
        }).or_else(|err: Error| {
            let resp = Response::from_string(crate::templates::error_page(&err.compat()))
                .with_status(hyper::StatusCode::INTERNAL_SERVER_ERROR);
            future::ok(resp.as_response().unwrap())
        }).then(move |resp: Result<hyper::Response<hyper::Body>, _>| {
            let elapsed = now.elapsed();
            let time: f64 = elapsed.as_secs() as f64 * 1000.0 + elapsed.subsec_nanos() as f64 / 1_000_000.0;

            let mut status = None;

            match &resp {
                Ok(resp) => {
                    status = Some(resp.status().as_u16());
                }
                _ => (),
            }

            slog::info!(time_logger, "Handled in {}ms", time; "elapsed_time" => time, "status" => status);
            resp
        }))
    }
}


pub struct Server {
    root_logger: Logger,
    addr: SocketAddr,
}

impl Server {
    pub fn handle_with<S: Router + Send + Sync + 'static>(&self, router: S) -> WebResult<()> {
        let creator = ServiceCreator {
            logger: self.root_logger.new(slog::o!()),
            router
        };

        slog::info!(&creator.logger, "Starting Webserver on http://{}", &self.addr;
                    "port" => %self.addr.port(), "host" => %self.addr.ip());

        let server = hyper::Server::bind(&self.addr)
            .serve(creator)
            .map_err(|err| eprintln!("error: {}", err));

        hyper::rt::run(server);
        Ok(())
    }
}

pub fn load() -> Server {
    let decorator = slog_term::TermDecorator::new().build();
    let drain = slog_term::FullFormat::new(decorator).build().fuse();
    let drain = slog_async::Async::new(drain).build().fuse();

    let root_logger = Logger::root(drain, slog::o!());
    Server {
        root_logger,
        addr: ([127, 0, 0, 1], 3000).into(),
    }
}