use futures::future::{self, FutureObj};
use http_service::HttpService;
use std::sync::Arc;
use crate::{
middleware::{Middleware, Next},
router::{Router, Selection},
Context, Route,
};
pub struct App<AppData> {
router: Router<AppData>,
middleware: Vec<Arc<dyn Middleware<AppData>>>,
data: AppData,
}
impl<AppData: Send + Sync + 'static> App<AppData> {
pub fn new(data: AppData) -> App<AppData> {
App {
router: Router::new(),
middleware: Vec::new(),
data,
}
}
pub fn at<'a>(&'a mut self, path: &'a str) -> Route<'a, AppData> {
Route::new(&mut self.router, path.to_owned())
}
pub fn middleware(&mut self, m: impl Middleware<AppData>) -> &mut Self {
self.middleware.push(Arc::new(m));
self
}
pub fn into_http_service(self) -> Server<AppData> {
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::serve(self.into_http_service(), addr);
Ok(())
}
}
#[derive(Clone)]
pub struct Server<AppData> {
router: Arc<Router<AppData>>,
data: Arc<AppData>,
middleware: Arc<Vec<Arc<dyn Middleware<AppData>>>>,
}
impl<AppData: Sync + Send + 'static> HttpService for Server<AppData> {
type Connection = ();
type ConnectionFuture = future::Ready<Result<(), std::io::Error>>;
type Fut = FutureObj<'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::Fut {
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(await!(fut))
}
}
}
#[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,
) -> FutureObj<'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());
}
}
}