From 9e4c4551a786830bf34d74c4ef111a8ed292fa9f Mon Sep 17 00:00:00 2001 From: Vika Date: Tue, 15 Feb 2022 02:44:33 +0300 Subject: WIP: convert to Tokio and Warp Warp allows requests to be applied as "filters", allowing to flexibly split up logic and have it work in a functional style, similar to pipes. Tokio is just an alternative runtime. I thought that maybe switching runtimes and refactoring the code might allow me to fish out that pesky bug with the whole application hanging after a certain amount of requests... --- src/lib.rs | 270 +++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 166 insertions(+), 104 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 2b4d1cc..2585227 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,87 +1,172 @@ -use tide::{Request, Response}; - -/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database. -pub mod database; +//use tide::{Request, Response}; +use warp::Filter; +/*pub mod database; mod frontend; mod indieauth; -mod metrics; -mod micropub; +mod micropub;*/ +pub mod metrics; +/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database. +pub mod database; +pub mod micropub; +//pub mod indieauth; -use crate::indieauth::IndieAuthMiddleware; -use crate::micropub::CORSMiddleware; +/*use crate::indieauth::IndieAuthMiddleware; +use crate::micropub::CORSMiddleware;*/ -#[derive(Clone)] -pub struct ApplicationState -where - StorageBackend: database::Storage + Send + Sync + 'static, -{ - token_endpoint: surf::Url, - authorization_endpoint: surf::Url, - media_endpoint: Option, - internal_token: Option, - cookie_secret: String, - http_client: surf::Client, - storage: StorageBackend, +pub mod rejections { + #[derive(Debug)] + pub struct UnacceptableContentType; + impl warp::reject::Reject for UnacceptableContentType {} + + #[derive(Debug)] + pub struct HostHeaderUnset; + impl warp::reject::Reject for HostHeaderUnset {} } -type App = tide::Server>; - -static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html"); - -fn equip_app(mut app: App) -> App -where - Storage: database::Storage + Send + Sync + Clone, -{ - app.at("/micropub") - .with(CORSMiddleware {}) - .with(IndieAuthMiddleware::new()) - .get(micropub::get_handler) - .post(micropub::post_handler); - // The Micropub client. It'll start small, but could grow into something full-featured - app.at("/micropub/client").get(|_: Request<_>| async move { - Ok(Response::builder(200) - .body(MICROPUB_CLIENT) - .content_type("text/html") - .build()) - }); - app.at("/") - .with(CORSMiddleware {}) - .with(frontend::ErrorHandlerMiddleware {}) - .get(frontend::mainpage) - .post(frontend::onboarding_receiver); - app.at("/login") - .with(frontend::ErrorHandlerMiddleware {}) - .get(frontend::login::form) - .post(frontend::login::handler); - app.at("/login/callback") - .with(frontend::ErrorHandlerMiddleware {}) - .get(frontend::login::callback); - app.at("/static/*path") - .with(frontend::ErrorHandlerMiddleware {}) - .get(frontend::handle_static); - app.at("/*path") - .with(frontend::ErrorHandlerMiddleware {}) - .get(frontend::render_post); - app.at("/coffee") - .with(frontend::ErrorHandlerMiddleware {}) - .get(frontend::coffee); - // TODO make sure the health check actually checks the backend or something - // otherwise it'll get false-negatives for application faults like resource - // exhaustion - app.at("/health").get(|_| async { Ok("OK") }); - app.at("/metrics").get(metrics::gather); - - app.with(metrics::InstrumentationMiddleware {}); - app.with( - tide::sessions::SessionMiddleware::new( - tide::sessions::CookieStore::new(), - app.state().cookie_secret.as_bytes(), - ) - .with_cookie_name("kittybox_session") - .without_save_unchanged(), - ); - app +pub static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html"); + +pub mod util { + use warp::{Filter, host::Authority}; + use super::rejections; + + pub fn require_host() -> impl Filter + Copy { + warp::host::optional() + .and_then(|authority: Option| async move { + authority.ok_or_else(|| warp::reject::custom(rejections::HostHeaderUnset)) + }) + } + + pub fn template( + template: R + ) -> impl warp::Reply + where + R: markup::Render + std::fmt::Display + { + warp::reply::html(template.to_string()) + } + + pub fn parse_accept() -> impl Filter + Copy { + warp::header::value("Accept").and_then(|accept: warp::http::HeaderValue| async move { + let mut accept: http_types::content::Accept = { + // This is unneccesarily complicated because I want to reuse some http-types parsing + // and http-types has constructor for Headers private so I need to construct + // a mock Request to reason about headers... this is so dumb wtf + let bytes: &[u8] = accept.as_bytes(); + let value = http_types::headers::HeaderValue::from_bytes(bytes.to_vec()).unwrap(); + let values: http_types::headers::HeaderValues = vec![value].into(); + let mut request = http_types::Request::new(http_types::Method::Get, "http://example.com/"); + request.append_header("Accept".parse::().unwrap(), &values); + http_types::content::Accept::from_headers(&request).unwrap().unwrap() + }; + + // This code is INCREDIBLY dumb, honestly... + // why did I even try to use it? + // TODO vendor this stuff in so I can customize it + match accept.negotiate(&[ + "text/html; encoding=\"utf-8\"".into(), + "application/json; encoding=\"utf-8\"".into(), + "text/html".into(), + "application/json".into(), + + ]) { + Ok(mime) => { + Ok(http_types::Mime::from(mime.value().as_str())) + }, + Err(err) => { + log::error!("Content-Type negotiation error: {:?}, accepting: {:?}", err, accept); + Err(warp::reject::custom(rejections::UnacceptableContentType)) + } + } + }) + } + + mod tests { + #[tokio::test] + async fn test_require_host_with_host() { + use super::require_host; + + let filter = require_host(); + + let res = warp::test::request() + .path("/") + .header("Host", "localhost:8080") + .filter(&filter) + .await + .unwrap(); + + assert_eq!(res, "localhost:8080"); + + } + + #[tokio::test] + async fn test_require_host_no_host() { + use super::require_host; + + let filter = require_host(); + + let res = warp::test::request() + .path("/") + .filter(&filter) + .await; + + assert!(res.is_err()); + } + } } +// fn equip_app(mut app: App) -> App +// where +// Storage: database::Storage + Send + Sync + Clone, +// { +// app.at("/micropub") +// .with(CORSMiddleware {}) +// .with(IndieAuthMiddleware::new()) +// .get(micropub::get_handler) +// .post(micropub::post_handler); +// // The Micropub client. It'll start small, but could grow into something full-featured +// app.at("/micropub/client").get(|_: Request<_>| async move { +// Ok(Response::builder(200) +// .body(MICROPUB_CLIENT) +// .content_type("text/html") +// .build()) +// }); +// app.at("/") +// .with(CORSMiddleware {}) +// .with(frontend::ErrorHandlerMiddleware {}) +// .get(frontend::mainpage) +// .post(frontend::onboarding_receiver); +// app.at("/login") +// .with(frontend::ErrorHandlerMiddleware {}) +// .get(frontend::login::form) +// .post(frontend::login::handler); +// app.at("/login/callback") +// .with(frontend::ErrorHandlerMiddleware {}) +// .get(frontend::login::callback); +// app.at("/static/*path") +// .with(frontend::ErrorHandlerMiddleware {}) +// .get(frontend::handle_static); +// app.at("/*path") +// .with(frontend::ErrorHandlerMiddleware {}) +// .get(frontend::render_post); +// app.at("/coffee") +// .with(frontend::ErrorHandlerMiddleware {}) +// .get(frontend::coffee); +// // TODO make sure the health check actually checks the backend or something +// // otherwise it'll get false-negatives for application faults like resource +// // exhaustion +// app.at("/health").get(|_| async { Ok("OK") }); +// app.at("/metrics").get(metrics::gather); + +// app.with(metrics::InstrumentationMiddleware {}); +// app.with( +// tide::sessions::SessionMiddleware::new( +// tide::sessions::CookieStore::new(), +// app.state().cookie_secret.as_bytes(), +// ) +// .with_cookie_name("kittybox_session") +// .without_save_unchanged(), +// ); +// app +// } /*#[cfg(feature="redis")] pub async fn get_app_with_redis( @@ -103,30 +188,7 @@ pub async fn get_app_with_redis( equip_app(app) }*/ -pub async fn get_app_with_file( - token_endpoint: surf::Url, - authorization_endpoint: surf::Url, - backend_uri: String, - media_endpoint: Option, - cookie_secret: String, - internal_token: Option, -) -> App { - let folder = backend_uri.strip_prefix("file://").unwrap(); - let path = std::path::PathBuf::from(folder); - let app = tide::with_state(ApplicationState { - token_endpoint, - media_endpoint, - authorization_endpoint, - internal_token, - cookie_secret, - storage: database::FileStorage::new(path).await.unwrap(), - http_client: surf::Client::new(), - }); - - equip_app(app) -} - -#[cfg(test)] +/*#[cfg(test)] pub async fn get_app_with_test_file( token_endpoint: surf::Url, ) -> ( @@ -151,7 +213,7 @@ pub async fn get_app_with_test_file( (tempdir, backend, equip_app(app)) } -/*#[cfg(all(redis, test))] +#[cfg(all(redis, test))] pub async fn get_app_with_test_redis( token_endpoint: surf::Url, ) -> ( @@ -176,7 +238,7 @@ pub async fn get_app_with_test_redis( (redis_instance, backend, equip_app(app)) }*/ -#[cfg(test)] +/*#[cfg(test)] #[allow(unused_variables)] mod tests { use super::*; @@ -459,4 +521,4 @@ mod tests { assert_eq!(new_feed["children"][0].as_str().unwrap(), uid); assert_eq!(new_feed["children"][1].as_str().unwrap(), first_uid); } -} +}*/ -- cgit 1.4.1