mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-29 00:59:58 -07:00
global: snapshot
This commit is contained in:
@@ -12,8 +12,10 @@ use serde::Deserialize;
|
||||
use parser::{log, Date, DateMap, Height, HeightMap, MapChunkId, HEIGHT_MAP_CHUNK_SIZE, OHLC};
|
||||
|
||||
use crate::{
|
||||
chunk::Chunk, headers::add_cors_to_headers, kind::Kind, response::typed_value_to_response,
|
||||
routes::Route, AppState,
|
||||
api::structs::{Chunk, Kind, Route},
|
||||
header_map::HeaderMapUtils,
|
||||
response::typed_value_to_response,
|
||||
AppState,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -21,26 +23,26 @@ pub struct Params {
|
||||
chunk: Option<usize>,
|
||||
}
|
||||
|
||||
pub async fn file_handler(
|
||||
pub async fn dataset_handler(
|
||||
headers: HeaderMap,
|
||||
path: Path<String>,
|
||||
query: Query<Params>,
|
||||
State(app_state): State<AppState>,
|
||||
) -> Response {
|
||||
match _file_handler(headers, path, query, app_state) {
|
||||
match _dataset_handler(headers, path, query, app_state) {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
let mut response =
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
|
||||
|
||||
add_cors_to_headers(response.headers_mut());
|
||||
response.headers_mut().insert_cors();
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _file_handler(
|
||||
fn _dataset_handler(
|
||||
headers: HeaderMap,
|
||||
Path(path): Path<String>,
|
||||
query: Query<Params>,
|
||||
14
server/src/api/handlers/fallback.rs
Normal file
14
server/src/api/handlers/fallback.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use axum::{extract::State, http::HeaderMap, response::Response};
|
||||
use reqwest::header::HOST;
|
||||
|
||||
use crate::{response::generic_to_reponse, AppState};
|
||||
|
||||
pub async fn fallback(headers: HeaderMap, State(app_state): State<AppState>) -> Response {
|
||||
generic_to_reponse(
|
||||
app_state
|
||||
.routes
|
||||
.to_full_paths(headers[HOST].to_str().unwrap().to_string()),
|
||||
None,
|
||||
60,
|
||||
)
|
||||
}
|
||||
5
server/src/api/handlers/mod.rs
Normal file
5
server/src/api/handlers/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod dataset;
|
||||
mod fallback;
|
||||
|
||||
pub use dataset::*;
|
||||
pub use fallback::*;
|
||||
19
server/src/api/mod.rs
Normal file
19
server/src/api/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use axum::{routing::get, Router};
|
||||
use handlers::{dataset_handler, fallback};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
mod handlers;
|
||||
pub mod structs;
|
||||
|
||||
pub trait ApiRoutes {
|
||||
fn add_api_routes(self) -> Self;
|
||||
}
|
||||
|
||||
impl ApiRoutes for Router<AppState> {
|
||||
fn add_api_routes(self) -> Self {
|
||||
self.route("/api/*path", get(dataset_handler))
|
||||
.route("/api/", get(fallback))
|
||||
.route("/api", get(fallback))
|
||||
}
|
||||
}
|
||||
9
server/src/api/structs/mod.rs
Normal file
9
server/src/api/structs/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod chunk;
|
||||
mod kind;
|
||||
mod paths;
|
||||
mod routes;
|
||||
|
||||
pub use chunk::*;
|
||||
pub use kind::*;
|
||||
pub use paths::*;
|
||||
pub use routes::*;
|
||||
@@ -8,7 +8,9 @@ use derive_deref::{Deref, DerefMut};
|
||||
use itertools::Itertools;
|
||||
use parser::{Json, Serialization};
|
||||
|
||||
use crate::{paths::Paths, Grouped};
|
||||
use crate::Grouped;
|
||||
|
||||
use super::Paths;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Route {
|
||||
74
server/src/header_map.rs
Normal file
74
server/src/header_map.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::path::Path;
|
||||
|
||||
use axum::http::{header, HeaderMap};
|
||||
use parser::log;
|
||||
|
||||
const STALE_IF_ERROR: u64 = 31_536_000; // 1 Year
|
||||
|
||||
pub trait HeaderMapUtils {
|
||||
fn insert_cors(&mut self);
|
||||
|
||||
fn insert_cache_control(&mut self, max_age: u64, stale_while_revalidate: u64);
|
||||
|
||||
fn insert_content_type(&mut self, path: &Path);
|
||||
fn insert_content_type_application_javascript(&mut self);
|
||||
fn insert_content_type_application_json(&mut self);
|
||||
fn insert_content_type_text_html(&mut self);
|
||||
fn insert_content_type_text_plain(&mut self);
|
||||
fn insert_content_type_font_woff2(&mut self);
|
||||
}
|
||||
|
||||
impl HeaderMapUtils 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, max_age: u64, stale_while_revalidate: u64) {
|
||||
self.insert(
|
||||
header::CACHE_CONTROL,
|
||||
format!(
|
||||
"public, max-age={max_age}, stale-while-revalidate={stale_while_revalidate}, stale-if-error={STALE_IF_ERROR}")
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
fn insert_content_type(&mut self, path: &Path) {
|
||||
match path.extension().unwrap().to_str().unwrap() {
|
||||
"js" => self.insert_content_type_application_javascript(),
|
||||
"json" => self.insert_content_type_application_json(),
|
||||
"html" => self.insert_content_type_text_html(),
|
||||
"txt" => self.insert_content_type_text_plain(),
|
||||
"woff2" => self.insert_content_type_font_woff2(),
|
||||
extension => {
|
||||
log(&format!("Extension unsupported: {extension}"));
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_content_type_application_javascript(&mut self) {
|
||||
self.insert(
|
||||
header::CONTENT_TYPE,
|
||||
"application/javascript".parse().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn insert_content_type_application_json(&mut self) {
|
||||
self.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
}
|
||||
|
||||
fn insert_content_type_text_html(&mut self) {
|
||||
self.insert(header::CONTENT_TYPE, "text/html".parse().unwrap());
|
||||
}
|
||||
|
||||
fn insert_content_type_text_plain(&mut self) {
|
||||
self.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
|
||||
}
|
||||
|
||||
fn insert_content_type_font_woff2(&mut self) {
|
||||
self.insert(header::CONTENT_TYPE, "font/woff2".parse().unwrap());
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use axum::http::{header, HeaderMap};
|
||||
|
||||
const STALE_IF_ERROR: u64 = 604800; // 1 Week
|
||||
|
||||
pub fn add_cors_to_headers(headers: &mut HeaderMap) {
|
||||
headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
|
||||
headers.insert(header::ACCESS_CONTROL_ALLOW_HEADERS, "*".parse().unwrap());
|
||||
}
|
||||
|
||||
pub fn add_json_type_to_headers(headers: &mut HeaderMap) {
|
||||
headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
}
|
||||
|
||||
pub fn add_cache_control_to_headers(
|
||||
headers: &mut HeaderMap,
|
||||
max_age: u64,
|
||||
stale_while_revalidate: u64,
|
||||
) {
|
||||
headers.insert(
|
||||
header::CACHE_CONTROL,
|
||||
format!(
|
||||
"public, max-age={max_age}, stale-while-revalidate={stale_while_revalidate}, stale-if-error={STALE_IF_ERROR}")
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::State, http::HeaderMap, response::Response, routing::get, serve, Router};
|
||||
use api::{structs::Routes, ApiRoutes};
|
||||
use axum::{serve, Router};
|
||||
use parser::{log, reset_logs};
|
||||
use reqwest::header::HOST;
|
||||
use response::generic_to_reponse;
|
||||
use routes::Routes;
|
||||
use serde::Serialize;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
use website::WebsiteRoutes;
|
||||
|
||||
mod chunk;
|
||||
mod handler;
|
||||
mod headers;
|
||||
mod kind;
|
||||
mod paths;
|
||||
mod api;
|
||||
mod header_map;
|
||||
mod response;
|
||||
mod routes;
|
||||
|
||||
use handler::file_handler;
|
||||
mod website;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct Grouped<T> {
|
||||
@@ -52,28 +46,26 @@ async fn main() -> color_eyre::Result<()> {
|
||||
.zstd(true);
|
||||
|
||||
let router = Router::new()
|
||||
.route("/*path", get(file_handler))
|
||||
.route("/", get(fallback))
|
||||
.add_api_routes()
|
||||
.add_website_routes()
|
||||
.with_state(state)
|
||||
.layer(compression_layer);
|
||||
|
||||
let port = 3110;
|
||||
let mut port = 3110;
|
||||
|
||||
let mut listener;
|
||||
loop {
|
||||
listener = TcpListener::bind(format!("0.0.0.0:{port}")).await;
|
||||
if listener.is_ok() {
|
||||
break;
|
||||
}
|
||||
port += 1;
|
||||
}
|
||||
|
||||
log(&format!("Starting server on port {port}..."));
|
||||
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{port}")).await?;
|
||||
let listener = listener.unwrap();
|
||||
|
||||
serve(listener, router).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fallback(headers: HeaderMap, State(app_state): State<AppState>) -> Response {
|
||||
generic_to_reponse(
|
||||
app_state
|
||||
.routes
|
||||
.to_full_paths(headers[HOST].to_str().unwrap().to_string()),
|
||||
None,
|
||||
60,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
chunk::Chunk,
|
||||
headers::{add_cache_control_to_headers, add_cors_to_headers, add_json_type_to_headers},
|
||||
kind::Kind,
|
||||
routes::Route,
|
||||
api::structs::{Chunk, Kind, Route},
|
||||
header_map::HeaderMapUtils,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -88,9 +86,9 @@ where
|
||||
let max_age = cache_time;
|
||||
let stale_while_revalidate = 2 * max_age;
|
||||
|
||||
add_cors_to_headers(headers);
|
||||
add_json_type_to_headers(headers);
|
||||
add_cache_control_to_headers(headers, max_age, stale_while_revalidate);
|
||||
headers.insert_cors();
|
||||
headers.insert_content_type_application_json();
|
||||
headers.insert_cache_control(max_age, stale_while_revalidate);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
56
server/src/website/handlers/file.rs
Normal file
56
server/src/website/handlers/file.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::{
|
||||
fs::{self},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use axum::{extract, http::HeaderMap, response::Response};
|
||||
use parser::log;
|
||||
|
||||
use crate::header_map::HeaderMapUtils;
|
||||
|
||||
use super::minify_js;
|
||||
|
||||
const WEBSITE_PATH: &str = "../website";
|
||||
|
||||
pub async fn file_handler(headers: HeaderMap, path: extract::Path<String>) -> Response {
|
||||
let path = path.0.replace("..", "").replace("\\", "");
|
||||
let mut path = str_to_path(&path);
|
||||
if path.extension().is_none() && !path.exists() {
|
||||
path = str_to_path("index.html");
|
||||
}
|
||||
|
||||
path_to_response(&path)
|
||||
}
|
||||
|
||||
pub async fn index_handler(headers: HeaderMap) -> Response {
|
||||
path_to_response(&str_to_path("index.html"))
|
||||
}
|
||||
|
||||
fn path_to_response(path: &Path) -> Response {
|
||||
let mut response;
|
||||
|
||||
if path.extension().unwrap() == "js" {
|
||||
let content = minify_js(path);
|
||||
|
||||
response = Response::new(content.into());
|
||||
} else {
|
||||
let content = fs::read(path).unwrap_or_else(|error| {
|
||||
log(&error.to_string());
|
||||
let path = path.to_str().unwrap();
|
||||
log(&format!("Can't read file {path}"));
|
||||
panic!("")
|
||||
});
|
||||
|
||||
response = Response::new(content.into());
|
||||
}
|
||||
|
||||
let headers = response.headers_mut();
|
||||
headers.insert_cors();
|
||||
headers.insert_content_type(path);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn str_to_path(path: &str) -> PathBuf {
|
||||
PathBuf::from(&format!("{WEBSITE_PATH}/{path}"))
|
||||
}
|
||||
29
server/src/website/handlers/minify/js.rs
Normal file
29
server/src/website/handlers/minify/js.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Simplified version of: https://github.com/swc-project/swc/blob/main/crates/swc/examples/minify.rs
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use swc::{config::JsMinifyOptions, try_with_handler, JsMinifyExtras};
|
||||
use swc_common::{SourceMap, GLOBALS};
|
||||
|
||||
pub fn minify_js(path: &Path) -> String {
|
||||
let cm = Arc::<SourceMap>::default();
|
||||
|
||||
let c = swc::Compiler::new(cm.clone());
|
||||
|
||||
let output = GLOBALS
|
||||
.set(&Default::default(), || {
|
||||
try_with_handler(cm.clone(), Default::default(), |handler| {
|
||||
let fm = cm.load_file(path).expect("failed to load file");
|
||||
|
||||
c.minify(
|
||||
fm,
|
||||
handler,
|
||||
&JsMinifyOptions::default(),
|
||||
JsMinifyExtras::default(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
output.code
|
||||
}
|
||||
3
server/src/website/handlers/minify/mod.rs
Normal file
3
server/src/website/handlers/minify/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod js;
|
||||
|
||||
pub use js::*;
|
||||
5
server/src/website/handlers/mod.rs
Normal file
5
server/src/website/handlers/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod file;
|
||||
mod minify;
|
||||
|
||||
pub use file::*;
|
||||
use minify::*;
|
||||
18
server/src/website/mod.rs
Normal file
18
server/src/website/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
mod handlers;
|
||||
|
||||
use handlers::{file_handler, index_handler};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
pub trait WebsiteRoutes {
|
||||
fn add_website_routes(self) -> Self;
|
||||
}
|
||||
|
||||
impl WebsiteRoutes for Router<AppState> {
|
||||
fn add_website_routes(self) -> Self {
|
||||
self.route("/*path", get(file_handler))
|
||||
.route("/", get(index_handler))
|
||||
}
|
||||
}
|
||||
0
server/src/website/structs/mod.rs
Normal file
0
server/src/website/structs/mod.rs
Normal file
Reference in New Issue
Block a user