mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-12-07 16:22:31 -05:00
bundle UI files into the binary
This is optional but now enabled for release builds. Why? * It shrinks the release docker images a bit, as the binary includes only the gzipped version of files and uncompressed into RAM at startup (which should be fast). * It's a step toward #160.
This commit is contained in:
@@ -17,6 +17,7 @@ use self::path::Path;
|
||||
use crate::body::Body;
|
||||
use crate::json;
|
||||
use crate::mp4;
|
||||
use crate::web::static_file::Ui;
|
||||
use base::err;
|
||||
use base::Error;
|
||||
use base::ResultExt;
|
||||
@@ -28,7 +29,6 @@ use db::{auth, recording};
|
||||
use fnv::FnvHashMap;
|
||||
use http::header::{self, HeaderValue};
|
||||
use http::{status::StatusCode, Request, Response};
|
||||
use http_serve::dir::FsDir;
|
||||
use hyper::body::Bytes;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
@@ -162,7 +162,7 @@ fn require_csrf_if_session(caller: &Caller, csrf: Option<&str>) -> Result<(), ba
|
||||
|
||||
pub struct Config<'a> {
|
||||
pub db: Arc<db::Database>,
|
||||
pub ui_dir: Option<&'a std::path::Path>,
|
||||
pub ui_dir: Option<&'a crate::cmds::run::config::UiDir>,
|
||||
pub trust_forward_hdrs: bool,
|
||||
pub time_zone_name: String,
|
||||
pub allow_unauthenticated_permissions: Option<db::Permissions>,
|
||||
@@ -171,7 +171,7 @@ pub struct Config<'a> {
|
||||
|
||||
pub struct Service {
|
||||
db: Arc<db::Database>,
|
||||
ui_dir: Option<Arc<FsDir>>,
|
||||
ui: Ui,
|
||||
dirs_by_stream_id: Arc<FnvHashMap<i32, Arc<SampleFileDir>>>,
|
||||
time_zone_name: String,
|
||||
allow_unauthenticated_permissions: Option<db::Permissions>,
|
||||
@@ -195,19 +195,7 @@ enum CacheControl {
|
||||
|
||||
impl Service {
|
||||
pub fn new(config: Config) -> Result<Self, Error> {
|
||||
let mut ui_dir = None;
|
||||
if let Some(d) = config.ui_dir {
|
||||
match FsDir::builder().for_path(d) {
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Unable to load ui dir {}; will serve no static files: {}",
|
||||
d.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(d) => ui_dir = Some(d),
|
||||
};
|
||||
}
|
||||
let ui_dir = config.ui_dir.map(Ui::from).unwrap_or(Ui::None);
|
||||
let dirs_by_stream_id = {
|
||||
let l = config.db.lock();
|
||||
let mut d =
|
||||
@@ -225,7 +213,7 @@ impl Service {
|
||||
Ok(Service {
|
||||
db: config.db,
|
||||
dirs_by_stream_id,
|
||||
ui_dir,
|
||||
ui: ui_dir,
|
||||
allow_unauthenticated_permissions: config.allow_unauthenticated_permissions,
|
||||
trust_forward_hdrs: config.trust_forward_hdrs,
|
||||
time_zone_name: config.time_zone_name,
|
||||
|
||||
@@ -4,45 +4,104 @@
|
||||
|
||||
//! Static file serving.
|
||||
|
||||
use base::{bail, err, Error, ErrorKind, ResultExt};
|
||||
use std::sync::Arc;
|
||||
|
||||
use base::{bail, err, ErrorKind, ResultExt};
|
||||
use http::{header, HeaderValue, Request};
|
||||
use http_serve::dir::FsDir;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::cmds::run::config::UiDir;
|
||||
|
||||
use super::{ResponseResult, Service};
|
||||
|
||||
pub enum Ui {
|
||||
None,
|
||||
FromFilesystem(Arc<FsDir>),
|
||||
#[cfg(feature = "bundled-ui")]
|
||||
Bundled(&'static crate::bundled_ui::Ui),
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn from(cfg: &UiDir) -> Self {
|
||||
match cfg {
|
||||
UiDir::FromFilesystem(d) => match FsDir::builder().for_path(d) {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
%err,
|
||||
"unable to load ui dir {}; will serve no static files",
|
||||
d.display(),
|
||||
);
|
||||
Self::None
|
||||
}
|
||||
Ok(d) => Self::FromFilesystem(d),
|
||||
},
|
||||
#[cfg(feature = "bundled-ui")]
|
||||
UiDir::Bundled(_) => Self::Bundled(crate::bundled_ui::Ui::get()),
|
||||
#[cfg(not(feature = "bundled-ui"))]
|
||||
UiDir::Bundled(_) => {
|
||||
warn!("server compiled without bundled ui; will serve not static files");
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve(
|
||||
&self,
|
||||
path: &str,
|
||||
req: &Request<hyper::Body>,
|
||||
cache_control: &'static str,
|
||||
content_type: &'static str,
|
||||
) -> ResponseResult {
|
||||
match self {
|
||||
Ui::None => bail!(
|
||||
NotFound,
|
||||
msg("ui not configured or missing; no static files available")
|
||||
),
|
||||
Ui::FromFilesystem(d) => {
|
||||
let node = d.clone().get(path, req.headers()).await.map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
err!(NotFound, msg("static file not found"))
|
||||
} else {
|
||||
err!(Internal, source(e))
|
||||
}
|
||||
})?;
|
||||
let mut hdrs = http::HeaderMap::new();
|
||||
node.add_encoding_headers(&mut hdrs);
|
||||
hdrs.insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static(cache_control),
|
||||
);
|
||||
hdrs.insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
|
||||
let e = node.into_file_entity(hdrs).err_kind(ErrorKind::Internal)?;
|
||||
Ok(http_serve::serve(e, &req))
|
||||
}
|
||||
#[cfg(feature = "bundled-ui")]
|
||||
Ui::Bundled(ui) => {
|
||||
let Some(e) = ui.lookup(path, req.headers(), cache_control, content_type) else {
|
||||
bail!(NotFound, msg("static file not found"));
|
||||
};
|
||||
Ok(http_serve::serve(e, &req))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Serves a static file if possible.
|
||||
pub(super) async fn static_file(&self, req: Request<hyper::Body>) -> ResponseResult {
|
||||
let Some(dir) = self.ui_dir.clone() else {
|
||||
bail!(NotFound, msg("ui dir not configured or missing; no static files available"))
|
||||
};
|
||||
let Some(static_req) = StaticFileRequest::parse(req.uri().path()) else {
|
||||
bail!(NotFound, msg("static file not found"));
|
||||
};
|
||||
let f = dir.get(static_req.path, req.headers());
|
||||
let node = f.await.map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
err!(NotFound, msg("no such static file"))
|
||||
} else {
|
||||
Error::wrap(ErrorKind::Internal, e)
|
||||
}
|
||||
})?;
|
||||
let mut hdrs = http::HeaderMap::new();
|
||||
node.add_encoding_headers(&mut hdrs);
|
||||
hdrs.insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static(if static_req.immutable {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Caching_static_assets
|
||||
"public, max-age=604800, immutable"
|
||||
} else {
|
||||
"public"
|
||||
}),
|
||||
);
|
||||
hdrs.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(static_req.mime),
|
||||
);
|
||||
let e = node.into_file_entity(hdrs).err_kind(ErrorKind::Internal)?;
|
||||
Ok(http_serve::serve(e, &req))
|
||||
let cache_control = if static_req.immutable {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Caching_static_assets
|
||||
"public, max-age=604800, immutable"
|
||||
} else {
|
||||
"public"
|
||||
};
|
||||
self.ui
|
||||
.serve(static_req.path, &req, cache_control, static_req.mime)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user