compress html
This commit is contained in:
parent
ed3195afeb
commit
04deb1d89c
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -137,7 +137,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.6.2",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
@ -203,10 +203,12 @@ name = "centralex"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"bytes",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
"css-minify",
|
"css-minify",
|
||||||
"eyre",
|
"eyre",
|
||||||
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"hyper",
|
"hyper",
|
||||||
"minify-html",
|
"minify-html",
|
||||||
@ -368,12 +370,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.25"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.7.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -725,6 +727,15 @@ dependencies = [
|
|||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@ -1209,9 +1220,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.7"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -26,14 +26,16 @@ color-eyre = "0.6.2"
|
|||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
zerocopy = "0.6.1"
|
zerocopy = "0.6.1"
|
||||||
tokio-stream = { version = "0.1.14", features = ["sync"] }
|
tokio-stream = { version = "0.1.14", features = ["sync"] }
|
||||||
minify-html = "0.11.1"
|
flate2 = { version = "1.0.26", optional = true }
|
||||||
|
bytes = "1.4.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
minify-js = { version = "0.5.6", optional = true }
|
minify-js = { version = "0.5.6", optional = true }
|
||||||
minify-html = { version = "0.11.1", optional = true }
|
minify-html = { version = "0.11.1", optional = true }
|
||||||
css-minify = { version = "0.3.1", optional = true }
|
css-minify = { version = "0.3.1", optional = true }
|
||||||
|
flate2 = { version = "1.0.26", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["debug_server"]
|
default = ["debug_server"]
|
||||||
debug_server = ["dep:hyper", "minify-html", "dep:minify-js", "dep:css-minify"]
|
debug_server = ["dep:hyper", "minify-html", "dep:minify-js", "dep:css-minify", "dep:flate2"]
|
||||||
tokio_console = ["dep:console-subscriber"]
|
tokio_console = ["dep:console-subscriber"]
|
||||||
|
9
build.rs
9
build.rs
@ -9,6 +9,7 @@ fn main() {
|
|||||||
|
|
||||||
#[cfg(feature = "debug_server")]
|
#[cfg(feature = "debug_server")]
|
||||||
fn pack_debug_page() -> Result<(), Box<dyn std::error::Error>> {
|
fn pack_debug_page() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
use flate2::{write::GzEncoder, Compression};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use css_minify::optimizations::{Level, Minifier};
|
use css_minify::optimizations::{Level, Minifier};
|
||||||
@ -38,8 +39,12 @@ fn pack_debug_page() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
&minify_html::Cfg::spec_compliant(),
|
&minify_html::Cfg::spec_compliant(),
|
||||||
);
|
);
|
||||||
|
|
||||||
std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/minified.html")?
|
let mut encoder = GzEncoder::new(
|
||||||
.write_all(&html)?;
|
std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/minified.html.gz")?,
|
||||||
|
Compression::best(),
|
||||||
|
);
|
||||||
|
|
||||||
|
encoder.write_all(&html)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
168
src/http.rs
168
src/http.rs
@ -1,15 +1,17 @@
|
|||||||
|
use bytes::BytesMut;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use tokio_stream::StreamExt;
|
use hyper::header::{ACCEPT_ENCODING, CACHE_CONTROL, CONTENT_ENCODING, CONTENT_TYPE};
|
||||||
|
|
||||||
use hyper::rt::Executor;
|
use hyper::rt::Executor;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::{Body, Method, Response, Server, StatusCode};
|
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::io::Read;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_stream::wrappers::{IntervalStream, WatchStream};
|
use tokio_stream::wrappers::{IntervalStream, WatchStream};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use zerocopy::{AsBytes, FromBytes, LittleEndian, Unaligned};
|
use zerocopy::{AsBytes, FromBytes, LittleEndian, Unaligned};
|
||||||
@ -30,6 +32,117 @@ impl<T: Send + 'static, Fut: Future<Output = T> + Send + 'static> Executor<Fut>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COMPRESSED_HTML: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/minified.html.gz"));
|
||||||
|
|
||||||
|
async fn index(req: &Request<Body>) -> Result<Response<Body>, hyper::http::Error> {
|
||||||
|
let response = Response::builder();
|
||||||
|
|
||||||
|
let accepts_gzip = req
|
||||||
|
.headers()
|
||||||
|
.get(ACCEPT_ENCODING)
|
||||||
|
.map_or(false, |accept_encoding| {
|
||||||
|
accept_encoding
|
||||||
|
.as_bytes()
|
||||||
|
.split(|x| *x == b',')
|
||||||
|
.filter_map(|x| x.split(|x| *x == b';').next())
|
||||||
|
.filter_map(|x| std::str::from_utf8(x).ok())
|
||||||
|
.any(|x| x.trim() == "gzip")
|
||||||
|
});
|
||||||
|
|
||||||
|
if accepts_gzip {
|
||||||
|
response
|
||||||
|
.header(CONTENT_ENCODING, "gzip")
|
||||||
|
.body(Body::from(COMPRESSED_HTML))
|
||||||
|
} else {
|
||||||
|
let (mut sender, body) = Body::channel();
|
||||||
|
|
||||||
|
spawn("gunzip task", async move {
|
||||||
|
let mut decoder =
|
||||||
|
flate2::bufread::GzDecoder::new(std::io::Cursor::new(COMPRESSED_HTML));
|
||||||
|
|
||||||
|
let mut done = false;
|
||||||
|
while !done {
|
||||||
|
let mut chunk = BytesMut::zeroed(256);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let dst = &mut chunk.as_bytes_mut()[i..];
|
||||||
|
|
||||||
|
if dst.is_empty() {
|
||||||
|
break; // we are done
|
||||||
|
}
|
||||||
|
|
||||||
|
match decoder.read(dst) {
|
||||||
|
Ok(n) => {
|
||||||
|
if n == 0 {
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += n;
|
||||||
|
}
|
||||||
|
Err(err) => unreachable!("failed to read from gzip decode: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.truncate(i);
|
||||||
|
|
||||||
|
if sender.send_data(chunk.freeze()).await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
response.body(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn data(
|
||||||
|
_req: &Request<Body>,
|
||||||
|
port_handler: Arc<Mutex<PortHandler>>,
|
||||||
|
) -> Result<Response<Body>, hyper::http::Error> {
|
||||||
|
let res = Response::builder().header(CACHE_CONTROL, "no-store");
|
||||||
|
|
||||||
|
match serde_json::to_string(&*port_handler.lock().await) {
|
||||||
|
Ok(data) => res
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from(data)),
|
||||||
|
Err(err) => {
|
||||||
|
error!(%err, "failed to serialize data for debug server");
|
||||||
|
res.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.body(Body::from(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn events(
|
||||||
|
_req: &Request<Body>,
|
||||||
|
change_receiver: tokio::sync::watch::Receiver<std::time::Instant>,
|
||||||
|
) -> Result<Response<Body>, hyper::http::Error> {
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header(CACHE_CONTROL, "no-store")
|
||||||
|
.header(CONTENT_TYPE, "text/event-stream")
|
||||||
|
.body(Body::wrap_stream({
|
||||||
|
WatchStream::new(change_receiver)
|
||||||
|
.map(|x| ("change", x))
|
||||||
|
.merge(
|
||||||
|
IntervalStream::new(tokio::time::interval(DEBUG_SERVER_PING_INTERVAL))
|
||||||
|
.map(|x| ("ping", x.into_std())),
|
||||||
|
)
|
||||||
|
.filter_map(|(kind, time)| {
|
||||||
|
let timestamp = (SystemTime::now() + time.elapsed())
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.ok()?
|
||||||
|
.as_secs();
|
||||||
|
|
||||||
|
Some(Ok::<_, Infallible>(format!(
|
||||||
|
"event:{kind}\ndata: {timestamp}\n\n"
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn debug_server(
|
pub async fn debug_server(
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
port_handler: Arc<Mutex<PortHandler>>,
|
port_handler: Arc<Mutex<PortHandler>>,
|
||||||
@ -43,52 +156,13 @@ pub async fn debug_server(
|
|||||||
async move {
|
async move {
|
||||||
Ok::<_, Infallible>(service_fn(move |req| {
|
Ok::<_, Infallible>(service_fn(move |req| {
|
||||||
let port_handler = port_handler.clone();
|
let port_handler = port_handler.clone();
|
||||||
let change_receiver = WatchStream::new(change_receiver.clone());
|
let change_receiver = change_receiver.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
match (req.method(), req.uri().path()) {
|
match (req.method(), req.uri().path()) {
|
||||||
(&Method::GET, "/") => Ok(Response::new(Body::from(include_str!(
|
(&Method::GET, "/") => index(&req).await,
|
||||||
concat!(env!("OUT_DIR"), "/minified.html")
|
(&Method::GET, "/data") => data(&req, port_handler).await,
|
||||||
)))),
|
(&Method::GET, "/events") => events(&req, change_receiver),
|
||||||
|
|
||||||
(&Method::GET, "/data") => {
|
|
||||||
let res = Response::builder().header("Cache-Control", "no-store");
|
|
||||||
|
|
||||||
match serde_json::to_string(&*port_handler.lock().await) {
|
|
||||||
Ok(data) => res
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(Body::from(data)),
|
|
||||||
Err(err) => {
|
|
||||||
error!(%err, "failed to serialize data for debug server");
|
|
||||||
res.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
.body(Body::from(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(&Method::GET, "/events") => Response::builder()
|
|
||||||
.status(StatusCode::OK)
|
|
||||||
.header("Cache-Control", "no-store")
|
|
||||||
.header("Content-Type", "text/event-stream")
|
|
||||||
.body(Body::wrap_stream({
|
|
||||||
change_receiver
|
|
||||||
.map(|x| ("change", x))
|
|
||||||
.merge(
|
|
||||||
IntervalStream::new(tokio::time::interval(
|
|
||||||
DEBUG_SERVER_PING_INTERVAL,
|
|
||||||
))
|
|
||||||
.map(|x| ("ping", x.into_std())),
|
|
||||||
)
|
|
||||||
.filter_map(|(kind, time)| {
|
|
||||||
let timestamp = (SystemTime::now() + time.elapsed())
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.ok()?
|
|
||||||
.as_secs();
|
|
||||||
|
|
||||||
Some(Ok::<_, Infallible>(format!(
|
|
||||||
"event:{kind}\ndata: {timestamp}\n\n"
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
_ => Response::builder()
|
_ => Response::builder()
|
||||||
.status(StatusCode::NOT_FOUND)
|
.status(StatusCode::NOT_FOUND)
|
||||||
.body(Body::empty()),
|
.body(Body::empty()),
|
||||||
|
Loading…
Reference in New Issue
Block a user