global: snapshot

This commit is contained in:
nym21
2026-01-16 15:17:42 +01:00
parent f39681bb2b
commit 3b00a92fa4
23 changed files with 4904 additions and 845 deletions

View File

@@ -33,7 +33,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
tower-http = { version = "0.6.8", features = ["compression-full", "trace"] }
tower-http = { version = "0.6.8", features = ["catch-panic", "compression-full", "cors", "normalize-path", "timeout", "trace"] }
[build-dependencies]
# importmap = { path = "../../../importmap" }

View File

@@ -14,7 +14,8 @@ fn main() {
let map = if is_dev {
importmap::ImportMap::empty()
} else {
importmap::ImportMap::scan(&website_path, "").unwrap_or_else(|_| importmap::ImportMap::empty())
importmap::ImportMap::scan(&website_path, "")
.unwrap_or_else(|_| importmap::ImportMap::empty())
};
let _ = map.update_html_file(&website_path.join("index.html"));

View File

@@ -1,10 +1,5 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
extract::State,
http::HeaderMap,
response::Redirect,
routing::get,
};
use axum::{extract::State, http::HeaderMap, response::Redirect, routing::get};
use brk_types::{MempoolBlock, MempoolInfo, RecommendedFees, Txid};
use crate::{CacheStrategy, extended::TransformResponseExtended};

View File

@@ -6,6 +6,7 @@ use axum::{
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Response},
};
use brk_error::Result;
use brk_types::{Format, MetricSelection, Output};
use quick_cache::sync::GuardResult;
@@ -24,12 +25,7 @@ pub async fn handler(
) -> Response {
match req_to_response_res(uri, headers, query, state).await {
Ok(response) => response,
Err(error) => {
let mut response =
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
response.headers_mut().insert_cors();
response
}
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
}
}
@@ -38,19 +34,16 @@ async fn req_to_response_res(
headers: HeaderMap,
Query(params): Query<MetricSelection>,
AppState { query, cache, .. }: AppState,
) -> brk_error::Result<Response> {
) -> Result<Response> {
// Phase 1: Search and resolve metadata (cheap)
let resolved = query
.run(move |q| q.resolve(params, MAX_WEIGHT))
.await?;
let resolved = query.run(move |q| q.resolve(params, MAX_WEIGHT)).await?;
let format = resolved.format();
let etag = resolved.etag();
// Check if client has fresh cache
if headers.has_etag(etag.as_str()) {
let mut response = (StatusCode::NOT_MODIFIED, "").into_response();
response.headers_mut().insert_cors();
let response = (StatusCode::NOT_MODIFIED, "").into_response();
return Ok(response);
}
@@ -81,7 +74,6 @@ async fn req_to_response_res(
};
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_etag(etag.as_str());
headers.insert_cache_control(CACHE_CONTROL);

View File

@@ -6,6 +6,7 @@ use axum::{
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Response},
};
use brk_error::Result;
use brk_types::{Format, MetricSelection, Output};
use quick_cache::sync::GuardResult;
@@ -24,12 +25,7 @@ pub async fn handler(
) -> Response {
match req_to_response_res(uri, headers, query, state).await {
Ok(response) => response,
Err(error) => {
let mut response =
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
response.headers_mut().insert_cors();
response
}
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
}
}
@@ -38,19 +34,16 @@ async fn req_to_response_res(
headers: HeaderMap,
Query(params): Query<MetricSelection>,
AppState { query, cache, .. }: AppState,
) -> brk_error::Result<Response> {
) -> Result<Response> {
// Phase 1: Search and resolve metadata (cheap)
let resolved = query
.run(move |q| q.resolve(params, MAX_WEIGHT))
.await?;
let resolved = query.run(move |q| q.resolve(params, MAX_WEIGHT)).await?;
let format = resolved.format();
let etag = resolved.etag();
// Check if client has fresh cache
if headers.has_etag(etag.as_str()) {
let mut response = (StatusCode::NOT_MODIFIED, "").into_response();
response.headers_mut().insert_cors();
let response = (StatusCode::NOT_MODIFIED, "").into_response();
return Ok(response);
}
@@ -81,7 +74,6 @@ async fn req_to_response_res(
};
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_etag(etag.as_str());
headers.insert_cache_control(CACHE_CONTROL);

View File

@@ -6,6 +6,7 @@ use axum::{
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Response},
};
use brk_error::Result;
use brk_types::{Format, MetricSelection, OutputLegacy};
use quick_cache::sync::GuardResult;
@@ -24,12 +25,7 @@ pub async fn handler(
) -> Response {
match req_to_response_res(uri, headers, query, state).await {
Ok(response) => response,
Err(error) => {
let mut response =
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
response.headers_mut().insert_cors();
response
}
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
}
}
@@ -38,19 +34,16 @@ async fn req_to_response_res(
headers: HeaderMap,
Query(params): Query<MetricSelection>,
AppState { query, cache, .. }: AppState,
) -> brk_error::Result<Response> {
) -> Result<Response> {
// Phase 1: Search and resolve metadata (cheap)
let resolved = query
.run(move |q| q.resolve(params, MAX_WEIGHT))
.await?;
let resolved = query.run(move |q| q.resolve(params, MAX_WEIGHT)).await?;
let format = resolved.format();
let etag = resolved.etag();
// Check if client has fresh cache
if headers.has_etag(etag.as_str()) {
let mut response = (StatusCode::NOT_MODIFIED, "").into_response();
response.headers_mut().insert_cors();
let response = (StatusCode::NOT_MODIFIED, "").into_response();
return Ok(response);
}
@@ -62,9 +55,7 @@ async fn req_to_response_res(
Response::new(Body::from(v))
} else {
// Phase 2: Format (expensive, only on cache miss)
let metric_output = query
.run(move |q| q.format_legacy(resolved))
.await?;
let metric_output = query.run(move |q| q.format_legacy(resolved)).await?;
match metric_output.output {
OutputLegacy::CSV(s) => {
@@ -84,7 +75,6 @@ async fn req_to_response_res(
};
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_etag(etag.as_str());
headers.insert_cache_control(CACHE_CONTROL);

View File

@@ -6,9 +6,9 @@ use axum::{
routing::get,
};
use brk_types::{
BlockCountParam, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights,
DifficultyAdjustment, DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo,
PoolSlugParam, PoolsSummary, RewardStats, TimePeriodParam,
BlockCountParam, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights, DifficultyAdjustment,
DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo, PoolSlugParam, PoolsSummary,
RewardStats, TimePeriodParam,
};
use crate::{CacheStrategy, extended::TransformResponseExtended};

View File

@@ -377,7 +377,10 @@ mod tests {
let props = &parsed["components"]["schemas"]["AddressStats"]["properties"];
assert_eq!(props["address"], "Address", "address should be simplified");
assert_eq!(props["chain_stats"], "AddressChainStats", "chain_stats should be simplified");
assert_eq!(
props["chain_stats"], "AddressChainStats",
"chain_stats should be simplified"
);
}
#[test]

View File

@@ -19,8 +19,6 @@ pub enum ModifiedState {
}
pub trait HeaderMapExtended {
fn insert_cors(&mut self);
fn has_etag(&self, etag: &str) -> bool;
fn get_if_modified_since(&self) -> Option<DateTime>;
@@ -52,11 +50,6 @@ pub trait HeaderMapExtended {
}
impl HeaderMapExtended for HeaderMap {
fn insert_cors(&mut self) {
self.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
self.insert(header::ACCESS_CONTROL_ALLOW_HEADERS, "*".parse().unwrap());
}
fn insert_cache_control(&mut self, value: &str) {
self.insert(header::CACHE_CONTROL, value.parse().unwrap());
}

View File

@@ -36,8 +36,7 @@ where
impl ResponseExtended for Response<Body> {
fn new_not_modified() -> Response<Body> {
let mut response = (StatusCode::NOT_MODIFIED, "").into_response();
let headers = response.headers_mut();
headers.insert_cors();
let _headers = response.headers_mut();
response
}
@@ -56,7 +55,6 @@ impl ResponseExtended for Response<Body> {
let mut response = Response::builder().body(bytes.into()).unwrap();
*response.status_mut() = status;
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type_application_json();
headers.insert_cache_control_must_revalidate();
headers.insert_etag(etag);
@@ -68,12 +66,9 @@ impl ResponseExtended for Response<Body> {
}
fn new_text_with(status: StatusCode, value: &str, etag: &str) -> Self {
let mut response = Response::builder()
.body(value.to_string().into())
.unwrap();
let mut response = Response::builder().body(value.to_string().into()).unwrap();
*response.status_mut() = status;
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type_text_plain();
headers.insert_cache_control_must_revalidate();
headers.insert_etag(etag);
@@ -88,7 +83,6 @@ impl ResponseExtended for Response<Body> {
let mut response = Response::builder().body(value.into()).unwrap();
*response.status_mut() = status;
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type_octet_stream();
headers.insert_cache_control_must_revalidate();
headers.insert_etag(etag);
@@ -102,7 +96,6 @@ impl ResponseExtended for Response<Body> {
let bytes = serde_json::to_vec(&value).unwrap();
let mut response = Response::builder().body(bytes.into()).unwrap();
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type_application_json();
headers.insert_cache_control(&params.cache_control);
if let Some(etag) = &params.etag {
@@ -123,11 +116,8 @@ impl ResponseExtended for Response<Body> {
}
fn new_text_cached(value: &str, params: &CacheParams) -> Self {
let mut response = Response::builder()
.body(value.to_string().into())
.unwrap();
let mut response = Response::builder().body(value.to_string().into()).unwrap();
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type_text_plain();
headers.insert_cache_control(&params.cache_control);
if let Some(etag) = &params.etag {
@@ -139,7 +129,6 @@ impl ResponseExtended for Response<Body> {
fn new_bytes_cached(value: Vec<u8>, params: &CacheParams) -> Self {
let mut response = Response::builder().body(value.into()).unwrap();
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type_octet_stream();
headers.insert_cache_control(&params.cache_control);
if let Some(etag) = &params.etag {

View File

@@ -81,7 +81,6 @@ fn build_response(state: &AppState, path: &Path, content: Vec<u8>, cache_key: &s
};
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_content_type(path);
if cfg!(debug_assertions) || must_revalidate {
@@ -114,9 +113,8 @@ fn embedded_handler(state: &AppState, path: Option<String>) -> Response {
});
let Some(file) = file else {
let mut response: Response<Body> =
let response: Response<Body> =
(StatusCode::NOT_FOUND, "File not found".to_string()).into_response();
response.headers_mut().insert_cors();
return response;
};
@@ -147,9 +145,8 @@ fn filesystem_handler(
let allowed = canonical.starts_with(&canonical_base)
|| project_root.is_some_and(|root| canonical.starts_with(root));
if !allowed {
let mut response: Response<Body> =
let response: Response<Body> =
(StatusCode::FORBIDDEN, "Access denied".to_string()).into_response();
response.headers_mut().insert_cors();
return response;
}
}
@@ -165,12 +162,11 @@ fn filesystem_handler(
// SPA fallback
if !path.exists() || path.is_dir() {
if path.extension().is_some() {
let mut response: Response<Body> = (
let response: Response<Body> = (
StatusCode::INTERNAL_SERVER_ERROR,
"File doesn't exist".to_string(),
)
.into_response();
response.headers_mut().insert_cors();
return response;
} else {
path = files_path.join("index.html");
@@ -188,12 +184,7 @@ fn filesystem_handler(
fn path_to_response(headers: &HeaderMap, state: &AppState, path: &Path) -> Response {
match path_to_response_(headers, state, path) {
Ok(response) => response,
Err(error) => {
let mut response =
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
response.headers_mut().insert_cors();
response
}
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
}
}

View File

@@ -1,6 +1,11 @@
#![doc = include_str!("../README.md")]
use std::{panic, path::PathBuf, sync::Arc, time::{Duration, Instant}};
use std::{
panic,
path::PathBuf,
sync::Arc,
time::{Duration, Instant},
};
use aide::axum::ApiRouter;
use axum::{
@@ -14,10 +19,14 @@ use axum::{
};
use brk_error::Result;
use brk_query::AsyncQuery;
use include_dir::{include_dir, Dir};
use include_dir::{Dir, include_dir};
use quick_cache::sync::Cache;
use tokio::net::TcpListener;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use tower_http::{
catch_panic::CatchPanicLayer, classify::ServerErrorsFailureClass,
compression::CompressionLayer, cors::CorsLayer, normalize_path::NormalizePathLayer,
timeout::TimeoutLayer, trace::TraceLayer,
};
use tracing::{error, info};
/// Embedded website assets
@@ -86,19 +95,25 @@ impl Server {
let trace_layer = TraceLayer::new_for_http()
.on_request(())
.on_response(|response: &Response<Body>, latency: Duration, _: &tracing::Span| {
let status = response.status().as_u16();
let uri = response.extensions().get::<Uri>().unwrap();
match response.status() {
StatusCode::OK => info!(status, %uri, ?latency),
StatusCode::NOT_MODIFIED
| StatusCode::TEMPORARY_REDIRECT
| StatusCode::PERMANENT_REDIRECT => info!(status, %uri, ?latency),
_ => error!(status, %uri, ?latency),
}
})
.on_response(
|response: &Response<Body>, latency: Duration, _: &tracing::Span| {
let status = response.status().as_u16();
let uri = response.extensions().get::<Uri>().unwrap();
match response.status() {
StatusCode::OK => info!(status, %uri, ?latency),
StatusCode::NOT_MODIFIED
| StatusCode::TEMPORARY_REDIRECT
| StatusCode::PERMANENT_REDIRECT => info!(status, %uri, ?latency),
_ => error!(status, %uri, ?latency),
}
},
)
.on_body_chunk(())
.on_failure(())
.on_failure(
|error: ServerErrorsFailureClass, latency: Duration, _: &tracing::Span| {
error!(?error, ?latency, "request failed");
},
)
.on_eos(());
let vecs = state.query.inner().vecs();
@@ -126,9 +141,13 @@ impl Server {
)
.route("/nostr", get(Redirect::temporary("https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44")))
.with_state(state)
.layer(CatchPanicLayer::new())
.layer(compression_layer)
.layer(response_uri_layer)
.layer(trace_layer);
.layer(trace_layer)
.layer(TimeoutLayer::with_status_code(StatusCode::GATEWAY_TIMEOUT, Duration::from_secs(5)))
.layer(CorsLayer::permissive())
.layer(NormalizePathLayer::trim_trailing_slash());
const BASE_PORT: u16 = 3110;
const MAX_PORT: u16 = BASE_PORT + 100;