Refactoring

main
Pantonshire 4 years ago
parent a41218e0d9
commit 7e5cfd058e

6
Cargo.lock generated

@ -538,14 +538,14 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.125"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "libshire"
version = "0.1.0"
source = "git+https://github.com/pantonshire/libshire#70fa5daf6c11229d88687324475c016cb49789f1"
source = "git+https://github.com/pantonshire/libshire#62dae931409c14531cf338d73269e415c638dede"
[[package]]
name = "line-wrap"

@ -1,32 +1,17 @@
mod codeblock;
mod fs_watcher;
mod handlers;
mod html_response;
mod post;
mod posts_store;
mod render;
mod services;
use std::{env, fs, io, path::PathBuf, thread};
use axum::{
{routing::{get, get_service}, Router},
extract::{Extension, Path},
response::{IntoResponse, Response},
handler::Handler,
http::StatusCode
};
use libshire::convert::infallible_elim;
use maud::html;
use std::{env, fs, path::PathBuf, thread};
use axum::Server;
use miette::{IntoDiagnostic, Context};
use tower::{
limit::ConcurrencyLimitLayer,
ServiceExt,
};
use tower_http::{services::ServeDir, trace::TraceLayer};
use tracing::info;
use codeblock::CodeBlockRenderer;
use html_response::HtmlResponse;
use posts_store::ConcurrentPostsStore;
use render::Renderer;
@ -94,20 +79,11 @@ fn main() -> miette::Result<()> {
}
async fn run(config: Config, posts_store: ConcurrentPostsStore) -> miette::Result<()> {
let static_service = get_service(ServeDir::new(&config.static_dir)
.fallback(handle_fallback
.into_service()
.map_err(infallible_elim::<io::Error>)))
.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 service = services::site_service(
posts_store,
&config.static_dir,
config.concurrency_limit
);
let bind_address = &config.bind
.parse()
@ -116,106 +92,11 @@ async fn run(config: Config, posts_store: ConcurrentPostsStore) -> miette::Resul
info!(address = %bind_address, "Starting server");
axum::Server::try_bind(bind_address)
Server::try_bind(bind_address)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to bind {}", bind_address))?
.serve(router.into_make_service())
.serve(service.into_make_service())
.await
.into_diagnostic()
.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 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 {
status: StatusCode,
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
}
Loading…
Cancel
Save