static_file_server_lib/startup/
server.rs

1use crate::cli::CliArgs;
2use crate::terminal::colored_msg;
3use anyhow::anyhow;
4use axum::http::{header, HeaderValue};
5use axum::Router;
6use std::fs;
7use std::net::SocketAddr;
8use termcolor::Color;
9use tokio::net::TcpListener;
10use tower_http::services::ServeDir;
11use tower_http::set_header::{SetRequestHeaderLayer, SetResponseHeaderLayer};
12use tower_http::trace::TraceLayer;
13use tracing_subscriber::layer::SubscriberExt;
14use tracing_subscriber::util::SubscriberInitExt;
15
16pub struct Server {
17    port: u16,
18    listener: TcpListener,
19    addr: String,
20    cli_args: CliArgs,
21    routes: Router,
22}
23
24impl Server {
25    pub async fn new(cli_args: &CliArgs) -> anyhow::Result<Server> {
26        tracing_subscriber::registry()
27            .with(
28                tracing_subscriber::EnvFilter::try_from_default_env()
29                    .unwrap_or_else(|_| "static_file_server=debug,tower_http=debug".into()),
30            )
31            .with(tracing_subscriber::fmt::layer())
32            .init();
33
34        let routes = Server::routes(cli_args)?;
35        let listener = Server::listen(cli_args).await?;
36        let port = listener.local_addr()?.port();
37
38        Ok(Server {
39            port,
40            addr: format!("http://localhost:{}", port),
41            cli_args: cli_args.clone(),
42            listener,
43            routes,
44        })
45    }
46
47    fn routes(cli_args: &CliArgs) -> anyhow::Result<Router> {
48        if fs::metadata(&cli_args.dir).is_err() {
49            colored_msg(&format!("Dir {} does not exist", cli_args.dir), Color::Red)?;
50            return Err(anyhow!(format!("Dir {} does not exist", cli_args.dir)));
51        } else {
52            colored_msg(&format!("Found dir {}", cli_args.dir), Color::Green)?;
53        }
54        let mut routes: Router;
55        routes = if cli_args.compression_dir {
56            Router::new().nest_service("/", ServeDir::new(&cli_args.dir).precompressed_gzip())
57        } else {
58            Router::new().nest_service("/", ServeDir::new(&cli_args.dir))
59        };
60        routes = if cli_args.compression_response {
61            routes
62                .layer(tower_http::compression::CompressionLayer::new())
63                .layer(SetRequestHeaderLayer::if_not_present(
64                    header::ACCEPT_ENCODING,
65                    HeaderValue::from_static("gzip"),
66                ))
67        } else {
68            routes
69        };
70        routes = if cli_args.cors {
71            routes
72                .layer(SetResponseHeaderLayer::if_not_present(
73                    header::ACCESS_CONTROL_ALLOW_METHODS,
74                    HeaderValue::from_static("*"),
75                ))
76                .layer(SetResponseHeaderLayer::if_not_present(
77                    header::ACCESS_CONTROL_ALLOW_HEADERS,
78                    HeaderValue::from_static("*"),
79                ))
80                .layer(SetResponseHeaderLayer::if_not_present(
81                    header::ACCESS_CONTROL_ALLOW_ORIGIN,
82                    HeaderValue::from_static("*"),
83                ))
84        } else {
85            routes
86        };
87        Ok(routes)
88    }
89
90    async fn listen(args: &CliArgs) -> anyhow::Result<TcpListener> {
91        let addr = SocketAddr::from(([127, 0, 0, 1], args.port));
92        let listener = TcpListener::bind(addr).await?;
93        Ok(listener)
94    }
95
96    pub async fn serve(self) -> anyhow::Result<()> {
97        colored_msg(&format!("Options:\n{:#?}", &self.cli_args,), Color::Green)?;
98        colored_msg(
99            &format!(
100                "Listening on = http://localhost:{} | {}",
101                &self.port, &self.addr
102            ),
103            Color::Green,
104        )?;
105        axum::serve(self.listener, self.routes.layer(TraceLayer::new_for_http()))
106            .await
107            .unwrap();
108        Ok(())
109    }
110}