Skip to content

Commit 8c1ea00

Browse files
committed
add app w/ brotli support
1 parent b8f7401 commit 8c1ea00

27 files changed

+452
-1
lines changed

src/server.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ use std::sync::Mutex;
99
use axum::{
1010
extract::{Path, Query, State},
1111
http::StatusCode,
12-
response::{IntoResponse, Redirect, Response},
12+
response::{Html, IntoResponse, Redirect, Response},
1313
Router,
1414
routing::get,
1515
};
1616
use clap::Parser;
17+
use include_dir::{Dir, include_dir};
1718
use pflow_metamodel::compression::unzip_encoded;
1819
use pflow_metamodel::oid;
1920
use pflow_metamodel::petri_net::PetriNet;
@@ -160,13 +161,30 @@ async fn index_handler(
160161
return index_response(zblob.ipfs_cid, zblob.base64_zipped).into_response();
161162
}
162163

164+
const STATIC_DIR: Dir = include_dir!("static");
165+
async fn serve_static(filepath: Path<String>) -> impl IntoResponse {
166+
let filepath = if filepath.as_str().is_empty() {
167+
"index.html".to_string()
168+
} else {
169+
(*filepath).clone()
170+
};
171+
172+
tracing::info!("Serving static file: {:?}", filepath);
173+
174+
match STATIC_DIR.get_file(&filepath) {
175+
Some(file) => Html::<axum::body::Body>(file.contents_utf8().unwrap().into()),
176+
None => Html::<axum::body::Body>("404 Not Found".into()),
177+
}
178+
}
179+
163180
pub fn app() -> Router {
164181
let store = Storage::new("pflow.db").unwrap();
165182
store.create_tables().unwrap();
166183
let state: Arc<Mutex<Storage>> = Arc::new(Mutex::new(store));
167184

168185
// Build route service
169186
Router::new()
187+
.route("/p/:filepath", get(serve_static)) // serve static files
170188
.route("/img/:ipfs_cid.svg", get(img_handler))
171189
.route("/src/:ipfs_cid.json", get(src_handler))
172190
.route("/p/:ipfs_cid/", get(model_handler))

src/server.rs_

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use std::{
2+
collections::HashMap,
3+
net::{Ipv4Addr, SocketAddr},
4+
sync::{Arc, RwLock},
5+
time::Duration,
6+
};
7+
8+
use axum::{
9+
body::Bytes,
10+
extract::{Path, State},
11+
http::{header, HeaderValue, StatusCode},
12+
response::{Html, IntoResponse, Redirect},
13+
Router,
14+
routing::get,
15+
};
16+
use axum::routing::post;
17+
use clap::Parser;
18+
use include_dir::{Dir, include_dir};
19+
use tokio::net::TcpListener;
20+
use tower::ServiceBuilder;
21+
use tower_http::{
22+
LatencyUnit,
23+
ServiceBuilderExt,
24+
timeout::TimeoutLayer, trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer},
25+
};
26+
27+
/// Simple key/value store with an HTTP API
28+
#[derive(Debug, Parser)]
29+
struct Config {
30+
/// The port to listen on
31+
#[clap(short = 'p', long, default_value = "3000")]
32+
port: u16,
33+
}
34+
35+
#[derive(Clone, Debug)]
36+
struct AppState {
37+
db: Arc<RwLock<HashMap<String, Bytes>>>,
38+
}
39+
40+
#[tokio::main]
41+
async fn main() {
42+
// Setup tracing
43+
tracing_subscriber::fmt::init();
44+
45+
// Parse command line arguments
46+
let config = Config::parse();
47+
48+
// Run our service
49+
let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, config.port));
50+
tracing::info!("Listening on {}", addr);
51+
axum::serve(
52+
TcpListener::bind(addr).await.expect("bind error"),
53+
app().into_make_service(),
54+
)
55+
.await
56+
.expect("server error");
57+
}
58+
59+
const STATIC_DIR: Dir = include_dir!("static");
60+
61+
async fn serve_static(filepath: Path<String>) -> impl IntoResponse {
62+
let filepath = if filepath.as_str().is_empty() {
63+
"index.html".to_string()
64+
} else {
65+
(*filepath).clone()
66+
};
67+
68+
tracing::info!("Serving static file: {:?}", filepath);
69+
70+
match STATIC_DIR.get_file(&filepath) {
71+
Some(file) => Html::<axum::body::Body>(file.contents_utf8().unwrap().into()),
72+
None => Html::<axum::body::Body>("404 Not Found".into()),
73+
}
74+
}
75+
76+
pub(crate) fn app() -> Router {
77+
// Build our database for holding the key/value pairs
78+
let state = AppState {
79+
db: Arc::new(RwLock::new(HashMap::new())),
80+
};
81+
82+
let sensitive_headers: Arc<[_]> = vec![header::AUTHORIZATION, header::COOKIE].into();
83+
84+
// Build our middleware stack
85+
let middleware = ServiceBuilder::new()
86+
.layer(
87+
TraceLayer::new_for_http()
88+
.on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| {
89+
tracing::trace!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk")
90+
})
91+
.make_span_with(DefaultMakeSpan::new().include_headers(true))
92+
.on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)),
93+
)
94+
.layer(TimeoutLayer::new(Duration::from_secs(10)));
95+
96+
// Build route service
97+
Router::new()
98+
.route("/get/:key", get(get_key))
99+
.route("/set/:key", post(set_key))
100+
.route("/p/:filepath", get(serve_static)) // serve static files
101+
.route("/p/", get(|| serve_static(Path("index.html".to_string())))) // serve static files with default path
102+
.route("/p", get(|| async { Redirect::to("/p/") })) // redirect /p to /p/
103+
.route("/", get(|| async { Redirect::to("/p/") })) // redirect / to /p/
104+
.layer(middleware)
105+
.with_state(state)
106+
}
107+
108+
async fn get_key(path: Path<String>, state: State<AppState>) -> impl IntoResponse {
109+
let state = state.db.read().unwrap();
110+
111+
if let Some(value) = state.get(&*path).cloned() {
112+
Ok(value)
113+
} else {
114+
Err(StatusCode::NOT_FOUND)
115+
}
116+
}
117+
118+
async fn set_key(Path(path): Path<String>, state: State<AppState>, value: Bytes) {
119+
let mut state = state.db.write().unwrap();
120+
state.insert(path, value);
121+
}

static/UNLICENSE.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This is free and unencumbered software released into the public domain.
2+
3+
Anyone is free to copy, modify, publish, use, compile, sell, or
4+
distribute this software, either in source code form or as a compiled
5+
binary, for any purpose, commercial or non-commercial, and by any
6+
means.
7+
8+
In jurisdictions that recognize copyright laws, the author or authors
9+
of this software dedicate any and all copyright interest in the
10+
software to the public domain. We make this dedication for the benefit
11+
of the public at large and to the detriment of our heirs and
12+
successors. We intend this dedication to be an overt act of
13+
relinquishment in perpetuity of all present and future rights to this
14+
software under copyright law.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.
23+
24+
For more information, please refer to <https://unlicense.org>

static/android-chrome-192x192.png

8.41 KB
Loading

static/android-chrome-256x256.png

11.8 KB
Loading

static/apple-touch-icon.png

7.91 KB
Loading

static/asset-manifest.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"files": {
3+
"main.css": "/p/static/css/main.31cba778.css",
4+
"main.js": "/p/static/js/main.08efb24d.js",
5+
"static/media/brotli_wasm_bg.wasm": "/p/static/media/brotli_wasm_bg.0679dcc7b534fcfcac29.wasm",
6+
"index.html": "/p/index.html",
7+
"main.31cba778.css.map": "/p/static/css/main.31cba778.css.map",
8+
"main.08efb24d.js.map": "/p/static/js/main.08efb24d.js.map"
9+
},
10+
"entrypoints": [
11+
"static/css/main.31cba778.css",
12+
"static/js/main.08efb24d.js"
13+
]
14+
}

static/browserconfig.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<browserconfig>
3+
<msapplication>
4+
<tile>
5+
<square150x150logo src="/mstile-150x150.png"/>
6+
<TileColor>#da532c</TileColor>
7+
</tile>
8+
</msapplication>
9+
</browserconfig>

static/favicon-16x16.png

826 Bytes
Loading

static/favicon-32x32.png

1.4 KB
Loading

0 commit comments

Comments
 (0)