Merge branch 'allow-async-handlers' into user-management

This commit is contained in:
Emi Tatsuo 2020-11-21 16:55:36 -05:00
commit 0b4fca2c69
Signed by: Emi
GPG key ID: 68FAB2E2E6DFC98B
8 changed files with 77 additions and 97 deletions

View file

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- build time and size by [@Alch-Emi](https://github.com/Alch-Emi) - build time and size by [@Alch-Emi](https://github.com/Alch-Emi)
### Changed ### Changed
- Added route API [@Alch-Emi](https://github.com/Alch-Emi) - Added route API [@Alch-Emi](https://github.com/Alch-Emi)
- API for adding handlers now accepts async handlers [@Alch-Emi](https://github.com/Alch-Emi)
## [0.3.0] - 2020-11-14 ## [0.3.0] - 2020-11-14
### Added ### Added

View file

@ -21,7 +21,6 @@ tokio = { version = "0.3.1", features = ["io-util","net","time", "rt"] }
mime = "0.3.16" mime = "0.3.16"
uriparse = "0.6.3" uriparse = "0.6.3"
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
futures-core = "0.3.7"
log = "0.4.11" log = "0.4.11"
webpki = "0.21.0" webpki = "0.21.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
@ -39,5 +38,4 @@ required-features = ["user_management"]
[dev-dependencies] [dev-dependencies]
env_logger = "0.8.1" env_logger = "0.8.1"
futures-util = "0.3.7"
tokio = { version = "0.3.1", features = ["macros", "rt-multi-thread", "sync"] } tokio = { version = "0.3.1", features = ["macros", "rt-multi-thread", "sync"] }

View file

@ -1,6 +1,4 @@
use anyhow::*; use anyhow::*;
use futures_core::future::BoxFuture;
use futures_util::FutureExt;
use log::LevelFilter; use log::LevelFilter;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server}; use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server};
@ -31,44 +29,42 @@ async fn main() -> Result<()> {
/// selecting a username. They'll then get a message confirming their account creation. /// selecting a username. They'll then get a message confirming their account creation.
/// Any time this user visits the site in the future, they'll get a personalized welcome /// Any time this user visits the site in the future, they'll get a personalized welcome
/// message. /// message.
fn handle_request(users: Arc<RwLock<HashMap<CertBytes, String>>>, request: Request) -> BoxFuture<'static, Result<Response>> { async fn handle_request(users: Arc<RwLock<HashMap<CertBytes, String>>>, request: Request) -> Result<Response> {
async move { if let Some(Certificate(cert_bytes)) = request.certificate() {
if let Some(Certificate(cert_bytes)) = request.certificate() { // The user provided a certificate
// The user provided a certificate let users_read = users.read().await;
let users_read = users.read().await; if let Some(user) = users_read.get(cert_bytes) {
if let Some(user) = users_read.get(cert_bytes) { // The user has already registered
// The user has already registered Ok(
Response::success_with_body(
&GEMINI_MIME,
format!("Welcome {}!", user)
)
)
} else {
// The user still needs to register
drop(users_read);
if let Some(query_part) = request.uri().query() {
// The user provided some input (a username request)
let username = query_part.as_str();
let mut users_write = users.write().await;
users_write.insert(cert_bytes.clone(), username.to_owned());
Ok( Ok(
Response::success_with_body( Response::success_with_body(
&GEMINI_MIME, &GEMINI_MIME,
format!("Welcome {}!", user) format!(
"Your account has been created {}! Welcome!",
username
)
) )
) )
} else { } else {
// The user still needs to register // The user didn't provide input, and should be prompted
drop(users_read); Response::input("What username would you like?")
if let Some(query_part) = request.uri().query() {
// The user provided some input (a username request)
let username = query_part.as_str();
let mut users_write = users.write().await;
users_write.insert(cert_bytes.clone(), username.to_owned());
Ok(
Response::success_with_body(
&GEMINI_MIME,
format!(
"Your account has been created {}! Welcome!",
username
)
)
)
} else {
// The user didn't provide input, and should be prompted
Response::input("What username would you like?")
}
} }
} else {
// The user didn't provide a certificate
Ok(Response::client_certificate_required())
} }
}.boxed() } else {
// The user didn't provide a certificate
Ok(Response::client_certificate_required())
}
} }

View file

@ -1,6 +1,4 @@
use anyhow::*; use anyhow::*;
use futures_core::future::BoxFuture;
use futures_util::FutureExt;
use log::LevelFilter; use log::LevelFilter;
use northstar::{Server, Request, Response, GEMINI_PORT, Document}; use northstar::{Server, Request, Response, GEMINI_PORT, Document};
use northstar::document::HeadingLevel::*; use northstar::document::HeadingLevel::*;
@ -17,36 +15,33 @@ async fn main() -> Result<()> {
.await .await
} }
fn handle_request(_request: Request) -> BoxFuture<'static, Result<Response>> { async fn handle_request(_request: Request) -> Result<Response> {
async move { let mut document = Document::new();
let mut document = Document::new();
document document
.add_preformatted(include_str!("northstar_logo.txt")) .add_preformatted(include_str!("northstar_logo.txt"))
.add_blank_line() .add_blank_line()
.add_link("https://docs.rs/northstar", "Documentation") .add_link("https://docs.rs/northstar", "Documentation")
.add_link("https://github.com/panicbit/northstar", "GitHub") .add_link("https://github.com/panicbit/northstar", "GitHub")
.add_blank_line() .add_blank_line()
.add_heading(H1, "Usage") .add_heading(H1, "Usage")
.add_blank_line() .add_blank_line()
.add_text("Add the latest version of northstar to your `Cargo.toml`.") .add_text("Add the latest version of northstar to your `Cargo.toml`.")
.add_blank_line() .add_blank_line()
.add_heading(H2, "Manually") .add_heading(H2, "Manually")
.add_blank_line() .add_blank_line()
.add_preformatted_with_alt("toml", r#"northstar = "0.3.0" # check crates.io for the latest version"#) .add_preformatted_with_alt("toml", r#"northstar = "0.3.0" # check crates.io for the latest version"#)
.add_blank_line() .add_blank_line()
.add_heading(H2, "Automatically") .add_heading(H2, "Automatically")
.add_blank_line() .add_blank_line()
.add_preformatted_with_alt("sh", "cargo add northstar") .add_preformatted_with_alt("sh", "cargo add northstar")
.add_blank_line() .add_blank_line()
.add_heading(H1, "Generating a key & certificate") .add_heading(H1, "Generating a key & certificate")
.add_blank_line() .add_blank_line()
.add_preformatted_with_alt("sh", concat!( .add_preformatted_with_alt("sh", concat!(
"mkdir cert && cd cert\n", "mkdir cert && cd cert\n",
"openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365", "openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365",
)); ));
Ok(Response::document(document)) Ok(Response::document(document))
}
.boxed()
} }

View file

@ -1,6 +1,4 @@
use anyhow::*; use anyhow::*;
use futures_core::future::BoxFuture;
use futures_util::FutureExt;
use log::LevelFilter; use log::LevelFilter;
use northstar::{Document, document::HeadingLevel, Request, Response, GEMINI_PORT}; use northstar::{Document, document::HeadingLevel, Request, Response, GEMINI_PORT};
@ -18,25 +16,19 @@ async fn main() -> Result<()> {
.await .await
} }
fn handle_base(req: Request) -> BoxFuture<'static, Result<Response>> { async fn handle_base(req: Request) -> Result<Response> {
let doc = generate_doc("base", &req); let doc = generate_doc("base", &req);
async move { Ok(Response::document(doc))
Ok(Response::document(doc))
}.boxed()
} }
fn handle_short(req: Request) -> BoxFuture<'static, Result<Response>> { async fn handle_short(req: Request) -> Result<Response> {
let doc = generate_doc("short", &req); let doc = generate_doc("short", &req);
async move { Ok(Response::document(doc))
Ok(Response::document(doc))
}.boxed()
} }
fn handle_long(req: Request) -> BoxFuture<'static, Result<Response>> { async fn handle_long(req: Request) -> Result<Response> {
let doc = generate_doc("long", &req); let doc = generate_doc("long", &req);
async move { Ok(Response::document(doc))
Ok(Response::document(doc))
}.boxed()
} }
fn generate_doc(route_name: &str, req: &Request) -> Document { fn generate_doc(route_name: &str, req: &Request) -> Document {

View file

@ -1,6 +1,4 @@
use anyhow::*; use anyhow::*;
use futures_core::future::BoxFuture;
use futures_util::FutureExt;
use log::LevelFilter; use log::LevelFilter;
use northstar::{Server, Request, Response, GEMINI_PORT}; use northstar::{Server, Request, Response, GEMINI_PORT};
@ -16,12 +14,9 @@ async fn main() -> Result<()> {
.await .await
} }
fn handle_request(request: Request) -> BoxFuture<'static, Result<Response>> { async fn handle_request(request: Request) -> Result<Response> {
async move { let path = request.path_segments();
let path = request.path_segments(); let response = northstar::util::serve_dir("public", &path).await?;
let response = northstar::util::serve_dir("public", &path).await?;
Ok(response) Ok(response)
}
.boxed()
} }

View file

@ -7,8 +7,9 @@ use std::{
sync::Arc, sync::Arc,
path::PathBuf, path::PathBuf,
time::Duration, time::Duration,
pin::Pin,
}; };
use futures_core::future::BoxFuture; use std::future::Future;
use tokio::{ use tokio::{
prelude::*, prelude::*,
io::{self, BufStream}, io::{self, BufStream},
@ -41,7 +42,7 @@ pub const REQUEST_URI_MAX_LEN: usize = 1024;
pub const GEMINI_PORT: u16 = 1965; pub const GEMINI_PORT: u16 = 1965;
type Handler = Arc<dyn Fn(Request) -> HandlerResponse + Send + Sync>; type Handler = Arc<dyn Fn(Request) -> HandlerResponse + Send + Sync>;
pub (crate) type HandlerResponse = BoxFuture<'static, Result<Response>>; pub (crate) type HandlerResponse = Pin<Box<dyn Future<Output = Result<Response>> + Send>>;
#[derive(Clone)] #[derive(Clone)]
pub struct Server { pub struct Server {
@ -351,11 +352,13 @@ impl<A: ToSocketAddrs> Builder<A> {
/// "endpoint". Entering a relative or malformed path will result in a panic. /// "endpoint". Entering a relative or malformed path will result in a panic.
/// ///
/// For more information about routing mechanics, see the docs for [`RoutingNode`]. /// For more information about routing mechanics, see the docs for [`RoutingNode`].
pub fn add_route<H>(mut self, path: &'static str, handler: H) -> Self pub fn add_route<F, H>(mut self, path: &'static str, handler: H) -> Self
where where
H: Fn(Request) -> HandlerResponse + Send + Sync + 'static, H: Send + Sync + 'static + Fn(Request) -> F,
F: Send + Sync + 'static + Future<Output = Result<Response>>
{ {
self.routes.add_route(path, Arc::new(handler)); let wrapped = Arc::new(move|req| Box::pin((handler)(req)) as HandlerResponse);
self.routes.add_route(path, wrapped);
self self
} }

View file

@ -13,7 +13,7 @@ use crate::types::{Document, document::HeadingLevel::*};
use crate::types::Response; use crate::types::Response;
use std::panic::{catch_unwind, AssertUnwindSafe}; use std::panic::{catch_unwind, AssertUnwindSafe};
use std::task::Poll; use std::task::Poll;
use futures_core::future::Future; use std::future::Future;
use tokio::time; use tokio::time;
#[cfg(feature="serve_dir")] #[cfg(feature="serve_dir")]