start splitting up web.rs

It's getting huge and hard to work with. The path stuff is easy to pull
out.
This commit is contained in:
Scott Lamb 2021-10-28 12:38:29 -07:00
parent 44b1ac965d
commit 4231ec45ce
2 changed files with 173 additions and 157 deletions

View File

@ -1,7 +1,10 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
mod path;
use self::path::Path;
use crate::body::Body;
use crate::json;
use crate::mp4;
@ -55,97 +58,6 @@ impl From<base::Error> for HttpError {
}
}
#[derive(Debug, Eq, PartialEq)]
enum Path {
TopLevel, // "/api/"
Request, // "/api/request"
InitSegment(i32, bool), // "/api/init/<id>.mp4{.txt}"
Camera(Uuid), // "/api/cameras/<uuid>/"
Signals, // "/api/signals"
StreamRecordings(Uuid, db::StreamType), // "/api/cameras/<uuid>/<type>/recordings"
StreamViewMp4(Uuid, db::StreamType, bool), // "/api/cameras/<uuid>/<type>/view.mp4{.txt}"
StreamViewMp4Segment(Uuid, db::StreamType, bool), // "/api/cameras/<uuid>/<type>/view.m4s{.txt}"
StreamLiveMp4Segments(Uuid, db::StreamType), // "/api/cameras/<uuid>/<type>/live.m4s"
Login, // "/api/login"
Logout, // "/api/logout"
Static, // (anything that doesn't start with "/api/")
User(i32), // "/api/users/<id>"
NotFound,
}
impl Path {
fn decode(path: &str) -> Self {
let path = match path.strip_prefix("/api/") {
Some(p) => p,
None => return Path::Static,
};
match path {
"" => return Path::TopLevel,
"login" => return Path::Login,
"logout" => return Path::Logout,
"request" => return Path::Request,
"signals" => return Path::Signals,
_ => {}
};
if let Some(path) = path.strip_prefix("init/") {
let (debug, path) = match path.strip_suffix(".txt") {
Some(p) => (true, p),
None => (false, path),
};
let path = match path.strip_suffix(".mp4") {
Some(p) => p,
None => return Path::NotFound,
};
if let Ok(id) = i32::from_str(&path) {
return Path::InitSegment(id, debug);
}
return Path::NotFound;
} else if let Some(path) = path.strip_prefix("cameras/") {
let (uuid, path) = match path.split_once('/') {
Some(pair) => pair,
None => return Path::NotFound,
};
// TODO(slamb): require uuid to be in canonical format.
let uuid = match Uuid::parse_str(uuid) {
Ok(u) => u,
Err(_) => return Path::NotFound,
};
if path.is_empty() {
return Path::Camera(uuid);
}
let (type_, path) = match path.split_once('/') {
Some(pair) => pair,
None => return Path::NotFound,
};
let type_ = match db::StreamType::parse(type_) {
None => {
return Path::NotFound;
}
Some(t) => t,
};
match path {
"recordings" => Path::StreamRecordings(uuid, type_),
"view.mp4" => Path::StreamViewMp4(uuid, type_, false),
"view.mp4.txt" => Path::StreamViewMp4(uuid, type_, true),
"view.m4s" => Path::StreamViewMp4Segment(uuid, type_, false),
"view.m4s.txt" => Path::StreamViewMp4Segment(uuid, type_, true),
"live.m4s" => Path::StreamLiveMp4Segments(uuid, type_),
_ => Path::NotFound,
}
} else if let Some(path) = path.strip_prefix("users/") {
if let Ok(id) = i32::from_str(path) {
return Path::User(id);
}
Path::NotFound
} else {
Path::NotFound
}
}
}
fn plain_response<B: Into<Body>>(status: http::StatusCode, body: B) -> Response<Body> {
Response::builder()
.status(status)
@ -1505,71 +1417,6 @@ mod tests {
}
}
#[test]
fn paths() {
use super::Path;
use uuid::Uuid;
let cam_uuid = Uuid::parse_str("35144640-ff1e-4619-b0d5-4c74c185741c").unwrap();
assert_eq!(Path::decode("/foo"), Path::Static);
assert_eq!(Path::decode("/api/"), Path::TopLevel);
assert_eq!(
Path::decode("/api/init/42.mp4"),
Path::InitSegment(42, false)
);
assert_eq!(
Path::decode("/api/init/42.mp4.txt"),
Path::InitSegment(42, true)
);
assert_eq!(Path::decode("/api/init/x.mp4"), Path::NotFound); // non-digit
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/"),
Path::Camera(cam_uuid)
);
assert_eq!(Path::decode("/api/cameras/asdf/"), Path::NotFound);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/recordings"),
Path::StreamRecordings(cam_uuid, db::StreamType::Main)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/sub/recordings"),
Path::StreamRecordings(cam_uuid, db::StreamType::Sub)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/junk/recordings"),
Path::NotFound
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4"),
Path::StreamViewMp4(cam_uuid, db::StreamType::Main, false)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4.txt"),
Path::StreamViewMp4(cam_uuid, db::StreamType::Main, true)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s"),
Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, false)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s.txt"),
Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, true)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/live.m4s"),
Path::StreamLiveMp4Segments(cam_uuid, db::StreamType::Main)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/junk"),
Path::NotFound
);
assert_eq!(Path::decode("/api/login"), Path::Login);
assert_eq!(Path::decode("/api/logout"), Path::Logout);
assert_eq!(Path::decode("/api/signals"), Path::Signals);
assert_eq!(Path::decode("/api/junk"), Path::NotFound);
assert_eq!(Path::decode("/api/users/42"), Path::User(42));
assert_eq!(Path::decode("/api/users/asdf"), Path::NotFound);
}
#[test]
fn static_file() {
testutil::init();

169
server/src/web/path.rs Normal file
View File

@ -0,0 +1,169 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
//! Decodes request paths.
use std::str::FromStr;
use uuid::Uuid;
/// A decoded request path.
#[derive(Debug, Eq, PartialEq)]
pub(super) enum Path {
TopLevel, // "/api/"
Request, // "/api/request"
InitSegment(i32, bool), // "/api/init/<id>.mp4{.txt}"
Camera(Uuid), // "/api/cameras/<uuid>/"
Signals, // "/api/signals"
StreamRecordings(Uuid, db::StreamType), // "/api/cameras/<uuid>/<type>/recordings"
StreamViewMp4(Uuid, db::StreamType, bool), // "/api/cameras/<uuid>/<type>/view.mp4{.txt}"
StreamViewMp4Segment(Uuid, db::StreamType, bool), // "/api/cameras/<uuid>/<type>/view.m4s{.txt}"
StreamLiveMp4Segments(Uuid, db::StreamType), // "/api/cameras/<uuid>/<type>/live.m4s"
Login, // "/api/login"
Logout, // "/api/logout"
Static, // (anything that doesn't start with "/api/")
User(i32), // "/api/users/<id>"
NotFound,
}
impl Path {
/// Decodes a request path, notably not including any request parameters.
pub(super) fn decode(path: &str) -> Self {
let path = match path.strip_prefix("/api/") {
Some(p) => p,
None => return Path::Static,
};
match path {
"" => return Path::TopLevel,
"login" => return Path::Login,
"logout" => return Path::Logout,
"request" => return Path::Request,
"signals" => return Path::Signals,
_ => {}
};
if let Some(path) = path.strip_prefix("init/") {
let (debug, path) = match path.strip_suffix(".txt") {
Some(p) => (true, p),
None => (false, path),
};
let path = match path.strip_suffix(".mp4") {
Some(p) => p,
None => return Path::NotFound,
};
if let Ok(id) = i32::from_str(&path) {
return Path::InitSegment(id, debug);
}
return Path::NotFound;
} else if let Some(path) = path.strip_prefix("cameras/") {
let (uuid, path) = match path.split_once('/') {
Some(pair) => pair,
None => return Path::NotFound,
};
// TODO(slamb): require uuid to be in canonical format.
let uuid = match Uuid::parse_str(uuid) {
Ok(u) => u,
Err(_) => return Path::NotFound,
};
if path.is_empty() {
return Path::Camera(uuid);
}
let (type_, path) = match path.split_once('/') {
Some(pair) => pair,
None => return Path::NotFound,
};
let type_ = match db::StreamType::parse(type_) {
None => {
return Path::NotFound;
}
Some(t) => t,
};
match path {
"recordings" => Path::StreamRecordings(uuid, type_),
"view.mp4" => Path::StreamViewMp4(uuid, type_, false),
"view.mp4.txt" => Path::StreamViewMp4(uuid, type_, true),
"view.m4s" => Path::StreamViewMp4Segment(uuid, type_, false),
"view.m4s.txt" => Path::StreamViewMp4Segment(uuid, type_, true),
"live.m4s" => Path::StreamLiveMp4Segments(uuid, type_),
_ => Path::NotFound,
}
} else if let Some(path) = path.strip_prefix("users/") {
if let Ok(id) = i32::from_str(path) {
return Path::User(id);
}
Path::NotFound
} else {
Path::NotFound
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn paths() {
use super::Path;
use uuid::Uuid;
let cam_uuid = Uuid::parse_str("35144640-ff1e-4619-b0d5-4c74c185741c").unwrap();
assert_eq!(Path::decode("/foo"), Path::Static);
assert_eq!(Path::decode("/api/"), Path::TopLevel);
assert_eq!(
Path::decode("/api/init/42.mp4"),
Path::InitSegment(42, false)
);
assert_eq!(
Path::decode("/api/init/42.mp4.txt"),
Path::InitSegment(42, true)
);
assert_eq!(Path::decode("/api/init/x.mp4"), Path::NotFound); // non-digit
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/"),
Path::Camera(cam_uuid)
);
assert_eq!(Path::decode("/api/cameras/asdf/"), Path::NotFound);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/recordings"),
Path::StreamRecordings(cam_uuid, db::StreamType::Main)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/sub/recordings"),
Path::StreamRecordings(cam_uuid, db::StreamType::Sub)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/junk/recordings"),
Path::NotFound
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4"),
Path::StreamViewMp4(cam_uuid, db::StreamType::Main, false)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4.txt"),
Path::StreamViewMp4(cam_uuid, db::StreamType::Main, true)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s"),
Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, false)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s.txt"),
Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, true)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/live.m4s"),
Path::StreamLiveMp4Segments(cam_uuid, db::StreamType::Main)
);
assert_eq!(
Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/junk"),
Path::NotFound
);
assert_eq!(Path::decode("/api/login"), Path::Login);
assert_eq!(Path::decode("/api/logout"), Path::Logout);
assert_eq!(Path::decode("/api/signals"), Path::Signals);
assert_eq!(Path::decode("/api/junk"), Path::NotFound);
assert_eq!(Path::decode("/api/users/42"), Path::User(42));
assert_eq!(Path::decode("/api/users/asdf"), Path::NotFound);
}
}