Refactoring
This commit is contained in:
1 parent
a41218e0d9
commit
7e5cfd058e
9 files changed
+206
-132
No files matched your search
Generated
+3
-3
@@ -538,14 +538,14 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.125"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
|
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libshire"
|
name = "libshire"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pantonshire/libshire#70fa5daf6c11229d88687324475c016cb49789f1"
|
source = "git+https://github.com/pantonshire/libshire#62dae931409c14531cf338d73269e415c638dede"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "line-wrap"
|
name = "line-wrap"
|
||||||
|
|||||||
+10
-129
@@ -1,32 +1,17 @@
|
|||||||
mod codeblock;
|
mod codeblock;
|
||||||
mod fs_watcher;
|
mod fs_watcher;
|
||||||
mod handlers;
|
|
||||||
mod html_response;
|
|
||||||
mod post;
|
mod post;
|
||||||
mod posts_store;
|
mod posts_store;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod services;
|
||||||
|
|
||||||
use std::{env, fs, io, path::PathBuf, thread};
|
use std::{env, fs, path::PathBuf, thread};
|
||||||
|
|
||||||
use axum::{
|
use axum::Server;
|
||||||
{routing::{get, get_service}, Router},
|
|
||||||
extract::{Extension, Path},
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
handler::Handler,
|
|
||||||
http::StatusCode
|
|
||||||
};
|
|
||||||
use libshire::convert::infallible_elim;
|
|
||||||
use maud::html;
|
|
||||||
use miette::{IntoDiagnostic, Context};
|
use miette::{IntoDiagnostic, Context};
|
||||||
use tower::{
|
|
||||||
limit::ConcurrencyLimitLayer,
|
|
||||||
ServiceExt,
|
|
||||||
};
|
|
||||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use codeblock::CodeBlockRenderer;
|
use codeblock::CodeBlockRenderer;
|
||||||
use html_response::HtmlResponse;
|
|
||||||
use posts_store::ConcurrentPostsStore;
|
use posts_store::ConcurrentPostsStore;
|
||||||
use render::Renderer;
|
use render::Renderer;
|
||||||
|
|
||||||
@@ -94,20 +79,11 @@ fn main() -> miette::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run(config: Config, posts_store: ConcurrentPostsStore) -> miette::Result<()> {
|
async fn run(config: Config, posts_store: ConcurrentPostsStore) -> miette::Result<()> {
|
||||||
let static_service = get_service(ServeDir::new(&config.static_dir)
|
let service = services::site_service(
|
||||||
.fallback(handle_fallback
|
posts_store,
|
||||||
.into_service()
|
&config.static_dir,
|
||||||
.map_err(infallible_elim::<io::Error>)))
|
config.concurrency_limit
|
||||||
.handle_error(handle_static_io_error);
|
);
|
||||||
|
|
||||||
let router = Router::new()
|
|
||||||
.route("/", get(handle_index))
|
|
||||||
.route("/posts/:post_id", get(handle_post_page))
|
|
||||||
.nest("/static", static_service)
|
|
||||||
.fallback(handle_fallback.into_service())
|
|
||||||
.layer(ConcurrencyLimitLayer::new(config.concurrency_limit))
|
|
||||||
.layer(TraceLayer::new_for_http())
|
|
||||||
.layer(Extension(posts_store));
|
|
||||||
|
|
||||||
let bind_address = &config.bind
|
let bind_address = &config.bind
|
||||||
.parse()
|
.parse()
|
||||||
@@ -116,106 +92,11 @@ async fn run(config: Config, posts_store: ConcurrentPostsStore) -> miette::Resul
|
|||||||
|
|
||||||
info!(address = %bind_address, "Starting server");
|
info!(address = %bind_address, "Starting server");
|
||||||
|
|
||||||
axum::Server::try_bind(bind_address)
|
Server::try_bind(bind_address)
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.wrap_err_with(|| format!("Failed to bind {}", bind_address))?
|
.wrap_err_with(|| format!("Failed to bind {}", bind_address))?
|
||||||
.serve(router.into_make_service())
|
.serve(service.into_make_service())
|
||||||
.await
|
.await
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.wrap_err("Fatal error while running the server")
|
.wrap_err("Fatal error while running the server")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_fallback() -> Error {
|
|
||||||
Error::NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_static_io_error(_err: io::Error) -> Error {
|
|
||||||
Error::Internal
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_index(Extension(posts): Extension<ConcurrentPostsStore>) -> HtmlResponse {
|
|
||||||
HtmlResponse::new()
|
|
||||||
.with_title_static("Placeholder title")
|
|
||||||
.with_crawler_permissive()
|
|
||||||
.with_body(html! {
|
|
||||||
h1 { "Here is my great heading" }
|
|
||||||
p { "Hello world" }
|
|
||||||
ul {
|
|
||||||
@for post in posts.read().await.iter_by_created().rev() {
|
|
||||||
li {
|
|
||||||
a href={ "/posts/" (post.id_str()) } {
|
|
||||||
(post.title())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_post_page(
|
|
||||||
Path(post_id): Path<String>,
|
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>
|
|
||||||
) -> Result<HtmlResponse, Error>
|
|
||||||
{
|
|
||||||
let post = posts.get(&post_id)
|
|
||||||
.await
|
|
||||||
.ok_or(Error::NotFound)?;
|
|
||||||
|
|
||||||
Ok(HtmlResponse::new()
|
|
||||||
.with_crawler_permissive()
|
|
||||||
.with_title_owned(post.title().to_owned())
|
|
||||||
.with_head(html! {
|
|
||||||
link href="/static/style/code.css" rel="stylesheet";
|
|
||||||
})
|
|
||||||
.with_body(html! {
|
|
||||||
h1 { (post.title()) }
|
|
||||||
p { "by " (post.author()) }
|
|
||||||
article {
|
|
||||||
(post.html())
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: store diagnostic information in Error struct which is output to trace
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Error {
|
|
||||||
Internal,
|
|
||||||
NotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
fn status_code(&self) -> StatusCode {
|
|
||||||
match self {
|
|
||||||
Error::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
Error::NotFound => StatusCode::NOT_FOUND,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoResponse for Error {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
let status_code = self.status_code();
|
|
||||||
|
|
||||||
// Create a string buffer containing the full error text, e.g. "404 Not Found".
|
|
||||||
let status_text = {
|
|
||||||
let status_code_str = status_code.as_str();
|
|
||||||
let reason = status_code.canonical_reason();
|
|
||||||
let mut buf = String::with_capacity(
|
|
||||||
status_code_str.len() + reason.map(|reason| reason.len() + 1).unwrap_or(0));
|
|
||||||
buf.push_str(status_code_str);
|
|
||||||
if let Some(reason) = reason {
|
|
||||||
buf.push(' ');
|
|
||||||
buf.push_str(reason);
|
|
||||||
}
|
|
||||||
buf
|
|
||||||
};
|
|
||||||
|
|
||||||
HtmlResponse::new()
|
|
||||||
.with_status(status_code)
|
|
||||||
.with_body(html! {
|
|
||||||
p { (status_text) }
|
|
||||||
})
|
|
||||||
.with_title_owned(status_text)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
use axum::extract::Extension;
|
||||||
|
use maud::html;
|
||||||
|
|
||||||
|
use crate::posts_store::ConcurrentPostsStore;
|
||||||
|
use super::response::HtmlResponse;
|
||||||
|
|
||||||
|
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> HtmlResponse {
|
||||||
|
HtmlResponse::new()
|
||||||
|
.with_title_static("Placeholder title")
|
||||||
|
.with_crawler_permissive()
|
||||||
|
.with_body(html! {
|
||||||
|
h1 { "Here is my great heading" }
|
||||||
|
p { "Hello world" }
|
||||||
|
ul {
|
||||||
|
@for post in posts.read().await.iter_by_created().rev() {
|
||||||
|
li {
|
||||||
|
a href={ "/posts/" (post.id_str()) } {
|
||||||
|
(post.title())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod index;
|
||||||
|
mod posts;
|
||||||
|
mod response;
|
||||||
|
mod site;
|
||||||
|
mod static_content;
|
||||||
|
|
||||||
|
pub use site::service as site_service;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use axum::extract::{Extension, Path};
|
||||||
|
use maud::html;
|
||||||
|
|
||||||
|
use crate::posts_store::ConcurrentPostsStore;
|
||||||
|
use super::response::{ErrorResponse, HtmlResponse};
|
||||||
|
|
||||||
|
pub async fn handle(
|
||||||
|
Path(post_id): Path<String>,
|
||||||
|
Extension(posts): Extension<ConcurrentPostsStore>
|
||||||
|
) -> Result<HtmlResponse, ErrorResponse>
|
||||||
|
{
|
||||||
|
let post = posts.get(&post_id)
|
||||||
|
.await
|
||||||
|
.ok_or(ErrorResponse::PostNotFound)?;
|
||||||
|
|
||||||
|
Ok(HtmlResponse::new()
|
||||||
|
.with_crawler_permissive()
|
||||||
|
.with_title_owned(post.title().to_owned())
|
||||||
|
.with_head(html! {
|
||||||
|
link href="/static/style/code.css" rel="stylesheet";
|
||||||
|
})
|
||||||
|
.with_body(html! {
|
||||||
|
h1 { (post.title()) }
|
||||||
|
p { "by " (post.author()) }
|
||||||
|
article {
|
||||||
|
(post.html())
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -5,6 +5,58 @@ use axum::response::{IntoResponse, Response};
|
|||||||
use axum::http::{self, StatusCode};
|
use axum::http::{self, StatusCode};
|
||||||
use maud::{html, Markup, Render, Escaper, DOCTYPE};
|
use maud::{html, Markup, Render, Escaper, DOCTYPE};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ErrorResponse {
|
||||||
|
Internal,
|
||||||
|
PostNotFound,
|
||||||
|
StaticResourceNotFound,
|
||||||
|
RouteNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorResponse {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
ErrorResponse::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorResponse::PostNotFound => StatusCode::NOT_FOUND,
|
||||||
|
ErrorResponse::StaticResourceNotFound => StatusCode::NOT_FOUND,
|
||||||
|
ErrorResponse::RouteNotFound => StatusCode::NOT_FOUND,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for ErrorResponse {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
let status_code = self.status_code();
|
||||||
|
|
||||||
|
// Create a string buffer containing the full error text, e.g. "404 Not Found".
|
||||||
|
let status_text = {
|
||||||
|
let status_code_str = status_code.as_str();
|
||||||
|
let reason = status_code.canonical_reason();
|
||||||
|
|
||||||
|
// Allocate a buffer with enough capacity to store the full error text.
|
||||||
|
let mut buf = String::with_capacity(
|
||||||
|
status_code_str.len() + reason.map(|reason| reason.len() + 1).unwrap_or(0));
|
||||||
|
|
||||||
|
// Push the numerical code string first, then a space, then the error reason string.
|
||||||
|
buf.push_str(status_code_str);
|
||||||
|
if let Some(reason) = reason {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push_str(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
|
};
|
||||||
|
|
||||||
|
HtmlResponse::new()
|
||||||
|
.with_status(status_code)
|
||||||
|
.with_body(html! {
|
||||||
|
p { (status_text) }
|
||||||
|
})
|
||||||
|
.with_title_owned(status_text)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct HtmlResponse {
|
pub struct HtmlResponse {
|
||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
title: Cow<'static, str>,
|
title: Cow<'static, str>,
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
handler::Handler,
|
||||||
|
http::Uri,
|
||||||
|
extract::Extension,
|
||||||
|
Router,
|
||||||
|
routing::get,
|
||||||
|
};
|
||||||
|
use tower::limit::ConcurrencyLimitLayer;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::posts_store::ConcurrentPostsStore;
|
||||||
|
use super::{
|
||||||
|
index,
|
||||||
|
posts,
|
||||||
|
response::ErrorResponse,
|
||||||
|
static_content,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn service(
|
||||||
|
posts_store: ConcurrentPostsStore,
|
||||||
|
static_dir: &Path,
|
||||||
|
concurrency_limit: usize
|
||||||
|
) -> Router
|
||||||
|
{
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(index::handle))
|
||||||
|
.route("/posts/:post_id", get(posts::handle))
|
||||||
|
.nest("/static", static_content::service(static_dir))
|
||||||
|
.fallback(handle_fallback.into_service())
|
||||||
|
.layer(ConcurrencyLimitLayer::new(concurrency_limit))
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.layer(Extension(posts_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_fallback(uri: Uri) -> ErrorResponse {
|
||||||
|
info!(path = %uri.path(), "Requested resource not found");
|
||||||
|
ErrorResponse::RouteNotFound
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
io,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
handler::Handler,
|
||||||
|
http::Uri,
|
||||||
|
routing::{get_service, MethodRouter},
|
||||||
|
};
|
||||||
|
use libshire::convert::Empty;
|
||||||
|
use tower::ServiceExt;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use tracing::{info, error};
|
||||||
|
|
||||||
|
use super::response::ErrorResponse;
|
||||||
|
|
||||||
|
pub fn service(static_dir: &Path) -> MethodRouter<Body, Infallible> {
|
||||||
|
let fallback_service = handle_fallback
|
||||||
|
.into_service()
|
||||||
|
.map_err(Empty::elim::<io::Error>);
|
||||||
|
|
||||||
|
let serve_dir = ServeDir::new(static_dir)
|
||||||
|
.fallback(fallback_service);
|
||||||
|
|
||||||
|
get_service(serve_dir)
|
||||||
|
.handle_error(handle_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_fallback(uri: Uri) -> ErrorResponse {
|
||||||
|
info!(path = %uri.path(), "Requested static file not found");
|
||||||
|
ErrorResponse::StaticResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_error(uri: Uri, err: io::Error) -> ErrorResponse {
|
||||||
|
error!(path = %uri.path(), err = %err, "IO error");
|
||||||
|
ErrorResponse::Internal
|
||||||
|
}
|
||||||
File renamed without changes.
Reference in new issue
Block a user