carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1//! todo-rust: the CARLOS bakeoff's TodoMVC, in Rust.
2//!
3//! todo-rust --socket <path> --db <path> (on Carloku)
4//! todo-rust --listen 127.0.0.1:<port> --db <path> (locally)
5
6use todo_rust::{app, db};
7
8use std::net::SocketAddr;
9use std::path::PathBuf;
10use std::process::ExitCode;
11
12use tokio::signal::unix::{SignalKind, signal};
13
14// musl's allocator is slow under many threads; mimalloc is a drop-in fix.
15#[global_allocator]
16static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
17
18/// The git short SHA, stamped in by `make` (see the Makefile).
19const VERSION: &str = match option_env!("APP_VERSION") {
20 Some(v) => v,
21 None => "dev",
22};
23
24enum Bind {
25 Socket(PathBuf),
26 Listen(SocketAddr),
27}
28
29struct Args {
30 bind: Bind,
31 db: PathBuf,
32}
33
34fn parse_args() -> Result<Args, String> {
35 let mut socket = None;
36 let mut listen = None;
37 let mut db = None;
38 let mut it = std::env::args().skip(1);
39 while let Some(flag) = it.next() {
40 let mut value = || it.next().ok_or_else(|| format!("{flag} needs a value"));
41 match flag.as_str() {
42 "--socket" => socket = Some(PathBuf::from(value()?)),
43 "--listen" => {
44 let addr: SocketAddr = value()?.parse().map_err(|e| format!("--listen: {e}"))?;
45 if !addr.ip().is_loopback() {
46 return Err("--listen only binds a loopback address, e.g. 127.0.0.1:8080".into());
47 }
48 listen = Some(addr);
49 }
50 "--db" => db = Some(PathBuf::from(value()?)),
51 "--version" => {
52 println!("{VERSION}");
53 std::process::exit(0);
54 }
55 other => return Err(format!("unknown argument {other}")),
56 }
57 }
58 let bind = match (socket, listen) {
59 (Some(path), None) => Bind::Socket(path),
60 (None, Some(addr)) => Bind::Listen(addr),
61 _ => return Err("pass exactly one of --socket <path> or --listen 127.0.0.1:<port>".into()),
62 };
63 let db = db.ok_or("--db <path> is required")?;
64 Ok(Args { bind, db })
65}
66
67/// The origin browsers will send with our own form posts.
68fn own_origin(bind: &Bind) -> String {
69 match bind {
70 Bind::Listen(addr) => format!("http://{addr}"),
71 Bind::Socket(_) => match std::env::var("CARLOS_HOST") {
72 Ok(host) if !host.is_empty() => format!("https://{host}"),
73 _ => {
74 eprintln!("CARLOS_HOST is not set; every cross-origin POST will be refused");
75 "https://localhost".into()
76 }
77 },
78 }
79}
80
81async fn shutdown() {
82 let mut term = signal(SignalKind::terminate()).expect("SIGTERM handler");
83 let mut int = signal(SignalKind::interrupt()).expect("SIGINT handler");
84 tokio::select! {
85 _ = term.recv() => {}
86 _ = int.recv() => {}
87 }
88}
89
90#[tokio::main]
91async fn main() -> ExitCode {
92 let args = match parse_args() {
93 Ok(a) => a,
94 Err(e) => {
95 eprintln!("todo-rust: {e}");
96 return ExitCode::from(2);
97 }
98 };
99 let db = match db::Db::open(&args.db) {
100 Ok(db) => db,
101 Err(e) => {
102 eprintln!("todo-rust: opening {}: {e}", args.db.display());
103 return ExitCode::FAILURE;
104 }
105 };
106 let state = app::AppState { db, origin: own_origin(&args.bind), version: VERSION };
107 let router = app::router(state);
108
109 let served = match &args.bind {
110 Bind::Listen(addr) => match tokio::net::TcpListener::bind(addr).await {
111 Ok(l) => {
112 eprintln!("todo-rust {VERSION} listening on http://{addr}");
113 axum::serve(l, router).with_graceful_shutdown(shutdown()).await
114 }
115 Err(e) => Err(e),
116 },
117 Bind::Socket(path) => {
118 // A socket left behind by an earlier run would make bind fail.
119 let _ = std::fs::remove_file(path);
120 match tokio::net::UnixListener::bind(path) {
121 Ok(l) => {
122 eprintln!("todo-rust {VERSION} listening on {}", path.display());
123 let r = axum::serve(l, router).with_graceful_shutdown(shutdown()).await;
124 let _ = std::fs::remove_file(path);
125 r
126 }
127 Err(e) => Err(e),
128 }
129 }
130 };
131 match served {
132 Ok(()) => ExitCode::SUCCESS,
133 Err(e) => {
134 eprintln!("todo-rust: {e}");
135 ExitCode::FAILURE
136 }
137 }
138}
139