Skip to content

Commit a94ecdc

Browse files
authored
Merge pull request #40 from stackhpc/refactor
refactor
2 parents 321a130 + fd17afc commit a94ecdc

File tree

5 files changed

+174
-147
lines changed

5 files changed

+174
-147
lines changed

src/app.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ fn router() -> Router {
8686
.nest("/v1", v1())
8787
}
8888

89-
/// Returns a [tower_service::Service] for the Active Storage server API
89+
/// S3 Active Storage Server Service type alias
90+
///
91+
/// This type implements [tower_service::Service].
92+
// FIXME: The Service type should be some form of tower_service::Service, but couldn't find the
93+
// necessary trait bounds.
94+
pub type Service = tower_http::normalize_path::NormalizePath<Router>;
95+
96+
/// Returns a [crate::app::Service] for the Active Storage server API
9097
///
9198
/// The service is populated with all routes as well as the following middleware:
9299
///
@@ -95,10 +102,7 @@ fn router() -> Router {
95102
/// headers
96103
/// * a [tower_http::normalize_path::NormalizePathLayer] for trimming trailing slashes from
97104
/// requests
98-
pub fn service() -> tower_http::normalize_path::NormalizePath<Router> {
99-
// FIXME: The return type should be some form of tower_service::Service, but couldn't find the
100-
// necessary trait bounds.
101-
105+
pub fn service() -> Service {
102106
// Note that any middleware that should affect routing must wrap the router.
103107
// See
104108
// https://docs.rs/axum/0.6.18/axum/middleware/index.html#rewriting-request-uri-in-middleware.

src/cli.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! Command Line Interface (CLI) arguments.
2+
3+
use clap::Parser;
4+
5+
/// S3 Active Storage Proxy command line interface
6+
#[derive(Debug, Parser)]
7+
pub struct CommandLineArgs {
8+
/// The IP address on which the proxy should listen
9+
#[arg(long, default_value = "0.0.0.0", env = "S3_ACTIVE_STORAGE_HOST")]
10+
pub host: String,
11+
/// The port to which the proxy should bind
12+
#[arg(long, default_value_t = 8080, env = "S3_ACTIVE_STORAGE_PORT")]
13+
pub port: u16,
14+
/// Flag indicating whether HTTPS should be used
15+
#[arg(long, default_value_t = false, env = "S3_ACTIVE_STORAGE_HTTPS")]
16+
pub https: bool,
17+
/// Path to the certificate file to be used for HTTPS encryption
18+
#[arg(
19+
long,
20+
default_value = "~/.config/s3-active-storage/certs/cert.pem",
21+
env = "S3_ACTIVE_STORAGE_CERT_FILE"
22+
)]
23+
pub cert_file: String,
24+
/// Path to the key file to be used for HTTPS encryption
25+
#[arg(
26+
long,
27+
default_value = "~/.config/s3-active-storage/certs/key.pem",
28+
env = "S3_ACTIVE_STORAGE_KEY_FILE"
29+
)]
30+
pub key_file: String,
31+
/// Maximum time in seconds to wait for operations to complete upon receiving `ctrl+c` signal.
32+
#[arg(long, default_value_t = 60, env = "S3_ACTIVE_STORAGE_SHUTDOWN_TIMEOUT")]
33+
pub graceful_shutdown_timeout: u64,
34+
}
35+
36+
/// Returns parsed command line arguments.
37+
pub fn parse() -> CommandLineArgs {
38+
CommandLineArgs::parse()
39+
}

src/main.rs

Lines changed: 6 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -22,159 +22,23 @@
2222
//! * [ndarray] provides [NumPy](https://numpy.orgq)-like n-dimensional arrays used in numerical
2323
//! computation.
2424
25-
use std::{net::SocketAddr, process::exit, str::FromStr, time::Duration};
26-
27-
use axum::ServiceExt;
28-
use axum_server::{tls_rustls::RustlsConfig, Handle};
29-
use clap::Parser;
30-
use expanduser::expanduser;
31-
use tokio::signal;
32-
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
33-
3425
mod app;
3526
mod array;
27+
mod cli;
3628
mod error;
3729
mod models;
3830
mod operation;
3931
mod operations;
4032
mod s3_client;
33+
mod server;
34+
mod tracing;
4135
mod validated_json;
4236

43-
/// S3 Active Storage Proxy command line interface
44-
#[derive(Debug, Parser)]
45-
struct CommandLineArgs {
46-
/// The IP address on which the proxy should listen
47-
#[arg(long, default_value = "0.0.0.0", env = "S3_ACTIVE_STORAGE_HOST")]
48-
host: String,
49-
/// The port to which the proxy should bind
50-
#[arg(long, default_value_t = 8080, env = "S3_ACTIVE_STORAGE_PORT")]
51-
port: u16,
52-
/// Flag indicating whether HTTPS should be used
53-
#[arg(long, default_value_t = false, env = "S3_ACTIVE_STORAGE_HTTPS")]
54-
https: bool,
55-
/// Path to the certificate file to be used for HTTPS encryption
56-
#[arg(
57-
long,
58-
default_value = "~/.config/s3-active-storage/certs/cert.pem",
59-
env = "S3_ACTIVE_STORAGE_CERT_FILE"
60-
)]
61-
cert_file: String,
62-
/// Path to the key file to be used for HTTPS encryption
63-
#[arg(
64-
long,
65-
default_value = "~/.config/s3-active-storage/certs/key.pem",
66-
env = "S3_ACTIVE_STORAGE_KEY_FILE"
67-
)]
68-
key_file: String,
69-
/// Maximum time in seconds to wait for operations to complete upon receiving `ctrl+c` signal.
70-
#[arg(long, default_value_t = 60, env = "S3_ACTIVE_STORAGE_SHUTDOWN_TIMEOUT")]
71-
graceful_shutdown_timeout: u64,
72-
}
73-
7437
/// Application entry point
7538
#[tokio::main]
7639
async fn main() {
77-
let args = CommandLineArgs::parse();
78-
79-
init_tracing();
80-
40+
let args = cli::parse();
41+
tracing::init_tracing();
8142
let service = app::service();
82-
let addr = SocketAddr::from_str(&format!("{}:{}", args.host, args.port))
83-
.expect("invalid host name, IP address or port number");
84-
85-
// Catch ctrl+c and try to shutdown gracefully
86-
let handle = Handle::new();
87-
tokio::spawn(shutdown_signal(
88-
handle.clone(),
89-
args.graceful_shutdown_timeout,
90-
));
91-
92-
if args.https {
93-
// Expand files
94-
let abs_cert_file = expanduser(args.cert_file)
95-
.expect("Failed to expand ~ to user name. Please provide an absolute path instead.")
96-
.canonicalize()
97-
.expect("failed to determine absolute path to TLS cerficate file");
98-
let abs_key_file = expanduser(args.key_file)
99-
.expect("Failed to expand ~ to user name. Please provide an absolute path instead.")
100-
.canonicalize()
101-
.expect("failed to determine absolute path to TLS key file");
102-
// Check files exist
103-
if !abs_cert_file.exists() {
104-
println!(
105-
"TLS certificate file expected at '{}' but not found.",
106-
abs_cert_file.display()
107-
);
108-
exit(1)
109-
}
110-
if !abs_key_file.exists() {
111-
println!(
112-
"TLS key file expected at '{}' but not found.",
113-
abs_key_file.display()
114-
);
115-
exit(1)
116-
}
117-
// Set up TLS config
118-
let tls_config = RustlsConfig::from_pem_file(abs_cert_file, abs_key_file)
119-
.await
120-
.expect("Failed to load TLS certificate files");
121-
// run HTTPS server with hyper
122-
axum_server::bind_rustls(addr, tls_config)
123-
.handle(handle)
124-
.serve(service.into_make_service())
125-
.await
126-
.unwrap();
127-
} else {
128-
// run HTTP server with hyper
129-
axum_server::bind(addr)
130-
.handle(handle)
131-
.serve(service.into_make_service())
132-
.await
133-
.unwrap();
134-
}
135-
}
136-
137-
/// Initlialise tracing (logging)
138-
///
139-
/// Applies a filter based on the `RUST_LOG` environment variable, falling back to enable debug
140-
/// logging for this crate and tower_http if not set.
141-
fn init_tracing() {
142-
tracing_subscriber::registry()
143-
.with(
144-
tracing_subscriber::EnvFilter::try_from_default_env()
145-
.unwrap_or_else(|_| "s3_active_storage=debug,tower_http=debug".into()),
146-
)
147-
.with(tracing_subscriber::fmt::layer())
148-
.init();
149-
}
150-
151-
/// Graceful shutdown handler
152-
///
153-
/// Installs signal handlers to catch Ctrl-C or SIGTERM and trigger a graceful shutdown.
154-
async fn shutdown_signal(handle: Handle, timeout: u64) {
155-
let ctrl_c = async {
156-
signal::ctrl_c()
157-
.await
158-
.expect("failed to install Ctrl+C handler");
159-
};
160-
161-
#[cfg(unix)]
162-
let terminate = async {
163-
signal::unix::signal(signal::unix::SignalKind::terminate())
164-
.expect("failed to install signal handler")
165-
.recv()
166-
.await;
167-
};
168-
169-
#[cfg(not(unix))]
170-
let terminate = std::future::pending::<()>();
171-
172-
tokio::select! {
173-
_ = ctrl_c => {},
174-
_ = terminate => {},
175-
}
176-
177-
println!("signal received, starting graceful shutdown");
178-
// Force shutdown if graceful shutdown takes longer than 10s
179-
handle.graceful_shutdown(Some(Duration::from_secs(timeout)));
43+
server::serve(&args, service).await;
18044
}

src/server.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//! Web server
2+
3+
use crate::cli;
4+
5+
use std::{net::SocketAddr, process::exit, str::FromStr, time::Duration};
6+
7+
use axum::ServiceExt;
8+
use axum_server::{tls_rustls::RustlsConfig, Handle};
9+
use expanduser::expanduser;
10+
use tokio::signal;
11+
12+
/// Serve the S3 Active Storage service
13+
///
14+
/// # Arguments
15+
///
16+
/// * `args`: Command line arguments
17+
/// * `service`: The [crate::app::Service] to serve
18+
pub async fn serve(args: &cli::CommandLineArgs, service: crate::app::Service) {
19+
let addr = SocketAddr::from_str(&format!("{}:{}", args.host, args.port))
20+
.expect("invalid host name, IP address or port number");
21+
22+
// Catch ctrl+c and try to shutdown gracefully
23+
let handle = Handle::new();
24+
tokio::spawn(shutdown_signal(
25+
handle.clone(),
26+
args.graceful_shutdown_timeout,
27+
));
28+
29+
if args.https {
30+
// Expand files
31+
let abs_cert_file = expanduser(&args.cert_file)
32+
.expect("Failed to expand ~ to user name. Please provide an absolute path instead.")
33+
.canonicalize()
34+
.expect("failed to determine absolute path to TLS cerficate file");
35+
let abs_key_file = expanduser(&args.key_file)
36+
.expect("Failed to expand ~ to user name. Please provide an absolute path instead.")
37+
.canonicalize()
38+
.expect("failed to determine absolute path to TLS key file");
39+
// Check files exist
40+
if !abs_cert_file.exists() {
41+
println!(
42+
"TLS certificate file expected at '{}' but not found.",
43+
abs_cert_file.display()
44+
);
45+
exit(1)
46+
}
47+
if !abs_key_file.exists() {
48+
println!(
49+
"TLS key file expected at '{}' but not found.",
50+
abs_key_file.display()
51+
);
52+
exit(1)
53+
}
54+
// Set up TLS config
55+
let tls_config = RustlsConfig::from_pem_file(abs_cert_file, abs_key_file)
56+
.await
57+
.expect("Failed to load TLS certificate files");
58+
// run HTTPS server with hyper
59+
axum_server::bind_rustls(addr, tls_config)
60+
.handle(handle)
61+
.serve(service.into_make_service())
62+
.await
63+
.unwrap();
64+
} else {
65+
// run HTTP server with hyper
66+
axum_server::bind(addr)
67+
.handle(handle)
68+
.serve(service.into_make_service())
69+
.await
70+
.unwrap();
71+
}
72+
}
73+
74+
/// Graceful shutdown handler
75+
///
76+
/// Installs signal handlers to catch Ctrl-C or SIGTERM and trigger a graceful shutdown.
77+
async fn shutdown_signal(handle: Handle, timeout: u64) {
78+
let ctrl_c = async {
79+
signal::ctrl_c()
80+
.await
81+
.expect("failed to install Ctrl+C handler");
82+
};
83+
84+
#[cfg(unix)]
85+
let terminate = async {
86+
signal::unix::signal(signal::unix::SignalKind::terminate())
87+
.expect("failed to install signal handler")
88+
.recv()
89+
.await;
90+
};
91+
92+
#[cfg(not(unix))]
93+
let terminate = std::future::pending::<()>();
94+
95+
tokio::select! {
96+
_ = ctrl_c => {},
97+
_ = terminate => {},
98+
}
99+
100+
println!("signal received, starting graceful shutdown");
101+
// Force shutdown if graceful shutdown takes longer than 10s
102+
handle.graceful_shutdown(Some(Duration::from_secs(timeout)));
103+
}

src/tracing.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! Tracing (logging)
2+
3+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
4+
5+
/// Initlialise tracing (logging)
6+
///
7+
/// Applies a filter based on the `RUST_LOG` environment variable, falling back to enable debug
8+
/// logging for this crate and tower_http if not set.
9+
pub fn init_tracing() {
10+
tracing_subscriber::registry()
11+
.with(
12+
tracing_subscriber::EnvFilter::try_from_default_env()
13+
.unwrap_or_else(|_| "s3_active_storage=debug,tower_http=debug".into()),
14+
)
15+
.with(tracing_subscriber::fmt::layer())
16+
.init();
17+
}

0 commit comments

Comments
 (0)