index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2021-05-31 21:42:45.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2021-05-31 21:42:45.0 +00:00:00
commit
991d4b8deefade61d35ef278efa28ed7fcb79bed [patch]
tree
36a51e95f1c81e636761ceca66f0ce3b63a5e40a
parent
21bdf8c26abd5d11b704b9345e539abadf4ff18a
download
991d4b8deefade61d35ef278efa28ed7fcb79bed.tar.gz

refactor: introduce a custom service for server



Diff

 Cargo.toml     |  2 +-
 src/handler.rs | 62 +++++++++++++++++++++++++++++++------------------
 src/lib.rs     |  1 +-
 src/server.rs  | 58 +++++++++++-----------------------------------
 src/service.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 131 insertions(+), 67 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index a51c49b..ed37d45 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,7 +45,7 @@ pin-project = "1.0"
tokio-rustls = { version = "0.22" }
humansize = "1.1"
time = "0.1"
listenfd = "0.3.3"
listenfd = "0.3"

[target.'cfg(not(windows))'.dependencies.nix]
version = "0.14"
diff --git a/src/handler.rs b/src/handler.rs
index cb191f7..04bd213 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -1,32 +1,50 @@
use hyper::{Body, Request, Response};
use std::path::Path;
use std::{future::Future, path::PathBuf};

use crate::{compression, control_headers, static_files};
use crate::{error::Result, error_page};
use crate::{error_page, Error, Result};

/// Main server request handler.
pub async fn handle_request(
    base: &Path,
    compression: bool,
    dir_listing: bool,
    req: &Request<Body>,
) -> Result<Response<Body>> {
    let headers = req.headers();
    let method = req.method();
// It defines options for a request handler.
pub struct RequestHandlerOpts {
    pub root_dir: PathBuf,
    pub compression: bool,
    pub dir_listing: bool,
}

    match static_files::handle_request(method, headers, base, req.uri().path(), dir_listing).await {
        Ok(mut resp) => {
            // Auto compression based on the `Accept-Encoding` header
            if compression {
                resp = compression::auto(method, headers, resp)?;
            }
// It defines the main request handler for Hyper service request.
pub struct RequestHandler {
    pub opts: RequestHandlerOpts,
}

            // Append `Cache-Control` headers for web assets
            let ext = req.uri().path().to_lowercase();
            control_headers::with_cache_control(&ext, &mut resp);
impl RequestHandler {
    pub fn handle<'a>(
        &'a self,
        req: &'a mut Request<Body>,
    ) -> impl Future<Output = Result<Response<Body>, Error>> + Send + 'a {
        let method = req.method();
        let headers = req.headers();
        let root_dir = self.opts.root_dir.as_path();
        let uri_path = req.uri().path();
        let dir_listing = self.opts.dir_listing;

            Ok(resp)
        async move {
            match static_files::handle_request(method, headers, root_dir, uri_path, dir_listing)
                .await
            {
                Ok(mut resp) => {
                    // Auto compression based on the `Accept-Encoding` header
                    if self.opts.compression {
                        resp = compression::auto(method, headers, resp)?;
                    }

                    // Append `Cache-Control` headers for web assets
                    let ext = uri_path.to_lowercase();
                    control_headers::with_cache_control(&ext, &mut resp);

                    Ok(resp)
                }
                Err(status) => error_page::get_error_response(method, &status),
            }
        }
        Err(status) => error_page::get_error_response(method, &status),
    }
}
diff --git a/src/lib.rs b/src/lib.rs
index fa591ab..a9b5e38 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,6 +11,7 @@ pub mod handler;
pub mod helpers;
pub mod logger;
pub mod server;
pub mod service;
pub mod signals;
pub mod static_files;
pub mod tls;
diff --git a/src/server.rs b/src/server.rs
index 97637a1..ef53295 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,16 +1,14 @@
use hyper::server::conn::AddrIncoming;
use hyper::server::Server as HyperServer;
use hyper::service::{make_service_fn, service_fn};
use listenfd::ListenFd;
use std::net::{IpAddr, SocketAddr, TcpListener};
use std::sync::Arc;
use structopt::StructOpt;

use crate::config::Config;
use crate::static_files::ArcPath;
use crate::handler::{RequestHandler, RequestHandlerOpts};
use crate::tls::{TlsAcceptor, TlsConfigBuilder};
use crate::Result;
use crate::{error, error_page, handler, helpers, logger};
use crate::{config::Config, service::RouterService};
use crate::{error_page, helpers, logger};

/// Define a multi-thread HTTP or HTTP/2 web server.
pub struct Server {
@@ -82,7 +80,6 @@ impl Server {

        // Check for a valid root directory
        let root_dir = helpers::get_valid_dirpath(&opts.root)?;
        let root_dir = ArcPath(Arc::new(root_dir));

        // Custom error pages content
        error_page::PAGE_404
@@ -105,6 +102,15 @@ impl Server {
        // Spawn a new Tokio asynchronous server task with its given options
        let threads = self.threads;

        // Create a service router for Hyper
        let router_service = RouterService::new(RequestHandler {
            opts: RequestHandlerOpts {
                root_dir,
                compression,
                dir_listing,
            },
        });

        if opts.http2 {
            // HTTP/2 + TLS

@@ -112,24 +118,6 @@ impl Server {
            let key_path = opts.http2_tls_key.clone();

            tokio::task::spawn(async move {
                let make_service = make_service_fn(move |_| {
                    let root_dir = root_dir.clone();
                    async move {
                        Ok::<_, error::Error>(service_fn(move |req| {
                            let root_dir = root_dir.clone();
                            async move {
                                handler::handle_request(
                                    root_dir.as_ref(),
                                    compression,
                                    dir_listing,
                                    &req,
                                )
                                .await
                            }
                        }))
                    }
                });

                tcplistener
                    .set_nonblocking(true)
                    .expect("Cannot set non-blocking");
@@ -145,7 +133,7 @@ impl Server {
                    .unwrap();

                let server =
                    HyperServer::builder(TlsAcceptor::new(tls, incoming)).serve(make_service);
                    HyperServer::builder(TlsAcceptor::new(tls, incoming)).serve(router_service);

                tracing::info!(
                    parent: tracing::info_span!("Server::start_server", ?addr_string, ?threads),
@@ -159,28 +147,10 @@ impl Server {
            // HTTP/1

            tokio::task::spawn(async move {
                let make_service = make_service_fn(move |_| {
                    let root_dir = root_dir.clone();
                    async move {
                        Ok::<_, error::Error>(service_fn(move |req| {
                            let root_dir = root_dir.clone();
                            async move {
                                handler::handle_request(
                                    root_dir.as_ref(),
                                    compression,
                                    dir_listing,
                                    &req,
                                )
                                .await
                            }
                        }))
                    }
                });

                let server = HyperServer::from_tcp(tcplistener)
                    .unwrap()
                    .tcp_nodelay(true)
                    .serve(make_service);
                    .serve(router_service);

                tracing::info!(
                    parent: tracing::info_span!("Server::start_server", ?addr_string, ?threads),
diff --git a/src/service.rs b/src/service.rs
new file mode 100644
index 0000000..3e5ab9e
--- /dev/null
+++ b/src/service.rs
@@ -0,0 +1,75 @@
use hyper::{service::Service, Body, Request, Response};
use std::convert::Infallible;
use std::future::{ready, Future, Ready};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};

use crate::handler::RequestHandler;
use crate::Error;

/// It defines the router service which is the main entry point for Hyper Server.
pub struct RouterService {
    builder: RequestServiceBuilder,
}

impl RouterService {
    pub fn new(handler: RequestHandler) -> Self {
        Self {
            builder: RequestServiceBuilder::new(handler),
        }
    }
}

impl<T> Service<T> for RouterService {
    type Response = RequestService;
    type Error = Infallible;
    type Future = Ready<Result<Self::Response, Self::Error>>;

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

    fn call(&mut self, _: T) -> Self::Future {
        ready(Ok(self.builder.build()))
    }
}

/// It defines a Hyper service request which delegates a request handler.
pub struct RequestService {
    handler: Arc<RequestHandler>,
}

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

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

    fn call(&mut self, mut req: Request<Body>) -> Self::Future {
        let handler = self.handler.clone();
        Box::pin(async move { handler.handle(&mut req).await })
    }
}

/// It defines a Hyper service request builder.
pub struct RequestServiceBuilder {
    handler: Arc<RequestHandler>,
}

impl RequestServiceBuilder {
    pub fn new(handler: RequestHandler) -> Self {
        Self {
            handler: Arc::new(handler),
        }
    }

    pub fn build(&self) -> RequestService {
        RequestService {
            handler: self.handler.clone(),
        }
    }
}