diff options
| author | Xe Iaso <me@christine.website> | 2022-03-21 20:14:14 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-03-21 20:14:14 -0400 |
| commit | 8b747c1c40876c6668191594eddcb260199cdb7f (patch) | |
| tree | 86edb9bc382751c6a32f5f2946ff235ea06674ba /src | |
| parent | f45ca40ae1052d46611ff2f27ad281695afc4f8f (diff) | |
| download | xesite-8b747c1c40876c6668191594eddcb260199cdb7f.tar.xz xesite-8b747c1c40876c6668191594eddcb260199cdb7f.zip | |
Rewrite the site routing with Axum (#441)
* broken state
Signed-off-by: Xe Iaso <me@christine.website>
* fix???
Signed-off-by: Xe Iaso <me@christine.website>
* Port everything else to axum
Signed-off-by: Xe <me@christine.website>
* headers
Signed-off-by: Xe Iaso <me@christine.website>
* site update post
Signed-off-by: Christine Dodrill <me@christine.website>
* fix headers
Signed-off-by: Xe Iaso <me@christine.website>
* remove warp example
Signed-off-by: Xe Iaso <me@christine.website>
* 80c wrap
Signed-off-by: Xe Iaso <me@christine.website>
* bump version
Signed-off-by: Xe Iaso <me@christine.website>
Diffstat (limited to 'src')
| -rw-r--r-- | src/domainsocket.rs | 94 | ||||
| -rw-r--r-- | src/handlers/blog.rs | 57 | ||||
| -rw-r--r-- | src/handlers/feeds.rs | 95 | ||||
| -rw-r--r-- | src/handlers/gallery.rs | 30 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 169 | ||||
| -rw-r--r-- | src/handlers/talks.rs | 30 | ||||
| -rw-r--r-- | src/main.rs | 407 | ||||
| -rw-r--r-- | src/post/mod.rs | 6 |
8 files changed, 466 insertions, 422 deletions
diff --git a/src/domainsocket.rs b/src/domainsocket.rs new file mode 100644 index 0000000..ef731f5 --- /dev/null +++ b/src/domainsocket.rs @@ -0,0 +1,94 @@ +use axum::extract::connect_info; +use futures::ready; +use hyper::{ + client::connect::{Connected, Connection}, + server::accept::Accept, +}; +use std::{ + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::{unix::UCred, UnixListener, UnixStream}, +}; +use tower::BoxError; + +pub struct ServerAccept { + pub uds: UnixListener, +} + +impl Accept for ServerAccept { + type Conn = UnixStream; + type Error = BoxError; + + fn poll_accept( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<Option<Result<Self::Conn, Self::Error>>> { + let (stream, _addr) = ready!(self.uds.poll_accept(cx))?; + Poll::Ready(Some(Ok(stream))) + } +} + +pub struct ClientConnection { + pub stream: UnixStream, +} + +impl AsyncWrite for ClientConnection { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll<Result<usize, io::Error>> { + Pin::new(&mut self.stream).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> { + Pin::new(&mut self.stream).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<Result<(), io::Error>> { + Pin::new(&mut self.stream).poll_shutdown(cx) + } +} + +impl AsyncRead for ClientConnection { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll<io::Result<()>> { + Pin::new(&mut self.stream).poll_read(cx, buf) + } +} + +impl Connection for ClientConnection { + fn connected(&self) -> Connected { + Connected::new() + } +} + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub struct UdsConnectInfo { + pub peer_addr: Arc<tokio::net::unix::SocketAddr>, + pub peer_cred: UCred, +} + +impl connect_info::Connected<&UnixStream> for UdsConnectInfo { + fn connect_info(target: &UnixStream) -> Self { + let peer_addr = target.peer_addr().unwrap(); + let peer_cred = target.peer_cred().unwrap(); + + Self { + peer_addr: Arc::new(peer_addr), + peer_cred, + } + } +} diff --git a/src/handlers/blog.rs b/src/handlers/blog.rs index 007b8e0..f6aae06 100644 --- a/src/handlers/blog.rs +++ b/src/handlers/blog.rs @@ -1,14 +1,13 @@ -use super::{PostNotFound, SeriesNotFound, LAST_MODIFIED}; -use crate::{ - app::State, - post::Post, - templates::{self, Html, RenderRucte}, +use super::Result; +use crate::{app::State, post::Post, templates}; +use axum::{ + extract::{Extension, Path}, + response::Html, }; use lazy_static::lazy_static; use prometheus::{opts, register_int_counter_vec, IntCounterVec}; use std::sync::Arc; use tracing::{error, instrument}; -use warp::{http::Response, Rejection, Reply}; lazy_static! { static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!( @@ -19,17 +18,18 @@ lazy_static! { } #[instrument(skip(state))] -pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn index(Extension(state): Extension<Arc<State>>) -> Result { let state = state.clone(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::blogindex_html(o, state.blog.clone())) + let mut result: Vec<u8> = vec![]; + templates::blogindex_html(&mut result, state.blog.clone())?; + Ok(Html(result)) } #[instrument(skip(state))] -pub async fn series(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn series(Extension(state): Extension<Arc<State>>) -> Result { let state = state.clone(); let mut series: Vec<String> = vec![]; + let mut result: Vec<u8> = vec![]; for post in &state.blog { if post.front_matter.series.is_some() { @@ -40,15 +40,18 @@ pub async fn series(state: Arc<State>) -> Result<impl Reply, Rejection> { series.sort(); series.dedup(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::series_html(o, series)) + templates::series_html(&mut result, series)?; + Ok(Html(result)) } #[instrument(skip(state))] -pub async fn series_view(series: String, state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn series_view( + Path(series): Path<String>, + Extension(state): Extension<Arc<State>>, +) -> Result { let state = state.clone(); let mut posts: Vec<Post> = vec![]; + let mut result: Vec<u8> = vec![]; for post in &state.blog { if post.front_matter.series.is_none() { @@ -62,16 +65,18 @@ pub async fn series_view(series: String, state: Arc<State>) -> Result<impl Reply if posts.len() == 0 { error!("series not found"); - Err(SeriesNotFound(series).into()) - } else { - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::series_posts_html(o, series, &posts)) + return Err(super::Error::SeriesNotFound(series)); } + + templates::series_posts_html(&mut result, series, &posts).unwrap(); + Ok(Html(result)) } #[instrument(skip(state))] -pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn post_view( + Path(name): Path<String>, + Extension(state): Extension<Arc<State>>, +) -> Result { let mut want: Option<Post> = None; for post in &state.blog { @@ -81,15 +86,15 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re } match want { - None => Err(PostNotFound("blog".into(), name).into()), + None => Err(super::Error::PostNotFound(name)), Some(post) => { HIT_COUNTER .with_label_values(&[name.clone().as_str()]) .inc(); - let body = Html(post.body_html.clone()); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::blogpost_html(o, post, body)) + let body = templates::Html(post.body_html.clone()); + let mut result: Vec<u8> = vec![]; + templates::blogpost_html(&mut result, post, body)?; + Ok(Html(result)) } } } diff --git a/src/handlers/feeds.rs b/src/handlers/feeds.rs index 3d540f6..c69e2f0 100644 --- a/src/handlers/feeds.rs +++ b/src/handlers/feeds.rs @@ -1,10 +1,14 @@ -use super::LAST_MODIFIED; -use crate::{app::State, post::Post, templates}; +use super::{Result, LAST_MODIFIED}; +use crate::{ + app::State, + post::{NewPost, Post}, + templates, +}; +use axum::{body, extract::Extension, response::Response, Json}; use lazy_static::lazy_static; use prometheus::{opts, register_int_counter_vec, IntCounterVec}; -use std::{io, sync::Arc}; +use std::sync::Arc; use tracing::instrument; -use warp::{http::Response, Rejection, Reply}; lazy_static! { pub static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!( @@ -16,103 +20,56 @@ lazy_static! { } #[instrument(skip(state))] -pub async fn jsonfeed(state: Arc<State>, since: Option<String>) -> Result<impl Reply, Rejection> { +pub async fn jsonfeed(Extension(state): Extension<Arc<State>>) -> Json<jsonfeed::Feed> { HIT_COUNTER.with_label_values(&["json"]).inc(); let state = state.clone(); - Ok(warp::reply::json(&state.jf)) + Json(state.jf.clone()) } #[instrument(skip(state))] -pub async fn new_post(state: Arc<State>) -> Result<impl Reply, Rejection> { +#[axum_macros::debug_handler] +pub async fn new_post(Extension(state): Extension<Arc<State>>) -> Result<Json<NewPost>> { let state = state.clone(); - let everything = state.everything.clone(); - let p: &Post = everything.iter().next().unwrap(); - Ok(warp::reply::json(&p.new_post)) + let p: Post = state.everything.iter().next().unwrap().clone(); + Ok(Json(p.new_post)) } -#[derive(Debug)] -pub enum RenderError { - Build(warp::http::Error), - IO(io::Error), -} - -impl warp::reject::Reject for RenderError {} - #[instrument(skip(state))] -pub async fn atom(state: Arc<State>, since: Option<String>) -> Result<impl Reply, Rejection> { - if let Some(etag) = since { - if etag == ETAG.clone() { - return Response::builder() - .status(304) - .header("Content-Type", "text/plain") - .body( - "You already have the newest version of this feed." - .to_string() - .into_bytes(), - ) - .map_err(RenderError::Build) - .map_err(warp::reject::custom); - } - } - +pub async fn atom(Extension(state): Extension<Arc<State>>) -> Result<Response> { HIT_COUNTER.with_label_values(&["atom"]).inc(); let state = state.clone(); let mut buf = Vec::new(); - templates::blog_atom_xml(&mut buf, state.everything.clone()) - .map_err(RenderError::IO) - .map_err(warp::reject::custom)?; - Response::builder() + templates::blog_atom_xml(&mut buf, state.everything.clone())?; + Ok(Response::builder() .status(200) .header("Content-Type", "application/atom+xml") .header("ETag", ETAG.clone()) .header("Last-Modified", &*LAST_MODIFIED) - .body(buf) - .map_err(RenderError::Build) - .map_err(warp::reject::custom) + .body(body::boxed(body::Full::from(buf)))?) } #[instrument(skip(state))] -pub async fn rss(state: Arc<State>, since: Option<String>) -> Result<impl Reply, Rejection> { - if let Some(etag) = since { - if etag == ETAG.clone() { - return Response::builder() - .status(304) - .header("Content-Type", "text/plain") - .body( - "You already have the newest version of this feed." - .to_string() - .into_bytes(), - ) - .map_err(RenderError::Build) - .map_err(warp::reject::custom); - } - } - +pub async fn rss(Extension(state): Extension<Arc<State>>) -> Result<Response> { HIT_COUNTER.with_label_values(&["rss"]).inc(); let state = state.clone(); let mut buf = Vec::new(); - templates::blog_rss_xml(&mut buf, state.everything.clone()) - .map_err(RenderError::IO) - .map_err(warp::reject::custom)?; - Response::builder() + templates::blog_rss_xml(&mut buf, state.everything.clone())?; + Ok(Response::builder() .status(200) .header("Content-Type", "application/rss+xml") .header("ETag", ETAG.clone()) .header("Last-Modified", &*LAST_MODIFIED) - .body(buf) - .map_err(RenderError::Build) - .map_err(warp::reject::custom) + .body(body::boxed(body::Full::from(buf)))?) } #[instrument(skip(state))] -pub async fn sitemap(state: Arc<State>) -> Result<impl Reply, Rejection> { +#[axum_macros::debug_handler] +pub async fn sitemap(Extension(state): Extension<Arc<State>>) -> Result<Response> { HIT_COUNTER.with_label_values(&["sitemap"]).inc(); let state = state.clone(); - Response::builder() + Ok(Response::builder() .status(200) .header("Content-Type", "application/xml") .header("Last-Modified", &*LAST_MODIFIED) - .body(state.sitemap.clone()) - .map_err(RenderError::Build) - .map_err(warp::reject::custom) + .body(body::boxed(body::Full::from(state.sitemap.clone())))?) } diff --git a/src/handlers/gallery.rs b/src/handlers/gallery.rs index 02bc01b..ae6c411 100644 --- a/src/handlers/gallery.rs +++ b/src/handlers/gallery.rs @@ -1,14 +1,13 @@ -use super::PostNotFound; -use crate::{ - app::State, - post::Post, - templates::{self, Html, RenderRucte}, +use super::{Error::*, Result}; +use crate::{app::State, post::Post, templates}; +use axum::{ + extract::{Extension, Path}, + response::Html, }; use lazy_static::lazy_static; use prometheus::{opts, register_int_counter_vec, IntCounterVec}; use std::sync::Arc; use tracing::instrument; -use warp::{http::Response, Rejection, Reply}; lazy_static! { static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!( @@ -19,13 +18,18 @@ lazy_static! { } #[instrument(skip(state))] -pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn index(Extension(state): Extension<Arc<State>>) -> Result { let state = state.clone(); - Response::builder().html(|o| templates::galleryindex_html(o, state.gallery.clone())) + let mut result: Vec<u8> = vec![]; + templates::galleryindex_html(&mut result, state.gallery.clone())?; + Ok(Html(result)) } #[instrument(skip(state))] -pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn post_view( + Path(name): Path<String>, + Extension(state): Extension<Arc<State>>, +) -> Result { let mut want: Option<Post> = None; for post in &state.gallery { @@ -35,13 +39,15 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re } match want { - None => Err(PostNotFound("gallery".into(), name).into()), + None => Err(PostNotFound(name)), Some(post) => { HIT_COUNTER .with_label_values(&[name.clone().as_str()]) .inc(); - let body = Html(post.body_html.clone()); - Response::builder().html(|o| templates::gallerypost_html(o, post, body)) + let body = templates::Html(post.body_html.clone()); + let mut result: Vec<u8> = vec![]; + templates::gallerypost_html(&mut result, post, body)?; + Ok(Html(result)) } } } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 798a911..07c4d52 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,16 +1,20 @@ -use crate::{ - app::State, - templates::{self, Html, RenderRucte}, +use crate::{app::State, templates}; +use axum::{ + body, + extract::Extension, + http::StatusCode, + response::{Html, IntoResponse, Response}, }; use chrono::{Datelike, Timelike, Utc}; use lazy_static::lazy_static; use prometheus::{opts, register_int_counter_vec, IntCounterVec}; -use std::{convert::Infallible, fmt, sync::Arc}; +use std::sync::Arc; use tracing::instrument; -use warp::{ - http::{Response, StatusCode}, - Rejection, Reply, -}; + +pub mod blog; +pub mod feeds; +pub mod gallery; +pub mod talks; lazy_static! { static ref HIT_COUNTER: IntCounterVec = @@ -32,139 +36,104 @@ lazy_static! { } #[instrument] -pub async fn index() -> Result<impl Reply, Rejection> { +pub async fn index() -> Result { HIT_COUNTER.with_label_values(&["index"]).inc(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::index_html(o)) + let mut result: Vec<u8> = vec![]; + templates::index_html(&mut result)?; + Ok(Html(result)) } #[instrument] -pub async fn contact() -> Result<impl Reply, Rejection> { +pub async fn contact() -> Result { HIT_COUNTER.with_label_values(&["contact"]).inc(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::contact_html(o)) + let mut result: Vec<u8> = vec![]; + templates::contact_html(&mut result)?; + Ok(Html(result)) } #[instrument] -pub async fn feeds() -> Result<impl Reply, Rejection> { +pub async fn feeds() -> Result { HIT_COUNTER.with_label_values(&["feeds"]).inc(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::feeds_html(o)) + let mut result: Vec<u8> = vec![]; + templates::feeds_html(&mut result)?; + Ok(Html(result)) } +#[axum_macros::debug_handler] #[instrument(skip(state))] -pub async fn resume(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn resume(Extension(state): Extension<Arc<State>>) -> Result { HIT_COUNTER.with_label_values(&["resume"]).inc(); let state = state.clone(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::resume_html(o, Html(state.resume.clone()))) + let mut result: Vec<u8> = vec![]; + templates::resume_html(&mut result, templates::Html(state.resume.clone()))?; + Ok(Html(result)) } #[instrument(skip(state))] -pub async fn patrons(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn patrons(Extension(state): Extension<Arc<State>>) -> Result { HIT_COUNTER.with_label_values(&["patrons"]).inc(); let state = state.clone(); + let mut result: Vec<u8> = vec![]; match &state.patrons { - None => Response::builder().status(500).html(|o| { - templates::error_html( - o, - "Could not load patrons, let me know the API token expired again".to_string(), - ) - }), - Some(patrons) => Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::patrons_html(o, patrons.clone())), + None => Err(Error::NoPatrons), + Some(patrons) => { + templates::patrons_html(&mut result, patrons.clone())?; + Ok(Html(result)) + } } } +#[axum_macros::debug_handler] #[instrument(skip(state))] -pub async fn signalboost(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn signalboost(Extension(state): Extension<Arc<State>>) -> Result { HIT_COUNTER.with_label_values(&["signalboost"]).inc(); let state = state.clone(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::signalboost_html(o, state.signalboost.clone())) + let mut result: Vec<u8> = vec![]; + templates::signalboost_html(&mut result, state.signalboost.clone())?; + Ok(Html(result)) } #[instrument] -pub async fn not_found() -> Result<impl Reply, Rejection> { +pub async fn not_found() -> Result { HIT_COUNTER.with_label_values(&["not_found"]).inc(); - Response::builder() - .header("Last-Modified", &*LAST_MODIFIED) - .html(|o| templates::notfound_html(o, "some path".into())) + let mut result: Vec<u8> = vec![]; + templates::notfound_html(&mut result, "some path".into())?; + Ok(Html(result)) } -pub mod blog; -pub mod feeds; -pub mod gallery; -pub mod talks; - #[derive(Debug, thiserror::Error)] -struct PostNotFound(String, String); +pub enum Error { + #[error("series not found: {0}")] + SeriesNotFound(String), -impl fmt::Display for PostNotFound { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "not found: {}/{}", self.0, self.1) - } -} + #[error("post not found: {0}")] + PostNotFound(String), -impl warp::reject::Reject for PostNotFound {} + #[error("patreon key not working, poke me to get this fixed")] + NoPatrons, -#[derive(Debug, thiserror::Error)] -struct SeriesNotFound(String); + #[error("io error: {0}")] + IO(#[from] std::io::Error), -impl fmt::Display for SeriesNotFound { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } + #[error("axum http error: {0}")] + AxumHTTP(#[from] axum::http::Error), } -impl warp::reject::Reject for SeriesNotFound {} +pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>; -lazy_static! { - static ref REJECTION_COUNTER: IntCounterVec = register_int_counter_vec!( - opts!("rejections", "Number of rejections by kind"), - &["kind"] - ) - .unwrap(); -} +impl IntoResponse for Error { + fn into_response(self) -> Response { + let mut result: Vec<u8> = vec![]; + templates::error_html(&mut result, format!("{}", self)).unwrap(); -#[instrument] -pub async fn rejection(err: Rejection) -> Result<impl Reply, Infallible> { - let path: String; - let code; - - if err.is_not_found() { - REJECTION_COUNTER.with_label_values(&["404"]).inc(); - path = "".into(); - code = StatusCode::NOT_FOUND; - } else if let Some(SeriesNotFound(series)) = err.find() { - REJECTION_COUNTER - .with_label_values(&["SeriesNotFound"]) - .inc(); - log::error!("invalid series {}", series); - path = format!("/blog/series/{}", series); - code = StatusCode::NOT_FOUND; - } else if let Some(PostNotFound(kind, name)) = err.find() { - REJECTION_COUNTER.with_label_values(&["PostNotFound"]).inc(); - log::error!("unknown post {}/{}", kind, name); - path = format!("/{}/{}", kind, name); - code = StatusCode::NOT_FOUND; - } else { - REJECTION_COUNTER.with_label_values(&["Other"]).inc(); - log::error!("unhandled rejection: {:?}", err); - path = format!("weird rejection: {:?}", err); - code = StatusCode::INTERNAL_SERVER_ERROR; - } + let body = body::boxed(body::Full::from(result)); - Ok(warp::reply::with_status( Response::builder() - .html(|o| templates::notfound_html(o, path)) - .unwrap(), - code, - )) + .status(match self { + Error::SeriesNotFound(_) | Error::PostNotFound(_) => StatusCode::NOT_FOUND, + _ => StatusCode::INTERNAL_SERVER_ERROR, + }) + .body(body) + .unwrap() + } } diff --git a/src/handlers/talks.rs b/src/handlers/talks.rs index 8db5c9e..22ee3cf 100644 --- a/src/handlers/talks.rs +++ b/src/handlers/talks.rs @@ -1,14 +1,13 @@ -use super::PostNotFound; -use crate::{ - app::State, - post::Post, - templates::{self, Html, RenderRucte}, +use super::{Error::*, Result}; +use crate::{app::State, post::Post, templates}; +use axum::{ + extract::{Extension, Path}, + response::Html, }; use lazy_static::lazy_static; use prometheus::{opts, register_int_counter_vec, IntCounterVec}; use std::sync::Arc; use tracing::instrument; -use warp::{http::Response, Rejection, Reply}; lazy_static! { static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!( @@ -19,13 +18,18 @@ lazy_static! { } #[instrument(skip(state))] -pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn index(Extension(state): Extension<Arc<State>>) -> Result { let state = state.clone(); - Response::builder().html(|o| templates::talkindex_html(o, state.talks.clone())) + let mut result: Vec<u8> = vec![]; + templates::talkindex_html(&mut result, state.talks.clone())?; + Ok(Html(result)) } #[instrument(skip(state))] -pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> { +pub async fn post_view( + Path(name): Path<String>, + Extension(state): Extension<Arc<State>>, +) -> Result { let mut want: Option<Post> = None; for post in &state.talks { @@ -35,13 +39,15 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re } match want { - None => Err(PostNotFound("talks".into(), name).into()), + None => Err(PostNotFound(name).into()), Some(post) => { HIT_COUNTER .with_label_values(&[name.clone().as_str()]) .inc(); - let body = Html(post.body_html.clone()); - Response::builder().html(|o| templates::talkpost_html(o, post, body)) + let body = templates::Html(post.body_html.clone()); + let mut result: Vec<u8> = vec![]; + templates::talkpost_html(&mut result, post, body)?; + Ok(Html(result)) } } } diff --git a/src/main.rs b/src/main.rs index bbe5658..c6dc33f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,64 @@ #[macro_use] extern crate tracing; +use axum::{ + body, + extract::Extension, + http::header::{self, HeaderValue, CACHE_CONTROL, CONTENT_TYPE}, + response::{Html, Response}, + routing::get, + Router, +}; use color_eyre::eyre::Result; -use hyper::{header::CONTENT_TYPE, Body, Response}; +use hyper::StatusCode; use prometheus::{Encoder, TextEncoder}; -use std::net::IpAddr; -use std::str::FromStr; -use std::sync::Arc; +use std::{ + env, + net::{IpAddr, SocketAddr}, + str::FromStr, + sync::Arc, +}; use tokio::net::UnixListener; -use tokio_stream::wrappers::UnixListenerStream; -use warp::{path, Filter}; +use tower_http::{ + services::{ServeDir, ServeFile}, + set_header::SetResponseHeaderLayer, + trace::TraceLayer, +}; pub mod app; pub mod handlers; pub mod post; pub mod signalboost; -use app::State; +mod domainsocket; +use domainsocket::*; const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); -fn with_state( - state: Arc<State>, -) -> impl Filter<Extract = (Arc<State>,), Error = std::convert::Infallible> + Clone { - warp::any().map(move || state.clone()) +async fn healthcheck() -> &'static str { + "OK" +} + +fn cache_header(_: &Response) -> Option<header::HeaderValue> { + Some(header::HeaderValue::from_static( + "public, max-age=3600, stale-if-error=60", + )) +} + +fn webmention_header(_: &Response) -> Option<HeaderValue> { + Some(header::HeaderValue::from_static( + r#"<https://mi.within.website/api/webmention/accept>; rel="webmention""#, + )) +} + +fn clacks_header(_: &Response) -> Option<HeaderValue> { + Some(HeaderValue::from_static("Ashlynn")) +} + +fn hacker_header(_: &Response) -> Option<HeaderValue> { + Some(header::HeaderValue::from_static( + "If you are reading this, check out /signalboost to find people for your team", + )) } #[tokio::main] @@ -35,7 +70,7 @@ async fn main() -> Result<()> { let state = Arc::new( app::init( - std::env::var("CONFIG_FNAME") + env::var("CONFIG_FNAME") .unwrap_or("./config.dhall".into()) .as_str() .into(), @@ -43,178 +78,129 @@ async fn main() -> Result<()> { .await?, ); - let healthcheck = warp::get().and(warp::path(".within").and(warp::path("health")).map(|| "OK")); - let new_post = warp::path!(".within" / "website.within.xesite" / "new_post") - .and(with_state(state.clone())) - .and_then(handlers::feeds::new_post); - - let base = warp::path!("blog" / ..); - let blog_index = base - .and(warp::path::end()) - .and(with_state(state.clone())) - .and_then(handlers::blog::index); - let series = base - .and(warp::path!("series").and(with_state(state.clone()).and_then(handlers::blog::series))); - let series_view = base.and( - warp::path!("series" / String) - .and(with_state(state.clone())) - .and(warp::get()) - .and_then(handlers::blog::series_view), - ); - let post_view = base.and( - warp::path!(String) - .and(with_state(state.clone())) - .and(warp::get()) - .and_then(handlers::blog::post_view), - ); - - let gallery_base = warp::path!("gallery" / ..); - let gallery_index = gallery_base - .and(warp::path::end()) - .and(with_state(state.clone())) - .and_then(handlers::gallery::index); - let gallery_post_view = gallery_base.and( - warp::path!(String) - .and(with_state(state.clone())) - .and(warp::get()) - .and_then(handlers::gallery::post_view), - ); - - let talk_base = warp::path!("talks" / ..); - let talk_index = talk_base - .and(warp::path::end()) - .and(with_state(state.clone())) - .and_then(handlers::talks::index); - let talk_post_view = talk_base.and( - warp::path!(String) - .and(with_state(state.clone())) - .and(warp::get()) - .and_then(handlers::talks::post_view), - ); - - let index = warp::get().and(path::end().and_then(handlers::index)); - let contact = warp::path!("contact").and_then(handlers::contact); - let feeds = warp::path!("feeds").and_then(handlers::feeds); - let resume = warp::path!("resume") - .and(with_state(state.clone())) - .and_then(handlers::resume); - let signalboost = warp::path!("signalboost") - .and(with_state(state.clone())) - .and_then(handlers::signalboost); - let patrons = warp::path!("patrons") - .and(with_state(state.clone())) - .and_then(handlers::patrons); + let middleware = tower::ServiceBuilder::new() + .layer(TraceLayer::new_for_http()) + .layer(Extension(state.clone())) + .layer(SetResponseHeaderLayer::overriding( + header::CACHE_CONTROL, + cache_header, + )) + .layer(SetResponseHeaderLayer::appending( + header::LINK, + webmention_header, + )) + .layer(SetResponseHeaderLayer::appending( + header::HeaderName::from_static("x-clacks-overhead"), + clacks_header, + )) + .layer(SetResponseHeaderLayer::overriding( + header::HeaderName::from_static("x-hacker"), + hacker_header, + )); - let files = warp::path("static") - .and(warp::fs::dir("./static")) - .map(|reply| { - warp::reply::with_header( - reply, - "Cache-Control", - "public, max-age=86400, stale-if-error=60", - ) - }); - - let css = warp::path("css").and(warp::fs::dir("./css")).map(|reply| { - warp::reply::with_header( - reply, - "Cache-Control", - "public, max-age=86400, stale-if-error=60", + let app = Router::new() + // meta + .route("/.within/health", get(healthcheck)) + .route( + "/.within/website.within.xesite/new_post", + get(handlers::feeds::new_post), ) - }); - - let sw = warp::path("sw.js").and(warp::fs::file("./static/js/sw.js")); - let robots = warp::path("robots.txt").and(warp::fs::file("./static/robots.txt")); - let favicon = warp::path("favicon.ico").and(warp::fs::file("./static/favicon/favicon.ico")); - - let jsonfeed = warp::path("blog.json") - .and(with_state(state.clone())) - .and(warp::header::optional("if-none-match")) - .and_then(handlers::feeds::jsonfeed); - let atom = warp::path("blog.atom") - .and(with_state(state.clone())) - .and(warp::header::optional("if-none-match")) - .and_then(handlers::feeds::atom); - let rss = warp::path("blog.rss") - .and(with_state(state.clone())) - .and(warp::header::optional("if-none-match |
