This commit is contained in:
nora 2022-03-06 16:31:22 +01:00
parent d7b574cef4
commit 85ef2aa7fd
6 changed files with 395 additions and 52 deletions

145
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -43,12 +49,16 @@ name = "amqp_dashboard"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"amqp_core", "amqp_core",
"anyhow",
"axum", "axum",
"mime_guess",
"serde", "serde",
"tokio", "tokio",
"tower", "tower",
"tower-http", "tower-http",
"tracing", "tracing",
"walkdir",
"zip",
] ]
[[package]] [[package]]
@ -125,9 +135,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.4.5" version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbbc81d15ddf33148615b778836b525dbae4e0731710294b2c484e80c4858f7" checksum = "c9f346c92c1e9a71d14fe4aaf7c2a5d9932cc4e5e48d8fb6641524416eb79ddd"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
@ -147,7 +157,6 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-util 0.6.9",
"tower", "tower",
"tower-http", "tower-http",
"tower-layer", "tower-layer",
@ -192,12 +201,39 @@ version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "bzip2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.2.7" version = "0.2.7"
@ -207,6 +243,12 @@ dependencies = [
"rustc_version", "rustc_version",
] ]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -254,6 +296,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.3.5" version = "0.3.5"
@ -362,6 +413,18 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "flate2"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -636,12 +699,32 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.0" version = "0.8.0"
@ -785,6 +868,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.1" version = "0.3.1"
@ -1174,6 +1263,16 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "tinytemplate" name = "tinytemplate"
version = "1.2.1" version = "1.2.1"
@ -1215,20 +1314,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tokio-util"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.0" version = "0.7.0"
@ -1254,7 +1339,7 @@ dependencies = [
"pin-project", "pin-project",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tokio-util 0.7.0", "tokio-util",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
@ -1274,6 +1359,7 @@ dependencies = [
"http-body", "http-body",
"http-range-header", "http-range-header",
"pin-project-lite", "pin-project-lite",
"tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]
@ -1359,6 +1445,15 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.9" version = "0.1.9"
@ -1570,3 +1665,17 @@ dependencies = [
"itertools", "itertools",
"strong-xml", "strong-xml",
] ]
[[package]]
name = "zip"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
dependencies = [
"byteorder",
"bzip2",
"crc32fast",
"flate2",
"thiserror",
"time",
]

View file

@ -7,9 +7,17 @@ edition = "2021"
[dependencies] [dependencies]
amqp_core = { path = "../amqp_core" } amqp_core = { path = "../amqp_core" }
axum = "0.4.5" anyhow = "1.0.55"
axum = "0.4.8"
mime_guess = "2.0.4"
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
tokio = { version = "1.17.0", features = ["full"] } tokio = { version = "1.17.0", features = ["full"] }
tower = "0.4.12" tower = "0.4.12"
tower-http = { version = "0.2.3", features = ["cors"] } tower-http = { version = "0.2.3", features = ["cors"] }
tracing = "0.1.31" tracing = "0.1.31"
zip = "0.5.13"
[build-dependencies]
anyhow = "1.0.55"
walkdir = "2.3.2"
zip = "0.5.13"

66
amqp_dashboard/build.rs Normal file
View file

@ -0,0 +1,66 @@
use anyhow::{ensure, Context, Result};
use std::{
env,
fs::File,
path::{Path, PathBuf},
process::Command,
};
use walkdir::WalkDir;
use zip::{write::FileOptions, ZipWriter};
// inspired by https://fasterthanli.me/series/dont-shell-out/part-8
fn main() -> Result<()> {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let frontend_dir = PathBuf::from(manifest_dir).join("frontend");
// this is not always fully correct, but we need to ignore `build` from the rerun or else it
// will always trigger itself
println!(
"cargo:rerun-if-changed={}",
frontend_dir.join("src").display()
);
build_frontend(&frontend_dir)
}
fn build_frontend(path: &Path) -> Result<()> {
let status = Command::new("yarn")
.arg("build")
.current_dir(path)
.status()
.context("run yarn build")?;
ensure!(status.success(), "Failed to build frontend");
let yarn_build_dir = path.join("build");
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let zip_path = out_dir.join("frontend.zip");
let mut zw = ZipWriter::new(File::create(&zip_path).unwrap());
for entry in WalkDir::new(&yarn_build_dir) {
let entry = entry.context("walk build directory")?;
let disk_path = entry.path();
let rel_path = disk_path
.strip_prefix(&yarn_build_dir)
.unwrap()
.to_string_lossy();
let meta = entry.metadata().context("entry metadata")?;
if meta.is_dir() {
zw.add_directory(rel_path, FileOptions::default()).unwrap();
} else if meta.is_file() {
zw.start_file(rel_path, Default::default()).unwrap();
std::io::copy(&mut File::open(disk_path).unwrap(), &mut zw).unwrap();
} else {
println!("cargo:warning=Ignoring entry {}", disk_path.display());
}
}
Ok(())
}

View file

@ -0,0 +1,149 @@
use axum::{
body::Body,
http::{header, Request, Response, StatusCode},
};
use mime_guess::mime;
use std::{
collections::HashMap,
fmt::{Debug, Formatter},
future,
io::Cursor,
path::Path,
task::{Context, Poll},
};
use tracing::trace;
use zip::ZipArchive;
#[derive(Debug, Clone)]
enum StaticFileKind {
File { mime: mime::Mime },
Directory,
}
#[derive(Clone)]
struct StaticFile {
data: Vec<u8>,
kind: StaticFileKind,
}
impl Debug for StaticFile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StaticFile")
.field("kind", &self.kind)
.field("data", &format!("[{} bytes]", self.data.len()))
.finish()
}
}
#[derive(Debug, Clone)]
pub struct StaticFileService {
files: HashMap<String, StaticFile>,
}
impl StaticFileService {
pub fn new(zip: &[u8]) -> Self {
let mut archive = ZipArchive::new(Cursor::new(zip)).unwrap();
let mut files = HashMap::with_capacity(archive.len());
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let mut data = Vec::with_capacity(usize::try_from(file.size()).unwrap());
std::io::copy(&mut file, &mut data).unwrap();
trace!(name = %file.name(), size = %file.size(),"Unpacking dashboard frontend file");
let path = Path::new(file.name());
let kind = if file.is_dir() {
StaticFileKind::Directory
} else {
let mime = match path.extension() {
Some(ext) => {
mime_guess::from_ext(&ext.to_string_lossy()).first_or_octet_stream()
}
None => mime::APPLICATION_OCTET_STREAM,
};
StaticFileKind::File { mime }
};
files.insert(
file.name().trim_start_matches('/').to_owned(),
StaticFile { data, kind },
);
}
trace!(?files, "Created StaticFileService");
Self { files }
}
fn call_inner(&mut self, req: Request<Body>) -> Result<Response<Body>, anyhow::Error> {
trace!(?req, "Got request for static file");
let path = req.uri().path().trim_start_matches('/');
let entry = self.files.get(path);
match entry {
Some(file) => {
if let StaticFileKind::File { mime } = &file.kind {
trace!(%path, "Found file");
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime.essence_str())
.body(Body::from(file.data.clone()))?)
} else {
trace!(%path, "Found directory, trying to append index.html");
let new_path = if path.is_empty() {
"index.html".to_owned()
} else {
let new_path = path.trim_end_matches('/');
format!("{}/index.html", new_path)
};
match self.files.get(&new_path) {
Some(file) => {
trace!(%new_path, "Found index.html");
if let StaticFileKind::File { mime } = &file.kind {
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime.essence_str())
.body(Body::from(file.data.clone()))?)
} else {
unreachable!()
}
}
None => {
trace!(%new_path, "Did not find index.html");
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())?)
}
}
}
}
None => {
trace!(%path, "Did not find file");
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())?)
}
}
}
}
impl tower::Service<Request<Body>> for StaticFileService {
type Response = Response<Body>;
type Error = anyhow::Error;
type Future = future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
future::ready(self.call_inner(req))
}
}

View file

@ -1,31 +1,42 @@
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
mod archive;
use crate::archive::StaticFileService;
use amqp_core::GlobalData; use amqp_core::GlobalData;
use axum::{ use axum::{
body::{boxed, Full}, http::{Method, StatusCode},
http::Method, response::IntoResponse,
response::{Html, IntoResponse, Response}, routing::{get, get_service},
routing::get,
Json, Router, Json, Router,
}; };
use serde::Serialize; use serde::Serialize;
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::{Any, CorsLayer};
use tracing::info; use tracing::{error, info};
const INDEX_HTML: &str = include_str!("../assets/index.html"); // const INDEX_HTML: &str = include_str!("../assets/index.html");
const SCRIPT_JS: &str = include_str!("../assets/script.js"); // const SCRIPT_JS: &str = include_str!("../assets/script.js");
const STYLE_CSS: &str = include_str!("../assets/style.css"); // const STYLE_CSS: &str = include_str!("../assets/style.css");
const DATA_ZIP: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/frontend.zip"));
pub async fn dashboard(global_data: GlobalData) { pub async fn dashboard(global_data: GlobalData) {
let cors = CorsLayer::new() let cors = CorsLayer::new()
.allow_methods(vec![Method::GET]) .allow_methods(vec![Method::GET])
.allow_origin(Any); .allow_origin(Any);
let static_file_service =
get_service(StaticFileService::new(DATA_ZIP)).handle_error(|error| async move {
error!(?error, "Error in static file service");
StatusCode::INTERNAL_SERVER_ERROR
});
let app = Router::new() let app = Router::new()
.route("/", get(get_index_html)) //.route("/", get(get_index_html))
.route("/script.js", get(get_script_js)) //.route("/script.js", get(get_script_js))
.route("/style.css", get(get_style_css)) //.route("/style.css", get(get_style_css))
.route("/api/data", get(move || get_data(global_data)).layer(cors)); .route("/api/data", get(move || get_data(global_data)).layer(cors))
.fallback(static_file_service);
let socket_addr = "0.0.0.0:8080".parse().unwrap(); let socket_addr = "0.0.0.0:8080".parse().unwrap();
@ -37,24 +48,24 @@ pub async fn dashboard(global_data: GlobalData) {
.unwrap(); .unwrap();
} }
async fn get_index_html() -> impl IntoResponse { //async fn get_index_html() -> impl IntoResponse {
info!("Requesting index.html"); // info!("Requesting index.html");
Html(INDEX_HTML) // Html(INDEX_HTML)
} //}
//
async fn get_script_js() -> Response { //async fn get_script_js() -> Response {
Response::builder() // Response::builder()
.header("content-type", "application/javascript") // .header("content-type", "application/javascript")
.body(boxed(Full::from(SCRIPT_JS))) // .body(boxed(Full::from(SCRIPT_JS)))
.unwrap() // .unwrap()
} //}
//
async fn get_style_css() -> Response { //async fn get_style_css() -> Response {
Response::builder() // Response::builder()
.header("content-type", "text/css") // .header("content-type", "text/css")
.body(boxed(Full::from(STYLE_CSS))) // .body(boxed(Full::from(STYLE_CSS)))
.unwrap() // .unwrap()
} //}
#[derive(Serialize)] #[derive(Serialize)]
struct Data { struct Data {

View file

@ -6,7 +6,7 @@ const channel = await connection.createChannel();
console.log('Successfully opened channel'); console.log('Successfully opened channel');
//await channel.close(); await channel.close();
//await connection.close(); await connection.close();
//
console.log('Successfully shut down connection'); console.log('Successfully shut down connection');