server: started

This commit is contained in:
nym21
2025-02-11 18:52:08 +01:00
parent 5aaa55197e
commit cb7ea40e7c
18 changed files with 542 additions and 277 deletions
+15 -18
View File
@@ -1,37 +1,34 @@
[package] [package]
name = "kibo_server" name = "berver"
version = "0.6.0" version = "0.6.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
# allocative = "0.3.4"
axum = "0.8.1"
# bincode = { git = "https://github.com/bincode-org/bincode.git", features = [
# "serde",
# ] }
# bitcoin_hashes = { version = "0.15.0" } # bitcoin_hashes = { version = "0.15.0" }
# biter = { path = "./src/crates/biter" } # biter = { path = "./src/crates/biter" }
# chrono = { version = "0.4.39", features = ["serde"] } # chrono = { version = "0.4.39", features = ["serde"] }
# clap = { version = "4.5.26", features = ["derive"] } # clap = { version = "4.5.26", features = ["derive"] }
color-eyre = { workspace = true }
# ctrlc = { version = "3.4.5", features = ["termination"] } # ctrlc = { version = "3.4.5", features = ["termination"] }
# derive_deref = "1.1.1"
# env_logger = "0.11.6"
# inferno = "0.12.1" # inferno = "0.12.1"
# itertools = "0.13.0" # itertools = "0.13.0"
# log = { version = "0.4.25", features = ["std", "serde"] }
# ordered-float = "4.6.0" # ordered-float = "4.6.0"
# rayon = "1.10.0" # rayon = "1.10.0"
# rlimit = "0.10.2"
# serde_json = "1.0.135"
axum = "0.8.1"
color-eyre = { workspace = true }
computer = { workspace = true }
derive_deref = { workspace = true }
indexer = { workspace = true }
jiff = { workspace = true }
logger = { workspace = true }
oxc = { version = "0.49.0", features = ["codegen", "minifier"] }
regex = "1.11.1" regex = "1.11.1"
reqwest = { version = "0.12.12", features = ["blocking", "json"] } reqwest = { version = "0.12.12", features = ["blocking", "json"] }
# rlimit = "0.10.2" serde = { workspace = true }
# snkrj = { path = "./src/crates/snkrj" } serde_json = "1.0.138"
# serde = { version = "1.0.217", features = ["derive"] } storable_vec = { workspace = true }
# serde_json = "1.0.135" struct_iterable = { workspace = true }
# struct_iterable = { path = "./src/crates/iterable" }
swc = "13.0.1"
swc_common = "6.0.0"
tokio = { version = "1.43.0", features = ["full"] } tokio = { version = "1.43.0", features = ["full"] }
# toml = "0.8.19"
tower-http = { version = "0.6.2", features = ["compression-full"] } tower-http = { version = "0.6.2", features = ["compression-full"] }
zstd = "0.13.2" zstd = "0.13.2"
+31 -63
View File
@@ -15,9 +15,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::{ use crate::{
server::{ server::{
api::{ api::{
structs::{ structs::{ChunkMetadata, DatasetRange, DatasetRangeChunk, Extension, Kind, Route, Routes},
ChunkMetadata, DatasetRange, DatasetRangeChunk, Extension, Kind, Route, Routes,
},
API_URL_PREFIX, API_URL_PREFIX,
}, },
header_map::{HeaderMapExtended, Modified}, header_map::{HeaderMapExtended, Modified},
@@ -26,8 +24,8 @@ use crate::{
AppState, AppState,
}, },
structs::{ structs::{
Date, GenericMap, Height, HeightMapChunkId, MapChunkId, MapKey, MapSerialized, MapValue, Date, GenericMap, Height, HeightMapChunkId, MapChunkId, MapKey, MapSerialized, MapValue, SerializedBTreeMap,
SerializedBTreeMap, SerializedDateMap, SerializedTimeMap, SerializedVec, Timestamp, OHLC, SerializedDateMap, SerializedTimeMap, SerializedVec, Timestamp, OHLC,
}, },
}; };
@@ -50,12 +48,8 @@ pub async fn dataset_handler(
"{API_URL_PREFIX}/{}?kind={}{}{}", "{API_URL_PREFIX}/{}?kind={}{}{}",
path.0, path.0,
query.kind, query.kind,
query query.chunk.map_or("".to_string(), |chunk| format!("&chunk={chunk}")),
.chunk query.all.map_or("".to_string(), |all| format!("&all={all}"))
.map_or("".to_string(), |chunk| format!("&chunk={chunk}")),
query
.all
.map_or("".to_string(), |all| format!("&all={all}"))
); );
match result_handler(headers, &path, &query, app_state) { match result_handler(headers, &path, &query, app_state) {
@@ -64,8 +58,7 @@ pub async fn dataset_handler(
response response
} }
Err(error) => { Err(error) => {
let mut response = let mut response = (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
log_result(response.status(), &ser_path, instant); log_result(response.status(), &ser_path, instant);
response.headers_mut().insert_cors(); response.headers_mut().insert_cors();
response response
@@ -129,64 +122,49 @@ where
let last_value: T = route.serialization.import(&route.path.join("last"))?; let last_value: T = route.serialization.import(&route.path.join("last"))?;
return Ok(axum::response::Json(last_value).into_response()); return Ok(axum::response::Json(last_value).into_response());
} }
Kind::Date => match read_serialized::<Date, T, _, SerializedDateMap<T>>( Kind::Date => match read_serialized::<Date, T, _, SerializedDateMap<T>>(id, &headers, route, &range, query)? {
id, &headers, route, &range, query,
)? {
ReadSerialized::DatasetAndDate((dataset, date, chunk_meta)) => { ReadSerialized::DatasetAndDate((dataset, date, chunk_meta)) => {
(serialized_to_response(dataset, id, chunk_meta, ext), date) (serialized_to_response(dataset, id, chunk_meta, ext), date)
} }
ReadSerialized::NotModified => return Ok(Response::new_not_modified()), ReadSerialized::NotModified => return Ok(Response::new_not_modified()),
ReadSerialized::_Phantom(_) => unreachable!(), ReadSerialized::_Phantom(_) => unreachable!(),
}, },
Kind::Height => match read_serialized::<Height, T, _, SerializedVec<T>>( Kind::Height => match read_serialized::<Height, T, _, SerializedVec<T>>(id, &headers, route, &range, query)? {
id, &headers, route, &range, query,
)? {
ReadSerialized::DatasetAndDate((dataset, date, chunk_meta)) => ( ReadSerialized::DatasetAndDate((dataset, date, chunk_meta)) => (
serialized_to_response::<Height, T, _, SerializedVec<T>>( serialized_to_response::<Height, T, _, SerializedVec<T>>(dataset, id, chunk_meta, ext),
dataset, id, chunk_meta, ext,
),
date, date,
), ),
ReadSerialized::NotModified => return Ok(Response::new_not_modified()), ReadSerialized::NotModified => return Ok(Response::new_not_modified()),
ReadSerialized::_Phantom(_) => unreachable!(), ReadSerialized::_Phantom(_) => unreachable!(),
}, },
Kind::Timestamp => { Kind::Timestamp => {
let (dataset, date, chunk_meta) = match read_serialized::<Height, T, _, SerializedVec<T>>( let (dataset, date, chunk_meta) =
id, &headers, route, &range, query, match read_serialized::<Height, T, _, SerializedVec<T>>(id, &headers, route, &range, query)? {
ReadSerialized::DatasetAndDate(tuple) => tuple,
ReadSerialized::NotModified => return Ok(Response::new_not_modified()),
ReadSerialized::_Phantom(_) => unreachable!(),
};
let (timestamp_dataset, _, _) = match read_serialized::<Height, Timestamp, _, SerializedVec<Timestamp>>(
"timestamp",
&headers,
routes.get("timestamp").unwrap(),
&range,
query,
)? { )? {
ReadSerialized::DatasetAndDate(tuple) => tuple, ReadSerialized::DatasetAndDate(tuple) => tuple,
ReadSerialized::NotModified => return Ok(Response::new_not_modified()), ReadSerialized::NotModified => return Ok(Response::new_not_modified()),
ReadSerialized::_Phantom(_) => unreachable!(), ReadSerialized::_Phantom(_) => unreachable!(),
}; };
let (timestamp_dataset, _, _) =
match read_serialized::<Height, Timestamp, _, SerializedVec<Timestamp>>(
"timestamp",
&headers,
routes.get("timestamp").unwrap(),
&range,
query,
)? {
ReadSerialized::DatasetAndDate(tuple) => tuple,
ReadSerialized::NotModified => return Ok(Response::new_not_modified()),
ReadSerialized::_Phantom(_) => unreachable!(),
};
let mut serialized_timemap: SerializedTimeMap<T> = SerializedBTreeMap::default(); let mut serialized_timemap: SerializedTimeMap<T> = SerializedBTreeMap::default();
dataset dataset.map.into_iter().enumerate().for_each(|(index, value)| {
.map serialized_timemap.map.insert(
.into_iter() timestamp_dataset.get_index(index).cloned().unwrap_or(Timestamp::now()),
.enumerate() value,
.for_each(|(index, value)| { );
serialized_timemap.map.insert( });
timestamp_dataset
.get_index(index)
.cloned()
.unwrap_or(Timestamp::now()),
value,
);
});
( (
serialized_to_response::<Timestamp, T, HeightMapChunkId, SerializedTimeMap<T>>( serialized_to_response::<Timestamp, T, HeightMapChunkId, SerializedTimeMap<T>>(
@@ -287,19 +265,13 @@ where
let date_modified; let date_modified;
let datasets = let datasets = GenericMap::<Key, Value, ChunkId, Serialized>::_read_dir(&folder_path, serialization);
GenericMap::<Key, Value, ChunkId, Serialized>::_read_dir(&folder_path, serialization);
let mut chunk_meta = None; let mut chunk_meta = None;
let dataset = if let DatasetRange::Chunk(range_chunk) = range { let dataset = if let DatasetRange::Chunk(range_chunk) = range {
let chunk_id = match range_chunk { let chunk_id = match range_chunk {
DatasetRangeChunk::Last => { DatasetRangeChunk::Last => *datasets.last_key_value().context("Last tuple of dataset directory")?.0,
*datasets
.last_key_value()
.context("Last tuple of dataset directory")?
.0
}
DatasetRangeChunk::Chunk(chunk) => ChunkId::from_usize(*chunk), DatasetRangeChunk::Chunk(chunk) => ChunkId::from_usize(*chunk),
}; };
@@ -358,11 +330,7 @@ where
Serialized::import_all(&folder_path, serialization) Serialized::import_all(&folder_path, serialization)
}; };
Ok(ReadSerialized::DatasetAndDate(( Ok(ReadSerialized::DatasetAndDate((dataset, date_modified, chunk_meta)))
dataset,
date_modified,
chunk_meta,
)))
} }
#[derive(Serialize)] #[derive(Serialize)]
+93 -6
View File
@@ -1,12 +1,25 @@
use axum::{routing::get, Router}; use std::time::Instant;
use handlers::{dataset_handler, last_values_handler};
use axum::{
extract::{Query, State},
http::HeaderMap,
response::{IntoResponse, Response},
routing::get,
Json, Router,
};
use color_eyre::eyre::eyre;
use reqwest::StatusCode;
use serde::Deserialize;
use structs::{Format, Index};
use crate::{log_result, traits::HeaderMapExtended};
use super::AppState; use super::AppState;
mod handlers; // mod handlers;
pub mod structs; pub mod structs;
pub const API_URL_PREFIX: &str = "/api"; pub const VECS_URL_PREFIX: &str = "/api/vecs";
pub trait ApiRoutes { pub trait ApiRoutes {
fn add_api_routes(self) -> Self; fn add_api_routes(self) -> Self;
@@ -14,7 +27,81 @@ pub trait ApiRoutes {
impl ApiRoutes for Router<AppState> { impl ApiRoutes for Router<AppState> {
fn add_api_routes(self) -> Self { fn add_api_routes(self) -> Self {
self.route(&format!("{API_URL_PREFIX}/last"), get(last_values_handler)) self.route(VECS_URL_PREFIX, get(handler))
.route(&format!("{API_URL_PREFIX}/*path"), get(dataset_handler)) }
}
#[derive(Debug, Deserialize)]
pub struct DatasetParams {
pub i: String,
pub v: String,
pub from: Option<i64>,
pub to: Option<i64>,
pub format: Option<String>,
}
pub async fn handler(headers: HeaderMap, query: Query<DatasetParams>, State(app_state): State<AppState>) -> Response {
let instant = Instant::now();
let path = format!(
"{VECS_URL_PREFIX}?i={}&v={}{}{}",
query.i,
query.v,
query.from.map_or("".to_string(), |from| format!("&from={from}")),
query.to.map_or("".to_string(), |to| format!("&to={to}")),
);
match req_to_response_res(headers, query, app_state) {
Ok(response) => {
log_result(response.status(), &path, instant);
response
}
Err(error) => {
let mut response = (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
log_result(response.status(), &path, instant);
response.headers_mut().insert_cors();
response
}
}
}
fn req_to_response_res(
headers: HeaderMap,
Query(DatasetParams { format, from, i, to, v }): Query<DatasetParams>,
AppState { vecs, .. }: AppState,
) -> color_eyre::Result<Response> {
let format = Format::try_from(format).ok();
let indexes = i
.to_lowercase()
.split(",")
.flat_map(|s| Index::try_from(s).ok())
.collect::<Vec<_>>();
if indexes.len() > 1 {
return Err(eyre!("Multiple indexes aren't supported yet !"));
} else if indexes.is_empty() {
return Err(eyre!("Bad index(es)"));
}
dbg!(format, &indexes, &v, from, to);
let values = v
.to_lowercase()
.split(",")
.flat_map(|s| vecs.get(&s.replace("_", "-")))
.flat_map(|i_to_v| i_to_v.get(indexes.first().unwrap()))
.map(|vec| vec.collect_range(from, to).unwrap())
.collect::<Vec<_>>();
if values.len() == 1 {
let values = values.first().unwrap();
if values.len() == 1 {
let value = values.first().unwrap();
Ok(Json(value).into_response())
} else {
Ok(Json(values).into_response())
}
} else {
Ok(Json(values).into_response())
} }
} }
-8
View File
@@ -1,8 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct ChunkMetadata {
pub id: usize,
pub previous: Option<String>,
pub next: Option<String>,
}
-34
View File
@@ -1,34 +0,0 @@
use std::path::Path;
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Extension {
#[allow(clippy::upper_case_acronyms)]
CSV,
#[allow(clippy::upper_case_acronyms)]
JSON,
}
impl Extension {
pub fn from(path: &Path) -> Option<Self> {
if let Some(extension) = path.extension() {
let extension = extension.to_str().unwrap();
if extension == Self::CSV.to_str() {
Some(Self::CSV)
} else if extension == Self::JSON.to_str() {
Some(Self::JSON)
} else {
None
}
} else {
None
}
}
pub fn to_str(&self) -> &str {
match self {
Extension::CSV => "csv",
Extension::JSON => "json",
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use color_eyre::eyre::eyre;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Format {
CSV,
JSON,
}
impl TryFrom<Option<String>> for Format {
type Error = color_eyre::Report;
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
if let Some(value) = value {
let value = value.to_lowercase();
let value = value.as_str();
if value == "csv" {
Ok(Self::CSV)
} else if value == "json" {
Ok(Self::JSON)
} else {
Err(eyre!("Fail"))
}
} else {
Err(eyre!("Fail"))
}
}
}
+122
View File
@@ -0,0 +1,122 @@
use std::fmt::{self, Debug};
use computer::Date;
use indexer::{
Addressindex, Height, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex,
Txinindex, Txoutindex,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Index {
Addressindex,
Date,
Height,
P2PK33index,
P2PK65index,
P2PKHindex,
P2SHindex,
P2TRindex,
P2WPKHindex,
P2WSHindex,
Txindex,
Txinindex,
Txoutindex,
}
impl TryFrom<&str> for Index {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(match value {
"addri" | "addressindex" => Self::Addressindex,
"d" | "date" => Self::Date,
"h" | "height" => Self::Height,
"p2pk33index" => Self::P2PK33index,
"p2pk65index" => Self::P2PK65index,
"p2pkhindex" => Self::P2PKHindex,
"p2shindex" => Self::P2SHindex,
"p2trindex" => Self::P2TRindex,
"p2wpkhindex" => Self::P2WPKHindex,
"p2wshindex" => Self::P2WSHindex,
"txi" | "txindex" => Self::Txindex,
"txini" | "txinindex" => Self::Txinindex,
"txouti" | "txoutindex" => Self::Txoutindex,
_ => return Err(()),
})
}
}
impl fmt::Display for Index {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(self, f)
}
}
pub trait IndexTypeToIndexEnum {
fn to_enum() -> Index;
}
impl IndexTypeToIndexEnum for Addressindex {
fn to_enum() -> Index {
Index::Addressindex
}
}
impl IndexTypeToIndexEnum for Date {
fn to_enum() -> Index {
Index::Date
}
}
impl IndexTypeToIndexEnum for Height {
fn to_enum() -> Index {
Index::Height
}
}
impl IndexTypeToIndexEnum for Txindex {
fn to_enum() -> Index {
Index::Txindex
}
}
impl IndexTypeToIndexEnum for Txinindex {
fn to_enum() -> Index {
Index::Txinindex
}
}
impl IndexTypeToIndexEnum for Txoutindex {
fn to_enum() -> Index {
Index::Txoutindex
}
}
impl IndexTypeToIndexEnum for P2PK33index {
fn to_enum() -> Index {
Index::P2PK33index
}
}
impl IndexTypeToIndexEnum for P2PK65index {
fn to_enum() -> Index {
Index::P2PK65index
}
}
impl IndexTypeToIndexEnum for P2PKHindex {
fn to_enum() -> Index {
Index::P2PKHindex
}
}
impl IndexTypeToIndexEnum for P2SHindex {
fn to_enum() -> Index {
Index::P2SHindex
}
}
impl IndexTypeToIndexEnum for P2TRindex {
fn to_enum() -> Index {
Index::P2TRindex
}
}
impl IndexTypeToIndexEnum for P2WPKHindex {
fn to_enum() -> Index {
Index::P2WPKHindex
}
}
impl IndexTypeToIndexEnum for P2WSHindex {
fn to_enum() -> Index {
Index::P2WSHindex
}
}
+4 -4
View File
@@ -9,8 +9,8 @@ use crate::structs::{AnyMap, Date, Height, MapKey};
pub enum Kind { pub enum Kind {
Date, Date,
Height, Height,
Timestamp, // Timestamp,
Last, // Last,
} }
impl TryFrom<&String> for Kind { impl TryFrom<&String> for Kind {
@@ -26,8 +26,8 @@ impl TryFrom<&String> for Kind {
{ {
'd' => Self::Date, 'd' => Self::Date,
'h' => Self::Height, 'h' => Self::Height,
't' => Self::Timestamp, // 't' => Self::Timestamp,
'l' => Self::Last, // 'l' => Self::Last,
_ => return Err(eyre!("Bad kind")), _ => return Err(eyre!("Bad kind")),
}, },
) )
+14 -12
View File
@@ -1,13 +1,15 @@
mod chunk_metadata; // mod chunk_metadata;
mod extension; mod format;
mod kind; mod index;
mod range; // mod kind;
mod route; // mod range;
mod routes; // mod route;
// mod routes;
pub use chunk_metadata::*; // pub use chunk_metadata::*;
pub use extension::*; pub use format::*;
pub use kind::*; pub use index::*;
pub use range::*; // pub use kind::*;
pub use route::*; // pub use range::*;
pub use routes::*; // pub use route::*;
// pub use routes::*;
+136 -20
View File
@@ -1,40 +1,155 @@
use std::{sync::Arc, time::Instant}; use std::{collections::BTreeMap, time::Instant};
use api::{structs::Routes, ApiRoutes}; use api::{
use axum::{routing::get, serve, Router}; structs::{Index, IndexTypeToIndexEnum},
ApiRoutes,
};
use axum::{body::Body, response::IntoResponse, routing::get, serve, Router};
use color_eyre::owo_colors::OwoColorize; use color_eyre::owo_colors::OwoColorize;
use log::{error, info}; use computer::Computer;
use derive_deref::{Deref, DerefMut};
use indexer::Indexer;
use logger::{error, info};
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::Serialize;
use serde_json::Value;
use storable_vec::{StorableVecIndex, StorableVecType, STATELESS};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
use website::WebsiteRoutes; use website::WebsiteRoutes;
use crate::structs::Config; mod api;
mod traits;
pub mod api;
mod header_map;
mod response;
mod website; mod website;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
routes: Arc<Routes>, vecs: &'static VecIdToIndexToVec,
config: Config, indexer: &'static Indexer<STATELESS>,
computer: &'static Computer<STATELESS>,
} }
pub async fn main(routes: Routes, config: Config) -> color_eyre::Result<()> { #[derive(Default, Deref, DerefMut)]
routes.generate_dts_file(); pub struct VecIdToIndexToVec(BTreeMap<String, IndexToVec>);
impl VecIdToIndexToVec {
pub fn insert<I, T>(&mut self, vec: &'static storable_vec::StorableVec<I, T, STATELESS>)
where
I: StorableVecIndex + IndexTypeToIndexEnum + Send + Sync,
T: StorableVecType + Send + Sync + Serialize,
{
let file_name = vec.file_name();
let split = file_name.split("_to_").collect::<Vec<_>>();
if split.len() != 2 {
panic!();
}
let index = vec.key_to_enum();
if split[0] != index.to_string().to_lowercase() {
dbg!(split[0], index.to_string());
panic!();
}
let key = split[1].to_string().replace("_", "-");
let prev = self.entry(key).or_default().insert(index, vec);
if prev.is_some() {
panic!()
}
}
}
#[derive(Default, Deref, DerefMut)]
pub struct IndexToVec {
pub index_to_vec: BTreeMap<Index, &'static (dyn AnyStatelessStorableVec + Send + Sync)>,
}
pub trait AnyStatelessStorableVec {
fn key_to_enum(&self) -> Index;
fn collect_range(&self, from: Option<i64>, to: Option<i64>) -> storable_vec::Result<Vec<Value>>;
// fn collect_range(&self, from: Option<i64>, to: Option<i64>) -> storable_vec::Result<Vec<T>>;
}
impl<I, T> AnyStatelessStorableVec for storable_vec::StorableVec<I, T, STATELESS>
where
I: StorableVecIndex + IndexTypeToIndexEnum + Send + Sync,
T: StorableVecType + Send + Sync + Serialize,
{
fn key_to_enum(&self) -> Index {
I::to_enum()
}
fn collect_range(&self, from: Option<i64>, to: Option<i64>) -> storable_vec::Result<Vec<Value>> {
Ok(self
.collect_range(from, to)?
.into_iter()
.map(|v| serde_json::to_value(v).unwrap())
.collect::<Vec<_>>())
}
}
trait StatelessVecs {
fn parse(&'static self, vecs: &mut VecIdToIndexToVec);
}
impl StatelessVecs for Indexer<STATELESS> {
fn parse(&'static self, vecs: &mut VecIdToIndexToVec) {
vecs.insert(&self.vecs.addressindex_to_addresstype);
vecs.insert(&self.vecs.addressindex_to_addresstypeindex);
vecs.insert(&self.vecs.addressindex_to_height);
vecs.insert(&self.vecs.height_to_blockhash);
vecs.insert(&self.vecs.height_to_difficulty);
vecs.insert(&self.vecs.height_to_first_addressindex);
vecs.insert(&self.vecs.height_to_first_emptyindex);
vecs.insert(&self.vecs.height_to_first_multisigindex);
vecs.insert(&self.vecs.height_to_first_opreturnindex);
vecs.insert(&self.vecs.height_to_first_pushonlyindex);
vecs.insert(&self.vecs.height_to_first_txindex);
vecs.insert(&self.vecs.height_to_first_txinindex);
vecs.insert(&self.vecs.height_to_first_txoutindex);
vecs.insert(&self.vecs.height_to_first_unknownindex);
vecs.insert(&self.vecs.height_to_first_p2pk33index);
vecs.insert(&self.vecs.height_to_first_p2pk65index);
vecs.insert(&self.vecs.height_to_first_p2pkhindex);
vecs.insert(&self.vecs.height_to_first_p2shindex);
vecs.insert(&self.vecs.height_to_first_p2trindex);
vecs.insert(&self.vecs.height_to_first_p2wpkhindex);
vecs.insert(&self.vecs.height_to_first_p2wshindex);
vecs.insert(&self.vecs.height_to_size);
vecs.insert(&self.vecs.height_to_timestamp);
vecs.insert(&self.vecs.height_to_weight);
vecs.insert(&self.vecs.p2pk33index_to_p2pk33addressbytes);
vecs.insert(&self.vecs.p2pk65index_to_p2pk65addressbytes);
vecs.insert(&self.vecs.p2pkhindex_to_p2pkhaddressbytes);
vecs.insert(&self.vecs.p2shindex_to_p2shaddressbytes);
vecs.insert(&self.vecs.p2trindex_to_p2traddressbytes);
vecs.insert(&self.vecs.p2wpkhindex_to_p2wpkhaddressbytes);
vecs.insert(&self.vecs.p2wshindex_to_p2wshaddressbytes);
vecs.insert(&self.vecs.txindex_to_first_txinindex);
vecs.insert(&self.vecs.txindex_to_first_txoutindex);
vecs.insert(&self.vecs.txindex_to_height);
vecs.insert(&self.vecs.txindex_to_locktime);
vecs.insert(&self.vecs.txindex_to_txid);
vecs.insert(&self.vecs.txindex_to_txversion);
vecs.insert(&self.vecs.txinindex_to_txoutindex);
vecs.insert(&self.vecs.txoutindex_to_addressindex);
vecs.insert(&self.vecs.txoutindex_to_amount);
}
}
pub async fn main(indexer: Indexer<STATELESS>, computer: Computer<STATELESS>) -> color_eyre::Result<()> {
// pub async fn main(routes: Routes, config: Config) -> color_eyre::Result<()> {
// routes.generate_dts_file();
let indexer = Box::leak(Box::new(indexer));
let computer = Box::leak(Box::new(computer));
let vecs = Box::leak(Box::new(VecIdToIndexToVec::default()));
indexer.parse(vecs);
let state = AppState { let state = AppState {
routes: Arc::new(routes), vecs,
config, indexer,
computer,
}; };
let compression_layer = CompressionLayer::new() let compression_layer = CompressionLayer::new().br(true).deflate(true).gzip(true).zstd(true);
.br(true)
.deflate(true)
.gzip(true)
.zstd(true);
let router = Router::new() let router = Router::new()
.add_api_routes() .add_api_routes()
@@ -55,6 +170,7 @@ pub async fn main(routes: Routes, config: Config) -> color_eyre::Result<()> {
} }
info!("Starting server on port {port}..."); info!("Starting server on port {port}...");
let listener = listener.unwrap(); let listener = listener.unwrap();
serve(listener, router).await?; serve(listener, router).await?;
+20
View File
@@ -0,0 +1,20 @@
use std::path::Path;
use computer::Computer;
use indexer::Indexer;
use storable_vec::STATELESS;
#[tokio::main]
pub async fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
logger::init_log(None);
let path = Path::new("../_outputs");
let indexer: Indexer<STATELESS> = Indexer::import(&path.join("indexes"))?;
let computer: Computer<STATELESS> = Computer::import(&path.join("computed"))?;
berver::main(indexer, computer).await.unwrap();
Ok(())
}
@@ -1,15 +1,15 @@
use std::path::Path; use std::{path::Path, time};
use axum::http::{header, HeaderMap}; use axum::http::{header, HeaderMap};
use chrono::{DateTime, Timelike, Utc}; use jiff::{civil::DateTime, fmt::strtime, tz::TimeZone, Timestamp};
use log::info; use logger::info;
use reqwest::header::{HOST, IF_MODIFIED_SINCE}; use reqwest::header::{HOST, IF_MODIFIED_SINCE};
const STALE_IF_ERROR: u64 = 30_000_000; // 1 Year ish const STALE_IF_ERROR: u64 = 30_000_000; // 1 Year ish
const MODIFIED_SINCE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT"; const MODIFIED_SINCE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT";
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub enum Modified { pub enum ModifiedState {
ModifiedSince, ModifiedSince,
NotModifiedSince, NotModifiedSince,
} }
@@ -23,14 +23,13 @@ pub trait HeaderMapExtended {
fn insert_cors(&mut self); fn insert_cors(&mut self);
fn get_if_modified_since(&self) -> Option<DateTime<Utc>>; fn get_if_modified_since(&self) -> Option<DateTime>;
fn check_if_modified_since(&self, path: &Path) fn check_if_modified_since(&self, path: &Path) -> color_eyre::Result<(ModifiedState, DateTime)>;
-> color_eyre::Result<(Modified, DateTime<Utc>)>;
fn insert_cache_control_immutable(&mut self); fn insert_cache_control_immutable(&mut self);
#[allow(unused)] #[allow(unused)]
fn insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64); fn insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64);
fn insert_last_modified(&mut self, date: DateTime<Utc>); fn insert_last_modified(&mut self, date: DateTime);
fn insert_content_disposition_attachment(&mut self); fn insert_content_disposition_attachment(&mut self);
@@ -102,40 +101,39 @@ impl HeaderMapExtended for HeaderMap {
); );
} }
fn insert_last_modified(&mut self, date: DateTime<Utc>) { fn insert_last_modified(&mut self, date: DateTime) {
let formatted = date.format(MODIFIED_SINCE_FORMAT).to_string(); let formatted = date
.to_zoned(TimeZone::system())
.unwrap()
.strftime(MODIFIED_SINCE_FORMAT)
.to_string();
self.insert(header::LAST_MODIFIED, formatted.parse().unwrap()); self.insert(header::LAST_MODIFIED, formatted.parse().unwrap());
} }
fn check_if_modified_since( fn check_if_modified_since(&self, path: &Path) -> color_eyre::Result<(ModifiedState, DateTime)> {
&self, let duration = path.metadata()?.modified()?.duration_since(time::UNIX_EPOCH).unwrap();
path: &Path, let date = Timestamp::new(duration.as_secs() as i64, 0)
) -> color_eyre::Result<(Modified, DateTime<Utc>)> { .unwrap()
let time = path.metadata()?.modified()?; .to_zoned(TimeZone::UTC)
let date: DateTime<Utc> = time.into(); .datetime();
let date = date.with_nanosecond(0).unwrap();
if let Some(if_modified_since) = self.get_if_modified_since() { if let Some(if_modified_since) = self.get_if_modified_since() {
if if_modified_since == date { if if_modified_since == date {
return Ok((Modified::NotModifiedSince, date)); return Ok((ModifiedState::NotModifiedSince, date));
} }
} }
Ok((Modified::ModifiedSince, date)) Ok((ModifiedState::ModifiedSince, date))
} }
fn get_if_modified_since(&self) -> Option<DateTime<Utc>> { fn get_if_modified_since(&self) -> Option<DateTime> {
if let Some(modified_since) = self.get(IF_MODIFIED_SINCE) { if let Some(modified_since) = self.get(IF_MODIFIED_SINCE) {
if let Ok(modified_since) = modified_since.to_str() { if let Ok(modified_since) = modified_since.to_str() {
let date = DateTime::parse_from_str( return strtime::parse(MODIFIED_SINCE_FORMAT, modified_since)
&format!("{modified_since} +00:00"), .unwrap()
&format!("{MODIFIED_SINCE_FORMAT} %z"), .to_datetime()
); .ok();
if let Ok(x) = date {
return Some(x.to_utc());
}
} }
} }
@@ -176,10 +174,7 @@ impl HeaderMapExtended for HeaderMap {
} }
fn insert_content_type_application_javascript(&mut self) { fn insert_content_type_application_javascript(&mut self) {
self.insert( self.insert(header::CONTENT_TYPE, "application/javascript".parse().unwrap());
header::CONTENT_TYPE,
"application/javascript".parse().unwrap(),
);
} }
fn insert_content_type_application_json(&mut self) { fn insert_content_type_application_json(&mut self) {
@@ -187,10 +182,7 @@ impl HeaderMapExtended for HeaderMap {
} }
fn insert_content_type_application_manifest_json(&mut self) { fn insert_content_type_application_manifest_json(&mut self) {
self.insert( self.insert(header::CONTENT_TYPE, "application/manifest+json".parse().unwrap());
header::CONTENT_TYPE,
"application/manifest+json".parse().unwrap(),
);
} }
fn insert_content_type_application_pdf(&mut self) { fn insert_content_type_application_pdf(&mut self) {
+5
View File
@@ -0,0 +1,5 @@
mod header_map;
mod response;
pub use header_map::*;
pub use response::*;
-41
View File
@@ -1,41 +0,0 @@
// Files are bigger than with SWC, to retest later
// Source: https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/examples/minifier.rs
use std::{fs, path::Path};
use oxc::{
allocator::Allocator,
codegen::{CodeGenerator, CodegenOptions},
minifier::{MinifierOptions, MinifierReturn},
parser::{Parser, ParserReturn},
span::SourceType,
};
//
pub fn minify_js(path: &Path) -> String {
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap();
let source_text = fs::read_to_string(path).unwrap();
let ParserReturn { mut program, .. } =
Parser::new(&allocator, &source_text, source_type).parse();
let minifier = oxc::minifier::Minifier::new(MinifierOptions::default());
let MinifierReturn { mangler } = minifier.build(&allocator, &mut program);
CodeGenerator::new()
.with_options(CodegenOptions {
single_quote: false,
minify: true,
comments: false,
annotation_comments: false,
source_map_path: None,
})
.with_mangler(mangler)
.build(&program)
.code
}
+12 -20
View File
@@ -10,18 +10,17 @@ use axum::{
http::HeaderMap, http::HeaderMap,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use log::{error, info}; use logger::{error, info};
use reqwest::StatusCode; use reqwest::StatusCode;
use crate::server::{ use crate::{
header_map::{HeaderMapExtended, Modified},
log_result, log_result,
response::ResponseExtended, traits::{HeaderMapExtended, ModifiedState, ResponseExtended},
}; };
use super::minify_js; use super::minify_js;
const WEBSITE_PATH: &str = "./src/website/"; const WEBSITE_DEV_PATH: &str = "../website/";
pub async fn file_handler(headers: HeaderMap, path: extract::Path<String>) -> Response { pub async fn file_handler(headers: HeaderMap, path: extract::Path<String>) -> Response {
any_handler(headers, Some(path)) any_handler(headers, Some(path))
@@ -41,11 +40,8 @@ fn any_handler(headers: HeaderMap, path: Option<extract::Path<String>>) -> Respo
if !path.exists() { if !path.exists() {
if path.extension().is_some() { if path.extension().is_some() {
let mut response: Response<Body> = ( let mut response: Response<Body> =
StatusCode::INTERNAL_SERVER_ERROR, (StatusCode::INTERNAL_SERVER_ERROR, "File doesn't exist".to_string()).into_response();
"File doesn't exist".to_string(),
)
.into_response();
response.headers_mut().insert_cors(); response.headers_mut().insert_cors();
@@ -70,11 +66,10 @@ fn any_handler(headers: HeaderMap, path: Option<extract::Path<String>>) -> Respo
} }
fn path_to_response(headers: &HeaderMap, path: &Path) -> Response { fn path_to_response(headers: &HeaderMap, path: &Path) -> Response {
match _path_to_response(headers, path) { match path_to_response_(headers, path) {
Ok(response) => response, Ok(response) => response,
Err(error) => { Err(error) => {
let mut response = let mut response = (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
response.headers_mut().insert_cors(); response.headers_mut().insert_cors();
@@ -83,9 +78,9 @@ fn path_to_response(headers: &HeaderMap, path: &Path) -> Response {
} }
} }
fn _path_to_response(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Response> { fn path_to_response_(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Response> {
let (modified, date) = headers.check_if_modified_since(path)?; let (modified, date) = headers.check_if_modified_since(path)?;
if modified == Modified::NotModifiedSince { if modified == ModifiedState::NotModifiedSince {
return Ok(Response::new_not_modified()); return Ok(Response::new_not_modified());
} }
@@ -124,10 +119,7 @@ fn _path_to_response(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Res
|| serialized_path.contains("assets/") || serialized_path.contains("assets/")
|| serialized_path.contains("packages/") || serialized_path.contains("packages/")
|| path.extension().is_some_and(|extension| { || path.extension().is_some_and(|extension| {
extension == "pdf" extension == "pdf" || extension == "jpg" || extension == "png" || extension == "woff2"
|| extension == "jpg"
|| extension == "png"
|| extension == "woff2"
}) })
{ {
headers.insert_cache_control_immutable(); headers.insert_cache_control_immutable();
@@ -140,5 +132,5 @@ fn _path_to_response(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Res
} }
fn str_to_path(path: &str) -> PathBuf { fn str_to_path(path: &str) -> PathBuf {
PathBuf::from(&format!("{WEBSITE_PATH}{path}")) PathBuf::from(&format!("{WEBSITE_DEV_PATH}{path}"))
} }
+34 -13
View File
@@ -1,22 +1,43 @@
// Simplified version of: https://github.com/swc-project/swc/blob/main/crates/swc/examples/minify.rs // Source: https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/examples/minifier.rs
use std::{path::Path, sync::Arc}; use std::{fs, path::Path};
use swc::{config::JsMinifyOptions, try_with_handler, JsMinifyExtras}; use oxc::{
use swc_common::{SourceMap, GLOBALS}; allocator::Allocator,
codegen::{CodeGenerator, CodegenOptions, LegalComment},
minifier::{CompressOptions, MangleOptions, Minifier, MinifierOptions},
parser::Parser,
span::SourceType,
};
//
pub fn minify_js(path: &Path) -> String { pub fn minify_js(path: &Path) -> String {
let source_map = Arc::<SourceMap>::default(); let allocator = Allocator::default();
let compiler = swc::Compiler::new(source_map.clone());
GLOBALS let source_type = SourceType::from_path(path).unwrap();
.set(&Default::default(), || {
try_with_handler(source_map.clone(), Default::default(), |handler| {
let fm = source_map.load_file(path).expect("failed to load file");
compiler.minify(fm, handler, &JsMinifyOptions::default(), JsMinifyExtras::default()) let source_text = fs::read_to_string(path).unwrap();
})
let parser_return = Parser::new(&allocator, &source_text, source_type).parse();
let mut program = parser_return.program;
let minifier_return = Minifier::new(MinifierOptions {
mangle: Some(MangleOptions::default()),
compress: Some(CompressOptions::default()),
})
.build(&allocator, &mut program);
CodeGenerator::new()
.with_options(CodegenOptions {
minify: true,
single_quote: false,
comments: false,
annotation_comments: false,
source_map_path: None,
legal_comments: LegalComment::None,
}) })
.unwrap() .with_symbol_table(minifier_return.symbol_table)
.build(&program)
.code .code
} }
+1 -2
View File
@@ -12,7 +12,6 @@ pub trait WebsiteRoutes {
impl WebsiteRoutes for Router<AppState> { impl WebsiteRoutes for Router<AppState> {
fn add_website_routes(self) -> Self { fn add_website_routes(self) -> Self {
self.route("/*path", get(file_handler)) self.route("/{*path}", get(file_handler)).route("/", get(index_handler))
.route("/", get(index_handler))
} }
} }