From 991d4b8deefade61d35ef278efa28ed7fcb79bed Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Mon, 31 May 2021 23:42:45 +0200 Subject: [PATCH] refactor: introduce a custom service for server --- 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(-) create mode 100644 src/service.rs 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, -) -> Result> { - 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, + ) -> impl Future, 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 Service for RouterService { + type Response = RequestService; + type Error = Infallible; + type Future = Ready>; + + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + 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, +} + +impl Service> for RequestService { + type Response = Response; + type Error = Error; + type Future = Pin, Error>> + Send + 'static>>; + + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, mut req: Request) -> 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, +} + +impl RequestServiceBuilder { + pub fn new(handler: RequestHandler) -> Self { + Self { + handler: Arc::new(handler), + } + } + + pub fn build(&self) -> RequestService { + RequestService { + handler: self.handler.clone(), + } + } +} -- libgit2 1.7.2