compress html
This commit is contained in:
168
src/http.rs
168
src/http.rs
@@ -1,15 +1,17 @@
|
||||
use bytes::BytesMut;
|
||||
use futures::Future;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use hyper::header::{ACCEPT_ENCODING, CACHE_CONTROL, CONTENT_ENCODING, CONTENT_TYPE};
|
||||
use hyper::rt::Executor;
|
||||
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::io::Read;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_stream::wrappers::{IntervalStream, WatchStream};
|
||||
use tokio_stream::StreamExt;
|
||||
use tracing::error;
|
||||
|
||||
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(
|
||||
addr: SocketAddr,
|
||||
port_handler: Arc<Mutex<PortHandler>>,
|
||||
@@ -43,52 +156,13 @@ pub async fn debug_server(
|
||||
async move {
|
||||
Ok::<_, Infallible>(service_fn(move |req| {
|
||||
let port_handler = port_handler.clone();
|
||||
let change_receiver = WatchStream::new(change_receiver.clone());
|
||||
let change_receiver = change_receiver.clone();
|
||||
|
||||
async move {
|
||||
match (req.method(), req.uri().path()) {
|
||||
(&Method::GET, "/") => Ok(Response::new(Body::from(include_str!(
|
||||
concat!(env!("OUT_DIR"), "/minified.html")
|
||||
)))),
|
||||
|
||||
(&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"
|
||||
)))
|
||||
})
|
||||
})),
|
||||
(&Method::GET, "/") => index(&req).await,
|
||||
(&Method::GET, "/data") => data(&req, port_handler).await,
|
||||
(&Method::GET, "/events") => events(&req, change_receiver),
|
||||
_ => Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::empty()),
|
||||
|
||||
Reference in New Issue
Block a user