support serving Access-Control-Allow-Origin header (#19)

support serving Access-Control-Allow-Origin header

Closes #17.
This commit is contained in:
Scott Lamb 2018-03-03 06:43:36 -08:00 committed by GitHub
parent 760b8d95fb
commit 672a327ee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 6 deletions

View File

@ -58,11 +58,15 @@ Options:
--sample-file-dir=DIR Set the directory holding video data. --sample-file-dir=DIR Set the directory holding video data.
This is typically on a hard drive. This is typically on a hard drive.
[default: /var/lib/moonfire-nvr/sample] [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] [default: /usr/local/lib/moonfire-nvr/ui]
--http-addr=ADDR Set the bind address for the unencrypted HTTP server. --http-addr=ADDR Set the bind address for the unencrypted HTTP server.
[default: 0.0.0.0:8080] [default: 0.0.0.0:8080]
--read-only Forces read-only mode / disables recording. --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)] #[derive(Debug, Deserialize)]
@ -72,6 +76,7 @@ struct Args {
flag_http_addr: String, flag_http_addr: String,
flag_ui_dir: String, flag_ui_dir: String,
flag_read_only: bool, flag_read_only: bool,
flag_allow_origin: Option<String>,
} }
fn setup_shutdown_future(h: &reactor::Handle) -> Box<Future<Item = (), Error = ()>> { fn setup_shutdown_future(h: &reactor::Handle) -> Box<Future<Item = (), Error = ()>> {
@ -101,7 +106,8 @@ pub fn run() -> Result<(), Error> {
let dir = dir::SampleFileDir::new(&args.flag_sample_file_dir, db.clone()).unwrap(); let dir = dir::SampleFileDir::new(&args.flag_sample_file_dir, db.clone()).unwrap();
info!("Database is loaded."); 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. // Start a streamer for each camera.
let shutdown_streamers = Arc::new(AtomicBool::new(false)); let shutdown_streamers = Arc::new(AtomicBool::new(false));

View File

@ -103,6 +103,12 @@ impl From<fmt::Error> 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<io::Error> for Error { impl From<io::Error> for Error {
fn from(err: io::Error) -> Self { fn from(err: io::Error) -> Self {
Error{description: String::from(err.description()), cause: Some(Box::new(err))} Error{description: String::from(err.description()), cause: Some(Box::new(err))}

View File

@ -39,7 +39,7 @@ use futures::{future, stream};
use futures_cpupool; use futures_cpupool;
use json; use json;
use http_serve; use http_serve;
use hyper::header; use hyper::header::{self, Header};
use hyper::server::{self, Request, Response}; use hyper::server::{self, Request, Response};
use mime; use mime;
use mp4; use mp4;
@ -170,6 +170,7 @@ struct ServiceInner {
db: Arc<db::Database>, db: Arc<db::Database>,
dir: Arc<SampleFileDir>, dir: Arc<SampleFileDir>,
ui_files: HashMap<String, UiFile>, ui_files: HashMap<String, UiFile>,
allow_origin: Option<header::AccessControlAllowOrigin>,
pool: futures_cpupool::CpuPool, pool: futures_cpupool::CpuPool,
time_zone_name: String, time_zone_name: String,
} }
@ -383,17 +384,22 @@ impl ServiceInner {
pub struct Service(Arc<ServiceInner>); pub struct Service(Arc<ServiceInner>);
impl Service { impl Service {
pub fn new(db: Arc<db::Database>, dir: Arc<SampleFileDir>, ui_dir: Option<&str>, zone: String) pub fn new(db: Arc<db::Database>, dir: Arc<SampleFileDir>, ui_dir: Option<&str>,
-> Result<Self, Error> { allow_origin: Option<String>, zone: String) -> Result<Self, Error> {
let mut ui_files = HashMap::new(); let mut ui_files = HashMap::new();
if let Some(d) = ui_dir { if let Some(d) = ui_dir {
Service::fill_ui_files(d, &mut ui_files); Service::fill_ui_files(d, &mut ui_files);
} }
debug!("UI files: {:#?}", 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 { Ok(Service(Arc::new(ServiceInner {
db, db,
dir, dir,
ui_files, ui_files,
allow_origin,
pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(), pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(),
time_zone_name: zone, time_zone_name: zone,
}))) })))
@ -461,6 +467,11 @@ impl server::Service for Service {
Path::NotFound => self.0.not_found(), Path::NotFound => self.0.not_found(),
Path::Static => self.0.static_file(&req), 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| { future::result(res.map_err(|e| {
error!("error: {}", e); error!("error: {}", e);
hyper::Error::Incomplete hyper::Error::Incomplete
@ -519,7 +530,8 @@ mod bench {
::std::thread::spawn(move || { ::std::thread::spawn(move || {
let addr = "127.0.0.1:0".parse().unwrap(); let addr = "127.0.0.1:0".parse().unwrap();
let (db, dir) = (db.db.clone(), db.dir.clone()); 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() let server = hyper::server::Http::new()
.bind(&addr, move || Ok(service.clone())) .bind(&addr, move || Ok(service.clone()))
.unwrap(); .unwrap();