about summary refs log tree commit diff
path: root/kittybox-rs/src/main.rs
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-07-07 00:32:33 +0300
committerVika <vika@fireburn.ru>2022-07-07 00:36:39 +0300
commit7f23ec84bc05c236c1bf40c2f0d72412af711516 (patch)
treef0ba64804fffce29a8f04e5b6c76f9863de81dd2 /kittybox-rs/src/main.rs
parent5cfac54aa4fb3c207ea2cbbeccd4571fa204a09b (diff)
treewide: rewrite using Axum
Axum has streaming bodies and allows to write simpler code. It also
helps enforce stronger types and looks much more neat.

This allows me to progress on the media endpoint and add streaming
reads and writes to the MediaStore trait.

Metrics are temporarily not implemented. Everything else was
preserved, and the tests still pass, after adjusting for new calling
conventions.

TODO: create method routers for protocol endpoints
Diffstat (limited to 'kittybox-rs/src/main.rs')
-rw-r--r--kittybox-rs/src/main.rs166
1 files changed, 115 insertions, 51 deletions
diff --git a/kittybox-rs/src/main.rs b/kittybox-rs/src/main.rs
index fd1875c..ef051ba 100644
--- a/kittybox-rs/src/main.rs
+++ b/kittybox-rs/src/main.rs
@@ -1,17 +1,16 @@
-use log::{debug, error, info};
-use std::{convert::Infallible, env, time::Duration};
+use kittybox::database::FileStorage;
+use std::{env, time::Duration};
+use tracing::{debug, error, info};
 use url::Url;
-use warp::{Filter, host::Authority};
 
 #[tokio::main]
 async fn main() {
-    // TODO turn into a feature so I can enable and disable it
-    #[cfg(debug_assertions)]
-    console_subscriber::init();
-
     // TODO use tracing instead of log
-    let logger_env = env_logger::Env::new().filter_or("RUST_LOG", "info");
-    env_logger::init_from_env(logger_env);
+    use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
+    Registry::default()
+        .with(EnvFilter::from_default_env())
+        .with(tracing_subscriber::fmt::layer().json())
+        .init();
 
     info!("Starting the kittybox server...");
 
@@ -63,24 +62,22 @@ async fn main() {
     let listen_at = match env::var("SERVE_AT")
         .ok()
         .unwrap_or_else(|| "[::]:8080".to_string())
-        .parse::<std::net::SocketAddr>() {
-            Ok(addr) => addr,
-            Err(e) => {
-                error!("Cannot parse SERVE_AT: {}", e);
-                std::process::exit(1);
-            }
-        };
+        .parse::<std::net::SocketAddr>()
+    {
+        Ok(addr) => addr,
+        Err(e) => {
+            error!("Cannot parse SERVE_AT: {}", e);
+            std::process::exit(1);
+        }
+    };
 
-    // This thing handles redirects automatically but is type-incompatible with hyper::Client
-    // Bonus: less generics to be aware of, this thing hides its complexity
     let http: reqwest::Client = {
         #[allow(unused_mut)]
-        let mut builder = reqwest::Client::builder()
-            .user_agent(concat!(
-                env!("CARGO_PKG_NAME"),
-                "/",
-                env!("CARGO_PKG_VERSION")
-            ));
+        let mut builder = reqwest::Client::builder().user_agent(concat!(
+            env!("CARGO_PKG_NAME"),
+            "/",
+            env!("CARGO_PKG_VERSION")
+        ));
         // TODO: add a root certificate if there's an environment variable pointing at it
         //builder = builder.add_root_certificate(reqwest::Certificate::from_pem(todo!()));
 
@@ -109,12 +106,8 @@ async fn main() {
             webmention: None,
             microsub: None,
         };
-        
-        let homepage = warp::get()
-            .and(warp::path::end())
-            .and(kittybox::frontend::homepage(database.clone(), endpoints.clone()));
-        
-        let micropub = warp::path("micropub")
+
+        /*let micropub = warp::path("micropub")
             .and(warp::path::end()
                  .and(kittybox::micropub::micropub(
                      database.clone(),
@@ -169,11 +162,8 @@ async fn main() {
         // TODO prettier error response
         let coffee = warp::path("coffee")
             .map(|| warp::reply::with_status("I'm a teapot!", warp::http::StatusCode::IM_A_TEAPOT));
-        
-        let catchall = kittybox::frontend::catchall(
-            database.clone(),
-            endpoints.clone()
-        );
+
+        et catchall = ;
 
         let app = homepage
             .or(technical)
@@ -186,29 +176,103 @@ async fn main() {
             ;
 
         let svc = warp::service(app);
+         */
 
-        // A little dance to turn a potential file descriptor into an async network socket
-        let mut listenfd = listenfd::ListenFd::from_env();
-        let tcp_listener: std::net::TcpListener = if let Ok(Some(listener)) = listenfd.take_tcp_listener(0) {
-            listener
-        } else {
-            std::net::TcpListener::bind(listen_at).unwrap()
-        };
-        // Set the socket to non-blocking so tokio can work with it properly
-        // This is the async magic
-        tcp_listener.set_nonblocking(true).unwrap();
+        let svc = axum::Router::new()
+            .route(
+                "/",
+                axum::routing::get(kittybox::frontend::homepage::<FileStorage>),
+            )
+            .route(
+                "/.kittybox/coffee",
+                axum::routing::get(|| async {
+                    use axum::http::{header, StatusCode};
+                    (
+                        StatusCode::IM_A_TEAPOT,
+                        [(header::CONTENT_TYPE, "text/plain")],
+                        "Sorry, can't brew coffee yet!",
+                    )
+                }),
+            )
+            .route(
+                "/.kittybox/onboarding",
+                axum::routing::get(kittybox::frontend::onboarding::get)
+                    .post(kittybox::frontend::onboarding::post::<FileStorage>)
+            )
+            .route(
+                "/.kittybox/micropub",
+                axum::routing::get(kittybox::micropub::query::<FileStorage>)
+                    .post(kittybox::micropub::post::<FileStorage>)
+                    .layer(tower_http::cors::CorsLayer::new()
+                           .allow_methods([axum::http::Method::GET, axum::http::Method::POST])
+                           .allow_origin(tower_http::cors::Any)),
+            )
+            .route(
+                "/.kittybox/micropub/client",
+                axum::routing::get(|| {
+                    std::future::ready(axum::response::Html(kittybox::MICROPUB_CLIENT))
+                }),
+            )
+            .route(
+                "/.kittybox/health",
+                axum::routing::get(|| async {
+                    // TODO health-check the database
+                    "OK"
+                }),
+            )
+            .route(
+                "/.kittybox/metrics",
+                axum::routing::get(|| async { todo!() }),
+            )
+            .nest(
+                "/.kittybox/media",
+                axum::Router::new()
+                    .route(
+                        "/",
+                        axum::routing::get(|| async { todo!() }).post(|| async { todo!() }),
+                    )
+                    .route("/:filename", axum::routing::get(|| async { todo!() })),
+            )
+            .route(
+                "/.kittybox/static/:path",
+                axum::routing::get(kittybox::frontend::statics),
+            )
+            .fallback(axum::routing::get(
+                kittybox::frontend::catchall::<FileStorage>,
+            ))
+            .layer(axum::Extension(database))
+            .layer(axum::Extension(http))
+            .layer(axum::Extension(kittybox::indieauth::TokenEndpoint(
+                token_endpoint,
+            )))
+            .layer(
+                tower::ServiceBuilder::new()
+                    .layer(tower_http::trace::TraceLayer::new_for_http())
+                    .into_inner(),
+            );
+
+        // A little dance to turn a potential file descriptor into a guaranteed async network socket
+        let tcp_listener: std::net::TcpListener = {
+            let mut listenfd = listenfd::ListenFd::from_env();
 
+            let tcp_listener = if let Ok(Some(listener)) = listenfd.take_tcp_listener(0) {
+                listener
+            } else {
+                std::net::TcpListener::bind(listen_at).unwrap()
+            };
+            // Set the socket to non-blocking so tokio can work with it properly
+            // This is the async magic
+            tcp_listener.set_nonblocking(true).unwrap();
+
+            tcp_listener
+        };
         info!("Listening on {}", tcp_listener.local_addr().unwrap());
+
         let server = hyper::server::Server::from_tcp(tcp_listener)
             .unwrap()
             // Otherwise Chrome keeps connections open for too long
             .tcp_keepalive(Some(Duration::from_secs(30 * 60)))
-            .serve(hyper::service::make_service_fn(move |_| {
-                let service = svc.clone();
-                async move {
-                    Ok::<_, Infallible>(service)
-                }
-            }))
+            .serve(svc.into_make_service())
             .with_graceful_shutdown(async move {
                 // Defer to C-c handler whenever we're not on Unix
                 // TODO consider using a diverging future here