use futures::future::{self, BoxFuture};
use http_service::HttpService;
use std::sync::Arc;
use crate::{
middleware::{Middleware, Next},
router::{Router, Selection},
Context, Route,
};
#[allow(missing_debug_implementations)]
pub struct App<State> {
router: Router<State>,
middleware: Vec<Arc<dyn Middleware<State>>>,
data: State,
}
impl App<()> {
pub fn new() -> App<()> {
Self::with_state(())
}
}
impl Default for App<()> {
fn default() -> App<()> {
Self::new()
}
}
impl<State: Send + Sync + 'static> App<State> {
pub fn with_state(state: State) -> App<State> {
App {
router: Router::new(),
middleware: Vec::new(),
data: state,
}
}
pub fn at<'a>(&'a mut self, path: &'a str) -> Route<'a, State> {
Route::new(&mut self.router, path.to_owned())
}
pub fn middleware(&mut self, m: impl Middleware<State>) -> &mut Self {
self.middleware.push(Arc::new(m));
self
}
pub fn into_http_service(self) -> Server<State> {
Server {
router: Arc::new(self.router),
data: Arc::new(self.data),
middleware: Arc::new(self.middleware),
}
}
#[cfg(feature = "hyper")]
pub fn serve(self, addr: impl std::net::ToSocketAddrs) -> std::io::Result<()> {
let addr = addr
.to_socket_addrs()?
.next()
.ok_or(std::io::ErrorKind::InvalidInput)?;
println!("Server is listening on: http://{}", addr);
http_service_hyper::run(self.into_http_service(), addr);
Ok(())
}
}
#[derive(Clone)]
#[allow(missing_debug_implementations)]
pub struct Server<State> {
router: Arc<Router<State>>,
data: Arc<State>,
middleware: Arc<Vec<Arc<dyn Middleware<State>>>>,
}
impl<State: Sync + Send + 'static> HttpService for Server<State> {
type Connection = ();
type ConnectionFuture = future::Ready<Result<(), std::io::Error>>;
type ResponseFuture = BoxFuture<'static, Result<http_service::Response, std::io::Error>>;
fn connect(&self) -> Self::ConnectionFuture {
future::ok(())
}
fn respond(&self, _conn: &mut (), req: http_service::Request) -> Self::ResponseFuture {
let path = req.uri().path().to_owned();
let method = req.method().to_owned();
let router = self.router.clone();
let middleware = self.middleware.clone();
let data = self.data.clone();
box_async! {
let fut = {
let Selection { endpoint, params } = router.route(&path, method);
let cx = Context::new(data, req, params);
let next = Next {
endpoint,
next_middleware: &middleware,
};
next.run(cx)
};
Ok(fut.await)
}
}
}
#[cfg(test)]
mod tests {
use futures::executor::block_on;
use std::sync::Arc;
use super::*;
use crate::{middleware::Next, router::Selection, Context, Response};
fn simulate_request<'a, Data: Default + Clone + Send + Sync + 'static>(
app: &'a App<Data>,
path: &'a str,
method: http::Method,
) -> BoxFuture<'a, Response> {
let Selection { endpoint, params } = app.router.route(path, method.clone());
let data = Arc::new(Data::default());
let req = http::Request::builder()
.method(method)
.body(http_service::Body::empty())
.unwrap();
let cx = Context::new(data, req, params);
let next = Next {
endpoint,
next_middleware: &app.middleware,
};
next.run(cx)
}
#[test]
fn simple_static() {
let mut router = App::new();
router.at("/").get(|_| async move { "/" });
router.at("/foo").get(|_| async move { "/foo" });
router.at("/foo/bar").get(|_| async move { "/foo/bar" });
for path in &["/", "/foo", "/foo/bar"] {
let res = block_on(simulate_request(&router, path, http::Method::GET));
let body = block_on(res.into_body().into_vec()).expect("Reading body should succeed");
assert_eq!(&*body, path.as_bytes());
}
}
#[test]
fn nested_static() {
let mut router = App::new();
router.at("/a").get(|_| async move { "/a" });
router.at("/b").nest(|router| {
router.at("/").get(|_| async move { "/b" });
router.at("/a").get(|_| async move { "/b/a" });
router.at("/b").get(|_| async move { "/b/b" });
router.at("/c").nest(|router| {
router.at("/a").get(|_| async move { "/b/c/a" });
router.at("/b").get(|_| async move { "/b/c/b" });
});
router.at("/d").get(|_| async move { "/b/d" });
});
router.at("/a/a").nest(|router| {
router.at("/a").get(|_| async move { "/a/a/a" });
router.at("/b").get(|_| async move { "/a/a/b" });
});
router.at("/a/b").nest(|router| {
router.at("/").get(|_| async move { "/a/b" });
});
for failing_path in &["/", "/a/a", "/a/b/a"] {
let res = block_on(simulate_request(&router, failing_path, http::Method::GET));
if !res.status().is_client_error() {
panic!(
"Should have returned a client error when router cannot match with path {}",
failing_path
);
}
}
for path in &[
"/a", "/a/a/a", "/a/a/b", "/a/b", "/b", "/b/a", "/b/b", "/b/c/a", "/b/c/b", "/b/d",
] {
let res = block_on(simulate_request(&router, path, http::Method::GET));
let body = block_on(res.into_body().into_vec()).expect("Reading body should succeed");
assert_eq!(&*body, path.as_bytes());
}
}
#[test]
fn multiple_methods() {
let mut router = App::new();
router.at("/a").nest(|router| {
router.at("/b").get(|_| async move { "/a/b GET" });
});
router.at("/a/b").post(|_| async move { "/a/b POST" });
for (path, method) in &[("/a/b", http::Method::GET), ("/a/b", http::Method::POST)] {
let res = block_on(simulate_request(&router, path, method.clone()));
assert!(res.status().is_success());
let body = block_on(res.into_body().into_vec()).expect("Reading body should succeed");
assert_eq!(&*body, format!("{} {}", path, method).as_bytes());
}
}
}