thek4n.ru/src/main.rs

135 lines
3.4 KiB
Rust

mod client;
mod config;
mod handlers;
mod language;
mod templates;
use axum::Router;
use axum::extract::State;
use axum::response::IntoResponse;
use axum::routing::get;
use clap::Parser;
use handlers::{gpg_handler, root_handler};
use sha2::{Digest, Sha256};
use std::net::ToSocketAddrs;
use std::ops::RangeInclusive;
const STATIC_JS: &'static str = include_str!("../assets/script.js");
const STATIC_CSS: &'static str = include_str!("../assets/style.css");
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path to configuration file
#[arg(short, long)]
config: std::path::PathBuf,
/// Network port to use
#[arg(short, long, value_parser = port_in_range, value_name = "PORT", default_value = "8080")]
port: u16,
/// Host to listen
#[arg(long, default_value = "127.0.0.1")]
host: String,
}
#[derive(Clone)]
pub struct AppState {
config_path: std::path::PathBuf,
css_content: &'static str,
js_content: &'static str,
css_url: String,
script_js_url: String,
}
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
fn port_in_range(s: &str) -> Result<u16, String> {
let port: usize = s
.parse()
.map_err(|_| format!("`{s}` isn't a port number"))?;
if PORT_RANGE.contains(&port) {
Ok(port as u16)
} else {
Err(format!(
"port not in range {}-{}",
PORT_RANGE.start(),
PORT_RANGE.end()
))
}
}
#[tokio::main]
async fn main() {
let args = Args::parse();
let css_hash = calculate_sha256(STATIC_CSS);
let js_hash = calculate_sha256(STATIC_JS);
let css_path = format!("/style-{css_hash}.js");
let js_path = format!("/script-{js_hash}.js");
let state = AppState {
config_path: args.config,
css_content: STATIC_CSS,
js_content: STATIC_JS,
css_url: css_path.clone(),
script_js_url: js_path.clone(),
};
let app = Router::new()
.route("/", get(root_handler))
.route("/gpgkey.txt", get(gpg_handler))
.route(css_path.as_str(), get(static_css_handler))
.route(js_path.as_str(), get(static_js_handler))
.with_state(state);
let addr = format!("{}:{}", args.host, args.port)
.to_socket_addrs()
.expect("Fail to get address")
.next()
.ok_or("failed to resolve address")
.expect("");
println!("Server running on http://{}", addr);
axum::serve(
tokio::net::TcpListener::bind(addr)
.await
.expect("Failed to bind to address"),
app,
)
.await
.expect("Server failed to start");
}
async fn static_css_handler(State(state): State<AppState>) -> impl IntoResponse {
let headers = [
(axum::http::header::CONTENT_TYPE, mime::CSS.as_ref()),
(axum::http::header::CACHE_CONTROL, "public, max-age=86400"),
];
(headers, state.css_content).into_response()
}
async fn static_js_handler(State(state): State<AppState>) -> impl IntoResponse {
let headers = [
(
axum::http::header::CONTENT_TYPE,
mime::APPLICATION_JAVASCRIPT.as_ref(),
),
(axum::http::header::CACHE_CONTROL, "public, max-age=86400"),
];
(headers, state.js_content).into_response()
}
fn calculate_sha256(input: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
hex::encode(result)[..6].to_string()
}