diff --git a/src/lib.rs b/src/lib.rs index e75b6be..b88fc0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,10 @@ -use std::{net::SocketAddr, sync::Arc, time::Duration}; +use std::sync::Arc; use axum::{ Router, - http::{Method, header}, routing::{get, post}, }; use tokio::sync::Mutex; -use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder}; -use tower_http::cors::{Any, CorsLayer}; pub mod handlers; pub use handlers::{AppState, render_form, submit}; @@ -17,54 +14,17 @@ pub use config::load_config; use crate::handlers::AppConfig; -/// Start the server with the given configuration and output file. -/// `addr` should be something like "127.0.0.1:8081" -pub async fn run_server(cfg: AppConfig, output_file: String, addr: &str) -> anyhow::Result<()> { - // CORS - let cors = CorsLayer::new() - .allow_origin(Any) - .allow_methods([Method::GET, Method::POST]) - .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]); - - // rate limiter - let governor_conf = GovernorConfigBuilder::default() - .per_second(3) - .burst_size(10) - .finish() - .unwrap(); - - // a separate background task to clean up - let governor_limiter = governor_conf.limiter().clone(); - let interval = Duration::from_secs(60); - std::thread::spawn(move || { - loop { - std::thread::sleep(interval); - // tracing::info!("rate limiting storage size: {}", governor_limiter.len()); - governor_limiter.retain_recent(); - } - }); - +/// Returns an `axum::Router` configured with the `/form` and `/submit` routes +/// This router can be merged into another router using `merge`. +pub fn app_router(cfg: AppConfig, output_file: String) -> Router { let state = AppState { cfg: Arc::new(cfg), file_lock: Arc::new(Mutex::new(())), output_file, }; - let app = Router::new() - .route("/", get(render_form)) + Router::new() + .route("/form", get(render_form)) .route("/submit", post(submit)) .with_state(state) - .layer(cors) - .layer(GovernorLayer::new(governor_conf)); - - let listener = tokio::net::TcpListener::bind(addr).await?; - tracing::info!("Listening on {}", addr); - - axum::serve( - listener, - app.into_make_service_with_connect_info::(), - ) - .await?; - - Ok(()) } diff --git a/src/main.rs b/src/main.rs index 72e3201..69d3985 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,17 @@ +use std::{net::SocketAddr, time::Duration}; + use anyhow::Context; +use axum::{ + Router, + http::{Method, header}, + response::Redirect, + routing::get, +}; use clap::Parser; -use form_generator::config::load_config; -use form_generator::run_server; +use form_generator::{app_router, config::load_config}; +use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder}; +use tower_http::cors::{Any, CorsLayer}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -32,10 +41,50 @@ async fn main() -> anyhow::Result<()> { cfg.fields.len() ); + // CORS + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods([Method::GET, Method::POST]) + .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]); + + // rate limiter + let governor_conf = GovernorConfigBuilder::default() + .per_second(3) + .burst_size(10) + .finish() + .unwrap(); + + // a separate background task to clean up + let governor_limiter = governor_conf.limiter().clone(); + std::thread::spawn(move || { + let interval = Duration::from_secs(60); + loop { + std::thread::sleep(interval); + governor_limiter.retain_recent(); + } + }); + + let app = Router::new() + .merge(app_router(cfg, cli.output_file)) + .route("/", get(form_redirect)) + .layer(cors) + .layer(GovernorLayer::new(governor_conf)); + let port = std::env::var("SERVER_PORT").unwrap_or("8081".to_string()); let addr = format!("127.0.0.1:{port}"); - run_server(cfg, cli.output_file, &addr).await?; + let listener = tokio::net::TcpListener::bind(&addr).await?; + tracing::info!("Listening on {}", addr); + + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await?; Ok(()) } + +async fn form_redirect() -> Redirect { + Redirect::to("/form") +}