1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-23 23:51:06 +01:00

Application, router, resource builders

This commit is contained in:
Nikolay Kim 2017-10-15 14:17:41 -07:00
parent 5480cb5d49
commit 5901f0f9f5
11 changed files with 406 additions and 146 deletions

View File

@ -39,34 +39,20 @@ use std::str::FromStr;
use actix::prelude::*; use actix::prelude::*;
use actix_web::*; use actix_web::*;
// Route
struct MyRoute;
impl Actor for MyRoute {
type Context = HttpContext<Self>;
}
impl Route for MyRoute {
type State = ();
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self>
{
Reply::reply(httpcodes::HTTPOk)
}
}
fn main() { fn main() {
let system = System::new("test"); let system = System::new("test");
// create routing map with `MyRoute` route
let mut routes = RoutingMap::default();
routes
.add_resource("/")
.post::<MyRoute>();
// start http server // start http server
let http = HttpServer::new(routes); HttpServer::new(
http.serve::<()>( // create routing map with `MyRoute` route
RoutingMap::default()
.resource("/", |r|
r.handler(Method::GET, |req, payload, state| {
httpcodes::HTTPOk
})
)
.finish())
.serve::<()>(
&net::SocketAddr::from_str("127.0.0.1:8880").unwrap()).unwrap(); &net::SocketAddr::from_str("127.0.0.1:8880").unwrap()).unwrap();
// stop system // stop system

View File

@ -5,11 +5,12 @@ use std::collections::HashMap;
use route_recognizer::Router; use route_recognizer::Router;
use task::Task; use task::Task;
use route::RouteHandler; use route::{RouteHandler, FnHandler};
use router::Handler; use router::Handler;
use resource::Resource; use resource::Resource;
use payload::Payload; use payload::Payload;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Application /// Application
@ -47,21 +48,34 @@ impl<S> Application<S> where S: 'static
} }
} }
impl Default for Application<()> {
/// Create default `Application` with no state impl Application<()> {
fn default() -> Self {
Application { /// Create default `ApplicationBuilder` with no state
pub fn default() -> ApplicationBuilder<()> {
ApplicationBuilder {
parts: Some(ApplicationBuilderParts {
state: (), state: (),
default: Resource::default(), default: Resource::default(),
handlers: HashMap::new(), handlers: HashMap::new(),
resources: HashMap::new(), resources: HashMap::new()})
} }
} }
} }
impl<S> Application<S> where S: 'static { impl<S> Application<S> where S: 'static {
/// Create application builder
pub fn builder(state: S) -> ApplicationBuilder<S> {
ApplicationBuilder {
parts: Some(ApplicationBuilderParts {
state: state,
default: Resource::default(),
handlers: HashMap::new(),
resources: HashMap::new()})
}
}
/// Create http application with specific state. State is shared with all /// Create http application with specific state. State is shared with all
/// routes within same application and could be /// routes within same application and could be
/// accessed with `HttpContext::state()` method. /// accessed with `HttpContext::state()` method.
@ -75,7 +89,7 @@ impl<S> Application<S> where S: 'static {
} }
/// Add resource by path. /// Add resource by path.
pub fn add<P: ToString>(&mut self, path: P) -> &mut Resource<S> pub fn resource<P: ToString>(&mut self, path: P) -> &mut Resource<S>
{ {
let path = path.to_string(); let path = path.to_string();
@ -87,8 +101,31 @@ impl<S> Application<S> where S: 'static {
self.resources.get_mut(&path).unwrap() self.resources.get_mut(&path).unwrap()
} }
/// This method register handler for specified path.
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
/// let mut app = Application::new(());
///
/// app.handler("/test", |req, payload, state| {
/// httpcodes::HTTPOk
/// });
/// }
/// ```
pub fn handler<P, F, R>(&mut self, path: P, handler: F) -> &mut Self
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Into<HttpResponse> + 'static,
P: ToString,
{
self.handlers.insert(path.to_string(), Box::new(FnHandler::new(handler)));
self
}
/// Add path handler /// Add path handler
pub fn add_handler<H, P>(&mut self, path: P, h: H) pub fn route_handler<H, P>(&mut self, path: P, h: H)
where H: RouteHandler<S> + 'static, P: ToString where H: RouteHandler<S> + 'static, P: ToString
{ {
let path = path.to_string(); let path = path.to_string();
@ -107,6 +144,141 @@ impl<S> Application<S> where S: 'static {
} }
} }
struct ApplicationBuilderParts<S> {
state: S,
default: Resource<S>,
handlers: HashMap<String, Box<RouteHandler<S>>>,
resources: HashMap<String, Resource<S>>,
}
impl<S> From<ApplicationBuilderParts<S>> for Application<S> {
fn from(b: ApplicationBuilderParts<S>) -> Self {
Application {
state: b.state,
default: b.default,
handlers: b.handlers,
resources: b.resources,
}
}
}
/// Application builder
pub struct ApplicationBuilder<S=()> {
parts: Option<ApplicationBuilderParts<S>>,
}
impl<S> ApplicationBuilder<S> where S: 'static {
/// Configure resource for specific path.
///
/// ```rust
/// extern crate actix;
/// extern crate actix_web;
/// use actix_web::*;
/// use actix::prelude::*;
///
/// struct MyRoute;
///
/// impl Actor for MyRoute {
/// type Context = HttpContext<Self>;
/// }
///
/// impl Route for MyRoute {
/// type State = ();
///
/// fn request(req: HttpRequest,
/// payload: Payload,
/// ctx: &mut HttpContext<Self>) -> Reply<Self> {
/// Reply::reply(httpcodes::HTTPOk)
/// }
/// }
/// fn main() {
/// let app = Application::default()
/// .resource("/test", |r| {
/// r.get::<MyRoute>();
/// r.handler(Method::HEAD, |req, payload, state| {
/// httpcodes::HTTPMethodNotAllowed
/// });
/// })
/// .finish();
/// }
/// ```
pub fn resource<F, P: ToString>(&mut self, path: P, f: F) -> &mut Self
where F: FnOnce(&mut Resource<S>) + 'static
{
{
let parts = self.parts.as_mut().expect("Use after finish");
// add resource
let path = path.to_string();
if !parts.resources.contains_key(&path) {
parts.resources.insert(path.clone(), Resource::default());
}
f(parts.resources.get_mut(&path).unwrap());
}
self
}
/// Default resource is used if no matches route could be found.
pub fn default_resource<F>(&mut self, f: F) -> &mut Self
where F: FnOnce(&mut Resource<S>) + 'static
{
{
let parts = self.parts.as_mut().expect("Use after finish");
f(&mut parts.default);
}
self
}
/// This method register handler for specified path.
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
/// let app = Application::default()
/// .handler("/test", |req, payload, state| {
/// match *req.method() {
/// Method::GET => httpcodes::HTTPOk,
/// Method::POST => httpcodes::HTTPMethodNotAllowed,
/// _ => httpcodes::HTTPNotFound,
/// }
/// })
/// .finish();
/// }
/// ```
pub fn handler<P, F, R>(&mut self, path: P, handler: F) -> &mut Self
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Into<HttpResponse> + 'static,
P: ToString,
{
self.parts.as_mut().expect("Use after finish")
.handlers.insert(path.to_string(), Box::new(FnHandler::new(handler)));
self
}
/// Add path handler
pub fn route_handler<H, P>(&mut self, path: P, h: H) -> &mut Self
where H: RouteHandler<S> + 'static, P: ToString
{
{
// add resource
let parts = self.parts.as_mut().expect("Use after finish");
let path = path.to_string();
if parts.handlers.contains_key(&path) {
panic!("Handler already registered: {:?}", path);
}
parts.handlers.insert(path, Box::new(h));
}
self
}
/// Construct application
pub fn finish(&mut self) -> Application<S> {
self.parts.take().expect("Use after finish").into()
}
}
pub(crate) pub(crate)
struct InnerApplication<S> { struct InnerApplication<S> {

View File

@ -10,9 +10,9 @@
pub use ws; pub use ws;
pub use httpcodes; pub use httpcodes;
pub use error::ParseError; pub use error::ParseError;
pub use application::Application; pub use application::{Application, ApplicationBuilder};
pub use httprequest::HttpRequest; pub use httprequest::HttpRequest;
pub use httpresponse::{Body, Builder, HttpResponse}; pub use httpresponse::{Body, HttpResponse, HttpResponseBuilder};
pub use payload::{Payload, PayloadItem, PayloadError}; pub use payload::{Payload, PayloadItem, PayloadError};
pub use router::RoutingMap; pub use router::RoutingMap;
pub use resource::{Reply, Resource}; pub use resource::{Reply, Resource};
@ -22,6 +22,7 @@ pub use context::HttpContext;
pub use staticfiles::StaticFiles; pub use staticfiles::StaticFiles;
// re-exports // re-exports
pub use http::{Method, StatusCode};
pub use cookie::{Cookie, CookieBuilder}; pub use cookie::{Cookie, CookieBuilder};
pub use cookie::{ParseError as CookieParseError}; pub use cookie::{ParseError as CookieParseError};
pub use route_recognizer::Params; pub use route_recognizer::Params;

View File

@ -7,7 +7,7 @@ use task::Task;
use route::RouteHandler; use route::RouteHandler;
use payload::Payload; use payload::Payload;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::{Body, Builder, HttpResponse}; use httpresponse::{Body, HttpResponse, HttpResponseBuilder};
pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK); pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK);
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED); pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
@ -23,7 +23,7 @@ pub const HTTPInternalServerError: StaticResponse =
pub struct StaticResponse(StatusCode); pub struct StaticResponse(StatusCode);
impl StaticResponse { impl StaticResponse {
pub fn builder(&self) -> Builder { pub fn builder(&self) -> HttpResponseBuilder {
HttpResponse::builder(self.0) HttpResponse::builder(self.0)
} }
pub fn response(&self) -> HttpResponse { pub fn response(&self) -> HttpResponse {

View File

@ -62,8 +62,8 @@ pub struct HttpResponse {
impl HttpResponse { impl HttpResponse {
#[inline] #[inline]
pub fn builder(status: StatusCode) -> Builder { pub fn builder(status: StatusCode) -> HttpResponseBuilder {
Builder { HttpResponseBuilder {
parts: Some(Parts::new(status)), parts: Some(Parts::new(status)),
err: None, err: None,
} }
@ -224,12 +224,12 @@ impl Parts {
/// This type can be used to construct an instance of `HttpResponse` through a /// This type can be used to construct an instance of `HttpResponse` through a
/// builder-like pattern. /// builder-like pattern.
#[derive(Debug)] #[derive(Debug)]
pub struct Builder { pub struct HttpResponseBuilder {
parts: Option<Parts>, parts: Option<Parts>,
err: Option<Error>, err: Option<Error>,
} }
impl Builder { impl HttpResponseBuilder {
/// Get the HTTP version of this response. /// Get the HTTP version of this response.
#[inline] #[inline]
pub fn version(&mut self, version: Version) -> &mut Self { pub fn version(&mut self, version: Version) -> &mut Self {

View File

@ -48,11 +48,11 @@ pub mod ws;
pub mod dev; pub mod dev;
pub mod httpcodes; pub mod httpcodes;
pub use error::ParseError; pub use error::ParseError;
pub use application::Application; pub use application::{Application, ApplicationBuilder};
pub use httprequest::HttpRequest; pub use httprequest::HttpRequest;
pub use httpresponse::{Body, Builder, HttpResponse}; pub use httpresponse::{Body, HttpResponse, HttpResponseBuilder};
pub use payload::{Payload, PayloadItem, PayloadError}; pub use payload::{Payload, PayloadItem, PayloadError};
pub use router::RoutingMap; pub use router::{Router, RoutingMap};
pub use resource::{Reply, Resource}; pub use resource::{Reply, Resource};
pub use route::{Route, RouteFactory, RouteHandler}; pub use route::{Route, RouteFactory, RouteHandler};
pub use server::HttpServer; pub use server::HttpServer;
@ -60,6 +60,7 @@ pub use context::HttpContext;
pub use staticfiles::StaticFiles; pub use staticfiles::StaticFiles;
// re-exports // re-exports
pub use http::{Method, StatusCode};
pub use cookie::{Cookie, CookieBuilder}; pub use cookie::{Cookie, CookieBuilder};
pub use cookie::{ParseError as CookieParseError}; pub use cookie::{ParseError as CookieParseError};
pub use route_recognizer::Params; pub use route_recognizer::Params;

View File

@ -105,23 +105,23 @@ fn main() {
let sys = actix::System::new("http-example"); let sys = actix::System::new("http-example");
let mut routes = RoutingMap::default(); HttpServer::new(
RoutingMap::default()
let mut app = Application::default(); .app(
app.add("/test") "/blah", Application::default()
.get::<MyRoute>() .resource("/test", |r| {
.post::<MyRoute>(); r.get::<MyRoute>();
r.post::<MyRoute>();
routes.add("/blah", app); })
.finish())
routes.add_resource("/test") .resource("/test", |r| r.post::<MyRoute>())
.post::<MyRoute>(); .resource("/ws/", |r| r.get::<MyWS>())
.resource("/simple/", |r|
routes.add_resource("/ws/") r.handler(Method::GET, |req, payload, state| {
.get::<MyWS>(); httpcodes::HTTPOk
}))
let http = HttpServer::new(routes); .finish())
http.serve::<()>( .serve::<()>(
&net::SocketAddr::from_str("127.0.0.1:9080").unwrap()).unwrap(); &net::SocketAddr::from_str("127.0.0.1:9080").unwrap()).unwrap();
println!("starting"); println!("starting");

View File

@ -5,9 +5,10 @@ use std::collections::HashMap;
use actix::Actor; use actix::Actor;
use http::Method; use http::Method;
use futures::Stream;
use task::Task; use task::Task;
use route::{Route, RouteHandler}; use route::{Route, RouteHandler, Frame, FnHandler, StreamHandler};
use payload::Payload; use payload::Payload;
use context::HttpContext; use context::HttpContext;
use httprequest::HttpRequest; use httprequest::HttpRequest;
@ -26,10 +27,9 @@ use httpcodes::HTTPMethodNotAllowed;
/// struct MyRoute; /// struct MyRoute;
/// ///
/// fn main() { /// fn main() {
/// let mut routes = RoutingMap::default(); /// let router = RoutingMap::default()
/// /// .resource("/", |r| r.post::<MyRoute>())
/// routes.add_resource("/") /// .finish();
/// .post::<MyRoute>();
/// } /// }
pub struct Resource<S=()> { pub struct Resource<S=()> {
state: PhantomData<S>, state: PhantomData<S>,
@ -50,48 +50,62 @@ impl<S> Default for Resource<S> {
impl<S> Resource<S> where S: 'static { impl<S> Resource<S> where S: 'static {
/// Register handler for specified method. /// Register handler for specified method.
pub fn handler<H>(&mut self, method: Method, handler: H) -> &mut Self pub fn handler<F, R>(&mut self, method: Method, handler: F)
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Into<HttpResponse> + 'static,
{
self.routes.insert(method, Box::new(FnHandler::new(handler)));
}
/// Register async handler for specified method.
pub fn async<F, R>(&mut self, method: Method, handler: F)
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
{
self.routes.insert(method, Box::new(StreamHandler::new(handler)));
}
/// Register handler for specified method.
pub fn route_handler<H>(&mut self, method: Method, handler: H)
where H: RouteHandler<S> where H: RouteHandler<S>
{ {
self.routes.insert(method, Box::new(handler)); self.routes.insert(method, Box::new(handler));
self
} }
/// Default handler is used if no matched route found. /// Default handler is used if no matched route found.
/// By default `HTTPMethodNotAllowed` is used. /// By default `HTTPMethodNotAllowed` is used.
pub fn default_handler<H>(&mut self, handler: H) -> &mut Self pub fn default_handler<H>(&mut self, handler: H)
where H: RouteHandler<S> where H: RouteHandler<S>
{ {
self.default = Box::new(handler); self.default = Box::new(handler);
self
} }
/// Handler for `GET` method. /// Handler for `GET` method.
pub fn get<A>(&mut self) -> &mut Self pub fn get<A>(&mut self)
where A: Actor<Context=HttpContext<A>> + Route<State=S> where A: Actor<Context=HttpContext<A>> + Route<State=S>
{ {
self.handler(Method::GET, A::factory()) self.route_handler(Method::GET, A::factory());
} }
/// Handler for `POST` method. /// Handler for `POST` method.
pub fn post<A>(&mut self) -> &mut Self pub fn post<A>(&mut self)
where A: Actor<Context=HttpContext<A>> + Route<State=S> where A: Actor<Context=HttpContext<A>> + Route<State=S>
{ {
self.handler(Method::POST, A::factory()) self.route_handler(Method::POST, A::factory());
} }
/// Handler for `PUR` method. /// Handler for `PUR` method.
pub fn put<A>(&mut self) -> &mut Self pub fn put<A>(&mut self)
where A: Actor<Context=HttpContext<A>> + Route<State=S> where A: Actor<Context=HttpContext<A>> + Route<State=S>
{ {
self.handler(Method::PUT, A::factory()) self.route_handler(Method::PUT, A::factory());
} }
/// Handler for `METHOD` method. /// Handler for `METHOD` method.
pub fn delete<A>(&mut self) -> &mut Self pub fn delete<A>(&mut self)
where A: Actor<Context=HttpContext<A>> + Route<State=S> where A: Actor<Context=HttpContext<A>> + Route<State=S>
{ {
self.handler(Method::DELETE, A::factory()) self.route_handler(Method::DELETE, A::factory());
} }
} }

View File

@ -1,8 +1,10 @@
use std::io;
use std::rc::Rc; use std::rc::Rc;
use std::marker::PhantomData; use std::marker::PhantomData;
use actix::Actor; use actix::Actor;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream;
use task::Task; use task::Task;
use context::HttpContext; use context::HttpContext;
@ -59,3 +61,70 @@ impl<A, S> RouteHandler<S> for RouteFactory<A, S>
A::request(req, payload, &mut ctx).into(ctx) A::request(req, payload, &mut ctx).into(ctx)
} }
} }
/// Simple route handler
pub(crate)
struct FnHandler<S, R, F>
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Into<HttpResponse>,
S: 'static,
{
f: Box<F>,
s: PhantomData<S>,
}
impl<S, R, F> FnHandler<S, R, F>
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Into<HttpResponse> + 'static,
S: 'static,
{
pub fn new(f: F) -> Self {
FnHandler{f: Box::new(f), s: PhantomData}
}
}
impl<S, R, F> RouteHandler<S> for FnHandler<S, R, F>
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Into<HttpResponse> + 'static,
S: 'static,
{
fn handle(&self, req: HttpRequest, payload: Payload, state: Rc<S>) -> Task
{
Task::reply((self.f)(req, payload, &state).into())
}
}
/// Async route handler
pub(crate)
struct StreamHandler<S, R, F>
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
S: 'static,
{
f: Box<F>,
s: PhantomData<S>,
}
impl<S, R, F> StreamHandler<S, R, F>
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
S: 'static,
{
pub fn new(f: F) -> Self {
StreamHandler{f: Box::new(f), s: PhantomData}
}
}
impl<S, R, F> RouteHandler<S> for StreamHandler<S, R, F>
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
S: 'static,
{
fn handle(&self, req: HttpRequest, payload: Payload, state: Rc<S>) -> Task
{
Task::with_stream(
(self.f)(req, payload, &state).map_err(
|_| io::Error::new(io::ErrorKind::Other, ""))
)
}
}

View File

@ -15,7 +15,30 @@ pub(crate) trait Handler: 'static {
fn handle(&self, req: HttpRequest, payload: Payload) -> Task; fn handle(&self, req: HttpRequest, payload: Payload) -> Task;
} }
/// Request routing map /// Server routing map
pub struct Router {
apps: HashMap<String, Box<Handler>>,
resources: Recognizer<Resource>,
}
impl Router {
pub(crate) fn call(&self, req: HttpRequest, payload: Payload) -> Task
{
if let Ok(h) = self.resources.recognize(req.path()) {
h.handler.handle(req.with_params(h.params), payload, Rc::new(()))
} else {
for (prefix, app) in &self.apps {
if req.path().starts_with(prefix) {
return app.handle(req, payload)
}
}
Task::reply(HTTPNotFound.response())
}
}
}
/// Request routing map builder
/// ///
/// Route supports glob patterns: * for a single wildcard segment and :param /// Route supports glob patterns: * for a single wildcard segment and :param
/// for matching storing that segment of the request url in the Params object, /// for matching storing that segment of the request url in the Params object,
@ -25,11 +48,15 @@ pub(crate) trait Handler: 'static {
/// store userid and friend in the exposed Params object: /// store userid and friend in the exposed Params object:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// let mut router = RoutingMap::default(); /// let mut map = RoutingMap::default();
/// ///
/// router.add_resource("/users/:userid/:friendid").get::<MyRoute>(); /// map.resource("/users/:userid/:friendid", |r| r.get::<MyRoute>());
/// ``` /// ```
pub struct RoutingMap { pub struct RoutingMap {
parts: Option<RoutingMapParts>,
}
struct RoutingMapParts {
apps: HashMap<String, Box<Handler>>, apps: HashMap<String, Box<Handler>>,
resources: HashMap<String, Resource>, resources: HashMap<String, Resource>,
} }
@ -37,8 +64,9 @@ pub struct RoutingMap {
impl Default for RoutingMap { impl Default for RoutingMap {
fn default() -> Self { fn default() -> Self {
RoutingMap { RoutingMap {
parts: Some(RoutingMapParts {
apps: HashMap::new(), apps: HashMap::new(),
resources: HashMap::new() resources: HashMap::new()}),
} }
} }
} }
@ -53,92 +81,81 @@ impl RoutingMap {
/// struct MyRoute; /// struct MyRoute;
/// ///
/// fn main() { /// fn main() {
/// let mut app = Application::default(); /// let mut router =
/// app.add("/test") /// RoutingMap::default()
/// .get::<MyRoute>() /// .app("/pre", Application::default()
/// .post::<MyRoute>(); /// .resource("/test", |r| {
/// /// r.get::<MyRoute>();
/// let mut routes = RoutingMap::default(); /// r.post::<MyRoute>();
/// routes.add("/pre", app); /// })
/// .finish()
/// ).finish();
/// } /// }
/// ``` /// ```
/// In this example, `MyRoute` route is available as `http://.../pre/test` url. /// In this example, `MyRoute` route is available as `http://.../pre/test` url.
pub fn add<P, S: 'static>(&mut self, prefix: P, app: Application<S>) pub fn app<P, S: 'static>(&mut self, prefix: P, app: Application<S>) -> &mut Self
where P: ToString where P: ToString
{ {
let prefix = prefix.to_string(); {
let parts = self.parts.as_mut().expect("Use after finish");
// we can not override registered resource // we can not override registered resource
if self.apps.contains_key(&prefix) { let prefix = prefix.to_string();
if parts.apps.contains_key(&prefix) {
panic!("Resource is registered: {}", prefix); panic!("Resource is registered: {}", prefix);
} }
// add application // add application
self.apps.insert(prefix.clone(), app.prepare(prefix)); parts.apps.insert(prefix.clone(), app.prepare(prefix));
}
self
} }
/// This method creates `Resource` for specified path /// Configure resource for specific path.
/// or returns mutable reference to resource object.
/// ///
/// ```rust,ignore /// ```rust,ignore
/// ///
/// struct MyRoute; /// struct MyRoute;
/// ///
/// fn main() { /// fn main() {
/// let mut routes = RoutingMap::default(); /// RoutingMap::default().resource("/test", |r| {
/// /// r.post::<MyRoute>();
/// routes.add_resource("/test") /// }).finish();
/// .post::<MyRoute>();
/// } /// }
/// ``` /// ```
/// In this example, `MyRoute` route is available as `http://.../test` url. /// In this example, `MyRoute` route is available as `http://.../test` url.
pub fn add_resource<P>(&mut self, path: P) -> &mut Resource pub fn resource<P, F>(&mut self, path: P, f: F) -> &mut Self
where P: ToString where F: FnOnce(&mut Resource<()>) + 'static,
P: ToString,
{ {
let path = path.to_string(); {
let parts = self.parts.as_mut().expect("Use after finish");
// add resource // add resource
if !self.resources.contains_key(&path) { let path = path.to_string();
self.resources.insert(path.clone(), Resource::default()); if !parts.resources.contains_key(&path) {
parts.resources.insert(path.clone(), Resource::default());
}
// configure resource
f(parts.resources.get_mut(&path).unwrap());
}
self
} }
self.resources.get_mut(&path).unwrap() /// Finish configuration and create `Router` instance
} pub fn finish(&mut self) -> Router
{
let parts = self.parts.take().expect("Use after finish");
pub(crate) fn into_router(self) -> Router {
let mut router = Recognizer::new(); let mut router = Recognizer::new();
for (path, resource) in self.resources { for (path, resource) in parts.resources {
router.add(path.as_str(), resource); router.add(path.as_str(), resource);
} }
Router { Router {
apps: self.apps, apps: parts.apps,
resources: router, resources: router,
} }
} }
} }
pub(crate)
struct Router {
apps: HashMap<String, Box<Handler>>,
resources: Recognizer<Resource>,
}
impl Router {
pub fn call(&self, req: HttpRequest, payload: Payload) -> Task
{
if let Ok(h) = self.resources.recognize(req.path()) {
h.handler.handle(req.with_params(h.params), payload, Rc::new(()))
} else {
for (prefix, app) in &self.apps {
if req.path().starts_with(prefix) {
return app.handle(req, payload)
}
}
Task::reply(HTTPNotFound.response())
}
}
}

View File

@ -9,8 +9,8 @@ use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpListener, TcpStream}; use tokio_core::net::{TcpListener, TcpStream};
use task::{Task, RequestInfo}; use task::{Task, RequestInfo};
use router::Router;
use reader::{Reader, ReaderError}; use reader::{Reader, ReaderError};
use router::{Router, RoutingMap};
/// An HTTP Server /// An HTTP Server
pub struct HttpServer { pub struct HttpServer {
@ -23,8 +23,8 @@ impl Actor for HttpServer {
impl HttpServer { impl HttpServer {
/// Create new http server with specified `RoutingMap` /// Create new http server with specified `RoutingMap`
pub fn new(routes: RoutingMap) -> Self { pub fn new(router: Router) -> Self {
HttpServer {router: Rc::new(routes.into_router())} HttpServer {router: Rc::new(router)}
} }
/// Start listening for incomming connections. /// Start listening for incomming connections.