use std::cell::RefCell; use std::rc::Rc; use actix_http::{Error, Response}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::dev::{insert_slash, HttpServiceFactory, ResourceDef, ServiceConfig}; use crate::extract::FromRequest; use crate::guard::Guard; use crate::handler::{AsyncFactory, Factory}; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; type HttpService
= BoxedService =
BoxedNewService<(), ServiceRequest , ServiceResponse, Error, ()>;
/// *Resource* is an entry in resources table which corresponds to requested URL.
///
/// Resource in turn has at least one route.
/// Route consists of an handlers objects and list of guards
/// (objects that implement `Guard` trait).
/// Resources and rouets uses builder-like pattern for configuration.
/// During request handling, resource object iterate through all routes
/// and check guards for specific route, if request matches all
/// guards, route considered matched and route handler get called.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/")
/// .route(web::get().to(|| HttpResponse::Ok())));
/// }
/// ```
///
/// If no matching route could be found, *405* response code get returned.
/// Default behavior could be overriden with `default_resource()` method.
pub struct Resource > {
endpoint: T,
rdef: String,
name: Option Resource {
pub fn new(path: &str) -> Resource {
let fref = Rc::new(RefCell::new(None));
Resource {
routes: Vec::new(),
rdef: path.to_string(),
name: None,
endpoint: ResourceEndpoint::new(fref.clone()),
factory_ref: fref,
guards: Vec::new(),
default: Rc::new(RefCell::new(None)),
}
}
}
impl Resource
where
P: 'static,
T: NewService<
Request = ServiceRequest ,
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
{
/// Set resource name.
///
/// Name is used for url generation.
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
/// Add match guard to a resource.
///
/// ```rust
/// use actix_web::{web, guard, App, HttpResponse};
///
/// fn index(data: web::Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::resource("/app")
/// .guard(guard::Header("content-type", "text/plain"))
/// .route(web::get().to(index))
/// )
/// .service(
/// web::resource("/app")
/// .guard(guard::Header("content-type", "text/json"))
/// .route(web::get().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn guard ) -> Self {
self.routes.push(route.finish());
self
}
/// Register a new route and add handler. This route matches all requests.
///
/// ```rust
/// use actix_web::*;
///
/// fn index(req: HttpRequest) -> HttpResponse {
/// unimplemented!()
/// }
///
/// App::new().service(web::resource("/").to(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().service(web::resource("/").route(web::route().to(index)));
/// ```
pub fn to + 'static,
R: Responder + 'static,
{
self.routes.push(Route::new().to(handler));
self
}
/// Register a new route and add async handler.
///
/// ```rust
/// use actix_web::*;
/// use futures::future::{ok, Future};
///
/// fn index(req: HttpRequest) -> impl Future + 'static,
R: IntoFuture + 'static,
R::Item: Into ,
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
T::Service,
Request = ServiceRequest ,
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
F: IntoTransform ) -> R,
R: IntoNewService,
U: NewService<
Request = ServiceRequest ,
Response = ServiceResponse,
Error = Error,
> + 'static,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
f(Resource::new("")).into_new_service().map_init_err(|_| ()),
)))));
self
}
}
impl HttpServiceFactory for Resource
where
P: 'static,
T: NewService<
Request = ServiceRequest ,
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
fn register(mut self, config: &mut ServiceConfig ) {
let guards = if self.guards.is_empty() {
None
} else {
Some(std::mem::replace(&mut self.guards, Vec::new()))
};
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
ResourceDef::new(&insert_slash(&self.rdef))
} else {
ResourceDef::new(&self.rdef)
};
if let Some(ref name) = self.name {
*rdef.name_mut() = name.clone();
}
config.register_service(rdef, guards, self, None)
}
}
impl IntoNewService
where
T: NewService<
Request = ServiceRequest ,
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
{
fn into_new_service(self) -> T {
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes,
default: self.default,
});
self.endpoint
}
}
pub struct ResourceFactory {
routes: Vec {
type Request = ServiceRequest ;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = ResourceService ;
type Future = CreateResourceService ;
fn new_service(&self, _: &()) -> Self::Future {
let default_fut = if let Some(ref default) = *self.default.borrow() {
Some(default.new_service(&()))
} else {
None
};
CreateResourceService {
fut: self
.routes
.iter()
.map(|route| CreateRouteServiceItem::Future(route.new_service(&())))
.collect(),
default: None,
default_fut,
}
}
}
enum CreateRouteServiceItem {
Future(CreateRouteService ),
Service(RouteService ),
}
pub struct CreateResourceService {
fut: Vec Future for CreateResourceService {
type Item = ResourceService ;
type Error = ();
fn poll(&mut self) -> Poll {
routes: Vec Service for ResourceService {
type Request = ServiceRequest ;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
Box ) -> Self::Future {
for route in self.routes.iter_mut() {
if route.check(&mut req) {
return Either::A(route.call(req));
}
}
if let Some(ref mut default) = self.default {
Either::B(Either::A(default.call(req)))
} else {
let req = req.into_request();
Either::B(Either::B(ok(ServiceResponse::new(
req,
Response::MethodNotAllowed().finish(),
))))
}
}
}
#[doc(hidden)]
pub struct ResourceEndpoint {
factory: Rc ResourceEndpoint {
fn new(factory: Rc {
type Request = ServiceRequest ;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = ResourceService ;
type Future = CreateResourceService ;
fn new_service(&self, _: &()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
}
}
#[cfg(test)]
mod tests {
use crate::http::{Method, StatusCode};
use crate::test::{call_success, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
fn test_default_resource() {
let mut srv = init_service(
App::new()
.service(
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
)
.default_resource(|r| r.to(|| HttpResponse::BadRequest())),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_success(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let mut srv = init_service(
App::new().service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok()))
.default_resource(|r| r.to(|| HttpResponse::BadRequest())),
),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_success(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}