This commit is contained in:
4cuck committed 2023-07-27 04:14:47 +02:00
1 parent 906bfbb70c
commit a304acc505
36 files changed
+9888

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
use crate::{Result, ServerError};
use axum::response::IntoResponse;
use axum::{extract::State, Json};
use entities::state::Cache;
use hyper::http::HeaderValue;
use std::sync::Arc;
pub async fn instances(
State(ref cache): State<Cache>,
State(ref config): State<Arc<crate::Config>>,
) -> Result<axum::response::Response> {
let mut res = {
let guard = cache.read().map_err(|_| ServerError::MutexFailure)?;
let res = Json(&*guard).into_response();
drop(guard);
res
};
res.headers_mut().insert(
"cache-control",
HeaderValue::from_str(&format!("public, max-age={}", config.max_age)).unwrap(),
);
Ok(res)
}
+112
View File
@@ -0,0 +1,112 @@
use std::{borrow::Cow, net::SocketAddr, sync::Arc};
use axum::{extract::DefaultBodyLimit, http::HeaderValue, response::Html, routing::get, Router};
use entities::state::{scanner::ScannerConfig, Cache};
use hyper::{header, StatusCode};
use sea_orm::DatabaseConnection;
use tera::Tera;
use thiserror::Error;
use tower::ServiceBuilder;
use tower_http::{
cors::CorsLayer, limit::RequestBodyLimitLayer, services::ServeDir,
set_header::SetResponseHeaderLayer, trace::TraceLayer,
};
mod api;
mod website;
#[derive(Debug)]
pub struct Config {
pub site_url: String,
pub max_age: usize,
}
#[derive(Clone, axum::extract::FromRef)]
struct AppState {
db: DatabaseConnection,
config: Arc<Config>,
scanner_config: ScannerConfig,
cache: Cache,
templates: Arc<Tera>,
}
/// Start webserver
pub async fn start(
addr: &SocketAddr,
db: DatabaseConnection,
config: Config,
scanner_config: ScannerConfig,
cache: Cache,
) -> Result<()> {
let config = Arc::new(config);
let tera = Tera::new("server/templates/*")?;
let state = AppState {
config: config.clone(),
db,
cache,
scanner_config,
templates: Arc::new(tera),
};
let router = Router::new()
.nest_service(
"/static",
ServeDir::new("server/static").append_index_html_on_directories(false),
)
.route("/api/v1/instances", get(api::instances))
.route("/about", get(website::about))
.route("/", get(website::instances))
.layer(
ServiceBuilder::new()
.layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(2usize.pow(20) * 2))
.layer(TraceLayer::new_for_http())
.layer(cors_policy(&config.site_url))
.layer(SetResponseHeaderLayer::overriding(
header::CONTENT_SECURITY_POLICY,
"default-src 'self'; child-src 'none'; script-src 'self'; style-src 'self';"
.parse::<HeaderValue>()
.expect("Invalid CSP header value"),
)),
)
.with_state(state.clone());
tracing::debug!("Starting server with config {:?}", *config);
tracing::info!("listening on http://{}", addr);
axum::Server::bind(&addr)
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
.await
.expect("Failed to start webserver");
Ok(())
}
fn cors_policy(_site_url: &str) -> CorsLayer {
CorsLayer::new()
.allow_origin(tower_http::cors::Any)
.allow_methods(tower_http::cors::Any)
.allow_headers(tower_http::cors::Any)
}
type Result<T = Html<String>> = std::result::Result<T, ServerError>;
#[derive(Error, Debug)]
pub enum ServerError {
#[error("Failed to access mutex")]
MutexFailure,
#[error("Failed to perform templating: {0:?}")]
Templating(#[from] tera::Error),
}
impl axum::response::IntoResponse for ServerError {
fn into_response(self) -> axum::response::Response {
use ServerError::*;
let msg = match &self {
MutexFailure | Templating(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Cow::Borrowed("Internal Server Error"),
),
};
if msg.0 == StatusCode::INTERNAL_SERVER_ERROR {
tracing::error!(?self);
}
msg.into_response()
}
}
+63
View File
@@ -0,0 +1,63 @@
use std::sync::Arc;
use crate::Result;
use crate::ServerError;
use axum::response::IntoResponse;
use axum::{extract::State, response::Html};
use entities::state::scanner::ScannerConfig;
use entities::state::Cache;
use hyper::http::HeaderValue;
pub async fn instances(
State(ref cache): State<Cache>,
State(ref template): State<Arc<tera::Tera>>,
State(ref config): State<Arc<crate::Config>>,
) -> Result<axum::response::Response> {
let mut context = tera::Context::new();
let mut res = {
let guard = cache.read().map_err(|_| ServerError::MutexFailure)?;
context.insert("instances", &guard.hosts);
let time = guard.last_update.format("%Y.%m.%d %H:%M").to_string();
context.insert("last_updated", &time);
let res = Html(template.render("instances.html.j2", &context)?).into_response();
drop(guard);
res
};
res.headers_mut().insert(
"cache-control",
HeaderValue::from_str(&format!("public, max-age={}", config.max_age)).unwrap(),
);
Ok(res)
}
pub async fn about(
State(ref template): State<Arc<tera::Tera>>,
State(ref scanner_config): State<ScannerConfig>,
) -> Result<axum::response::Response> {
let mut context = tera::Context::new();
let mut paths = Vec::with_capacity(5);
paths.push(&scanner_config.about_path);
paths.push(&scanner_config.rss_path);
paths.push(&scanner_config.profile_path);
context.insert("checked_paths", &paths);
context.insert(
"uptime_interval_s",
&scanner_config.instance_check_interval.as_secs(),
);
context.insert(
"wiki_interval_s",
&scanner_config.list_fetch_interval.as_secs(),
);
context.insert(
"ping_avg_interval_h",
&scanner_config.ping_range.num_hours(),
);
let mut res = Html(template.render("about.html.j2", &context)?).into_response();
res.headers_mut().insert(
"cache-control",
HeaderValue::from_static("public, max-age=900"),
);
Ok(res)
}