From 672a327ee248b9c17f5512d8c7ee9da6738b12f2 Mon Sep 17 00:00:00 2001 From: Scott Lamb Date: Sat, 3 Mar 2018 06:43:36 -0800 Subject: [PATCH] support serving Access-Control-Allow-Origin header (#19) support serving Access-Control-Allow-Origin header Closes #17. --- src/cmds/run.rs | 10 ++++++++-- src/error.rs | 6 ++++++ src/web.rs | 20 ++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/cmds/run.rs b/src/cmds/run.rs index bd69b6d..0d59b4d 100644 --- a/src/cmds/run.rs +++ b/src/cmds/run.rs @@ -58,11 +58,15 @@ Options: --sample-file-dir=DIR Set the directory holding video data. This is typically on a hard drive. [default: /var/lib/moonfire-nvr/sample] - --ui-dir=DIR Set the directory with the user interface files (.html, .js, etc). + --ui-dir=DIR Set the directory with the user interface files + (.html, .js, etc). [default: /usr/local/lib/moonfire-nvr/ui] --http-addr=ADDR Set the bind address for the unencrypted HTTP server. [default: 0.0.0.0:8080] --read-only Forces read-only mode / disables recording. + --allow-origin=ORIGIN If present, adds a Access-Control-Allow-Origin: + header to HTTP responses. This may be useful for + Javascript development. "#; #[derive(Debug, Deserialize)] @@ -72,6 +76,7 @@ struct Args { flag_http_addr: String, flag_ui_dir: String, flag_read_only: bool, + flag_allow_origin: Option, } fn setup_shutdown_future(h: &reactor::Handle) -> Box> { @@ -101,7 +106,8 @@ pub fn run() -> Result<(), Error> { let dir = dir::SampleFileDir::new(&args.flag_sample_file_dir, db.clone()).unwrap(); info!("Database is loaded."); - let s = web::Service::new(db.clone(), dir.clone(), Some(&args.flag_ui_dir), resolve_zone())?; + let s = web::Service::new(db.clone(), dir.clone(), Some(&args.flag_ui_dir), + args.flag_allow_origin, resolve_zone())?; // Start a streamer for each camera. let shutdown_streamers = Arc::new(AtomicBool::new(false)); diff --git a/src/error.rs b/src/error.rs index 1359867..882a6a7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -103,6 +103,12 @@ impl From for Error { } } +impl From<::hyper::Error> for Error { + fn from(err: ::hyper::Error) -> Self { + Error{description: String::from(err.description()), cause: Some(Box::new(err))} + } +} + impl From for Error { fn from(err: io::Error) -> Self { Error{description: String::from(err.description()), cause: Some(Box::new(err))} diff --git a/src/web.rs b/src/web.rs index d8ce81d..308655a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -39,7 +39,7 @@ use futures::{future, stream}; use futures_cpupool; use json; use http_serve; -use hyper::header; +use hyper::header::{self, Header}; use hyper::server::{self, Request, Response}; use mime; use mp4; @@ -170,6 +170,7 @@ struct ServiceInner { db: Arc, dir: Arc, ui_files: HashMap, + allow_origin: Option, pool: futures_cpupool::CpuPool, time_zone_name: String, } @@ -383,17 +384,22 @@ impl ServiceInner { pub struct Service(Arc); impl Service { - pub fn new(db: Arc, dir: Arc, ui_dir: Option<&str>, zone: String) - -> Result { + pub fn new(db: Arc, dir: Arc, ui_dir: Option<&str>, + allow_origin: Option, zone: String) -> Result { let mut ui_files = HashMap::new(); if let Some(d) = ui_dir { Service::fill_ui_files(d, &mut ui_files); } debug!("UI files: {:#?}", ui_files); + let allow_origin = match allow_origin { + None => None, + Some(o) => Some(header::AccessControlAllowOrigin::parse_header(&header::Raw::from(o))?), + }; Ok(Service(Arc::new(ServiceInner { db, dir, ui_files, + allow_origin, pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(), time_zone_name: zone, }))) @@ -461,6 +467,11 @@ impl server::Service for Service { Path::NotFound => self.0.not_found(), Path::Static => self.0.static_file(&req), }; + let res = if let Some(ref o) = self.0.allow_origin { + res.map(|resp| resp.with_header(o.clone())) + } else { + res + }; future::result(res.map_err(|e| { error!("error: {}", e); hyper::Error::Incomplete @@ -519,7 +530,8 @@ mod bench { ::std::thread::spawn(move || { let addr = "127.0.0.1:0".parse().unwrap(); let (db, dir) = (db.db.clone(), db.dir.clone()); - let service = super::Service::new(db.clone(), dir.clone(), None, "".to_owned()).unwrap(); + let service = super::Service::new(db.clone(), dir.clone(), None, None, + "".to_owned()).unwrap(); let server = hyper::server::Http::new() .bind(&addr, move || Ok(service.clone())) .unwrap();