From 0c0de262df94196ddc8552db48a0db7ededa6546 Mon Sep 17 00:00:00 2001 From: eiiko6 Date: Mon, 8 Dec 2025 20:41:35 +0100 Subject: [PATCH] added a lib --- README.md | 4 +++ src/lib.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 70 +++-------------------------------------------------- 3 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 src/lib.rs diff --git a/README.md b/README.md index 61c4905..c041f38 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,7 @@ See [config.toml](./config.toml) for an example. * `answer_type`: Type of input (`text`, `number`, `email`, `password`, `url`, `tel`, `textarea`). * `html_before` (optional): HTML snippet rendered before the field. * `html_after` (optional): HTML snippet rendered after the field. + +## Lib + +This crate also provides a library so you can run the form server alongside another app. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e75b6be --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,70 @@ +use std::{net::SocketAddr, sync::Arc, time::Duration}; + +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}; + +pub mod config; +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(); + } + }); + + let state = AppState { + cfg: Arc::new(cfg), + file_lock: Arc::new(Mutex::new(())), + output_file, + }; + + let app = Router::new() + .route("/", 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 f93b0ab..72e3201 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,18 @@ -use std::{net::SocketAddr, sync::Arc, time::Duration}; - use anyhow::Context; -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}; - use clap::Parser; -mod handlers; -use handlers::{AppState, render_form, submit}; -mod config; -use config::load_config; +use form_generator::config::load_config; +use form_generator::run_server; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] -pub struct Cli { - /// Config path +struct Cli { #[arg(short, long, default_value = "config.toml")] config_path: String, - /// Output file #[arg(short, long, default_value = "answers.json")] output_file: String, - /// Verbose mode #[arg(short, long)] verbose: bool, } @@ -40,7 +24,6 @@ async fn main() -> anyhow::Result<()> { let subscriber = tracing_subscriber::FmtSubscriber::new(); tracing::subscriber::set_global_default(subscriber).ok(); - // load config let cfg = load_config(&cli.config_path).context(format!("loading {}", cli.config_path))?; tracing::info!( "Loaded config: '{}', writing answers to '{}' with {} fields", @@ -49,55 +32,10 @@ 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(); - 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(); - } - }); - - let state = AppState { - cfg: Arc::new(cfg), - file_lock: Arc::new(Mutex::new(())), - output_file: cli.output_file.clone(), - }; - - let app = Router::new() - .route("/", get(render_form)) - .route("/submit", post(submit)) - .with_state(state) - .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}"); - let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - tracing::info!("Listening on {addr}"); - - axum::serve( - listener, - app.into_make_service_with_connect_info::(), - ) - .await - .unwrap(); + run_server(cfg, cli.output_file, &addr).await?; Ok(()) }