1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-05-19 16:58:14 +00:00
actix-web/actix-web/src/resource.rs

873 lines
28 KiB
Rust
Raw Normal View History

use std::{cell::RefCell, fmt, future::Future, rc::Rc};
2017-10-07 04:48:14 +00:00
use actix_http::Extensions;
2021-08-06 21:42:31 +00:00
use actix_router::{IntoPatterns, Patterns};
2019-03-02 06:51:32 +00:00
use actix_service::{
2021-12-04 19:40:47 +00:00
apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
ServiceFactoryExt, Transform,
2019-03-02 06:51:32 +00:00
};
use futures_core::future::LocalBoxFuture;
2021-01-09 03:36:58 +00:00
use futures_util::future::join_all;
2017-10-07 04:48:14 +00:00
2021-06-17 16:57:58 +00:00
use crate::{
2021-12-04 19:40:47 +00:00
body::MessageBody,
2021-06-17 16:57:58 +00:00
data::Data,
2021-12-04 19:40:47 +00:00
dev::{ensure_leading_slash, AppService, ResourceDef},
guard::{self, Guard},
2021-06-17 16:57:58 +00:00
handler::Handler,
http::header,
2021-06-17 16:57:58 +00:00
route::{Route, RouteService},
2021-12-04 19:40:47 +00:00
service::{
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
ServiceResponse,
},
2023-03-02 08:22:22 +00:00
web, Error, FromRequest, HttpResponse, Responder,
2021-06-17 16:57:58 +00:00
};
2018-06-25 04:58:04 +00:00
/// A collection of [`Route`]s that respond to the same path pattern.
2017-10-08 06:59:57 +00:00
///
/// 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 routes 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.
2019-03-03 22:45:56 +00:00
///
/// # Examples
/// ```
2019-03-03 22:45:56 +00:00
/// use actix_web::{web, App, HttpResponse};
///
/// let app = App::new().service(
/// web::resource("/")
2023-03-02 08:22:22 +00:00
/// .get(|| HttpResponse::Ok())
/// .post(|| async { "Hello World!" })
/// );
/// ```
///
2023-03-02 08:22:22 +00:00
/// If no matching route is found, an empty 405 response is returned which includes an
/// [appropriate Allow header][RFC 9110 §15.5.6]. This default behavior can be overridden using
/// [`default_service()`](Self::default_service).
///
/// [RFC 9110 §15.5.6]: https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6
pub struct Resource<T = ResourceEndpoint> {
2019-03-02 06:51:32 +00:00
endpoint: T,
2021-08-06 21:42:31 +00:00
rdef: Patterns,
name: Option<String>,
routes: Vec<Route>,
2021-01-09 03:36:58 +00:00
app_data: Option<Extensions>,
2019-07-17 09:48:37 +00:00
guards: Vec<Box<dyn Guard>>,
2021-12-04 19:40:47 +00:00
default: BoxedHttpServiceFactory,
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
2017-10-07 04:48:14 +00:00
}
impl Resource {
2023-03-02 08:22:22 +00:00
/// Constructs new resource that matches a `path` pattern.
2021-08-06 21:42:31 +00:00
pub fn new<T: IntoPatterns>(path: T) -> Resource {
2019-03-02 06:51:32 +00:00
let fref = Rc::new(RefCell::new(None));
2018-07-12 09:30:01 +00:00
Resource {
2019-03-02 06:51:32 +00:00
routes: Vec::new(),
rdef: path.patterns(),
name: None,
2019-03-02 06:51:32 +00:00
endpoint: ResourceEndpoint::new(fref.clone()),
factory_ref: fref,
guards: Vec::new(),
2021-01-09 03:36:58 +00:00
app_data: None,
default: boxed::factory(fn_service(|req: ServiceRequest| async {
use crate::HttpMessage as _;
let allowed = req.extensions().get::<guard::RegisteredMethods>().cloned();
if let Some(methods) = allowed {
Ok(req.into_response(
HttpResponse::MethodNotAllowed()
.insert_header(header::Allow(methods.0))
.finish(),
))
} else {
Ok(req.into_response(HttpResponse::MethodNotAllowed()))
}
2021-01-09 03:36:58 +00:00
})),
2018-04-13 23:02:01 +00:00
}
2017-10-07 04:48:14 +00:00
}
2019-03-02 06:51:32 +00:00
}
2017-10-07 04:48:14 +00:00
impl<T> Resource<T>
2019-03-02 06:51:32 +00:00
where
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
2019-03-02 06:51:32 +00:00
{
/// 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.
///
/// ```
/// use actix_web::{web, guard, App, HttpResponse};
///
2019-11-21 15:34:04 +00:00
/// async fn index(data: web::Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
2022-02-22 12:32:06 +00:00
/// 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<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Box::new(guard));
self
}
2019-07-17 09:48:37 +00:00
pub(crate) fn add_guards(mut self, guards: Vec<Box<dyn Guard>>) -> Self {
self.guards.extend(guards);
self
}
2019-03-03 20:09:38 +00:00
/// Register a new route.
2017-12-04 21:32:05 +00:00
///
/// ```
2019-03-03 20:09:38 +00:00
/// use actix_web::{web, guard, App, HttpResponse};
2017-12-04 21:32:05 +00:00
///
2021-12-25 04:53:51 +00:00
/// let app = App::new().service(
/// web::resource("/").route(
/// web::route()
/// .guard(guard::Any(guard::Get()).or(guard::Put()))
/// .guard(guard::Header("Content-Type", "text/plain"))
/// .to(|| HttpResponse::Ok()))
/// );
/// ```
2019-03-03 22:45:56 +00:00
///
/// Multiple routes could be added to a resource. Resource object uses
/// match guards for route selection.
2019-03-03 22:45:56 +00:00
///
/// ```
/// use actix_web::{web, guard, App};
2019-03-03 22:45:56 +00:00
///
2022-02-22 12:32:06 +00:00
/// let app = App::new().service(
/// web::resource("/container/")
/// .route(web::get().to(get_handler))
/// .route(web::post().to(post_handler))
/// .route(web::delete().to(delete_handler))
/// );
///
/// # async fn get_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
/// # async fn post_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
/// # async fn delete_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
2019-03-03 22:45:56 +00:00
/// ```
pub fn route(mut self, route: Route) -> Self {
self.routes.push(route);
self
}
2021-06-24 14:10:51 +00:00
/// Add resource data.
///
/// Data of different types from parent contexts will still be accessible. Any `Data<T>` types
/// set here can be extracted in handlers using the `Data<T>` extractor.
///
2021-06-24 14:10:51 +00:00
/// # Examples
/// ```
2021-06-24 14:10:51 +00:00
/// use std::cell::Cell;
/// use actix_web::{web, App, HttpRequest, HttpResponse, Responder};
///
2021-06-24 14:10:51 +00:00
/// struct MyData {
/// count: std::cell::Cell<usize>,
/// }
///
2021-06-24 14:10:51 +00:00
/// async fn handler(req: HttpRequest, counter: web::Data<MyData>) -> impl Responder {
/// // note this cannot use the Data<T> extractor because it was not added with it
/// let incr = *req.app_data::<usize>().unwrap();
/// assert_eq!(incr, 3);
///
/// // update counter using other value from app data
/// counter.count.set(counter.count.get() + incr);
///
/// HttpResponse::Ok().body(counter.count.get().to_string())
/// }
///
2021-06-24 14:10:51 +00:00
/// let app = App::new().service(
/// web::resource("/")
/// .app_data(3usize)
/// .app_data(web::Data::new(MyData { count: Default::default() }))
/// .route(web::get().to(handler))
/// );
/// ```
2021-12-30 07:11:35 +00:00
#[doc(alias = "manage")]
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
self.app_data
.get_or_insert_with(Extensions::new)
.insert(data);
2019-03-02 06:51:32 +00:00
self
}
2017-10-07 04:48:14 +00:00
2021-06-24 14:10:51 +00:00
/// Add resource data after wrapping in `Data<T>`.
///
/// Deprecated in favor of [`app_data`](Self::app_data).
#[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
pub fn data<U: 'static>(self, data: U) -> Self {
self.app_data(Data::new(data))
}
/// Register a new route and add handler. This route matches all requests.
2018-03-27 06:10:31 +00:00
///
/// ```
/// use actix_web::{App, HttpRequest, HttpResponse, web};
2018-06-01 22:57:24 +00:00
///
/// async fn index(req: HttpRequest) -> HttpResponse {
/// todo!()
2019-03-03 20:09:38 +00:00
/// }
///
/// App::new().service(web::resource("/").to(index));
2018-06-01 22:57:24 +00:00
/// ```
///
2018-03-27 06:10:31 +00:00
/// This is shortcut for:
///
/// ```
/// # use actix_web::*;
/// # async fn index(req: HttpRequest) -> HttpResponse { todo!() }
/// App::new().service(web::resource("/").route(web::route().to(index)));
2018-03-27 06:10:31 +00:00
/// ```
2021-12-27 00:44:30 +00:00
pub fn to<F, Args>(mut self, handler: F) -> Self
2018-04-13 23:02:01 +00:00
where
2021-12-27 00:44:30 +00:00
F: Handler<Args>,
2021-12-25 04:43:59 +00:00
Args: FromRequest + 'static,
2021-12-27 00:44:30 +00:00
F::Output: Responder + 'static,
{
2019-11-21 15:34:04 +00:00
self.routes.push(Route::new().to(handler));
2019-03-02 06:51:32 +00:00
self
}
/// Registers a resource middleware.
2018-01-10 04:00:18 +00:00
///
/// `mw` is a middleware component (type), that can modify the request and response across all
/// routes managed by this `Resource`.
///
/// See [`App::wrap`](crate::App::wrap) for more details.
#[doc(alias = "middleware")]
#[doc(alias = "use")] // nodejs terminology
pub fn wrap<M, B>(
2019-03-02 06:51:32 +00:00
self,
2019-11-20 17:33:22 +00:00
mw: M,
2019-03-02 06:51:32 +00:00
) -> Resource<
2019-11-20 17:33:22 +00:00
impl ServiceFactory<
ServiceRequest,
2019-05-12 15:34:51 +00:00
Config = (),
Response = ServiceResponse<B>,
Error = Error,
2019-03-02 06:51:32 +00:00
InitError = (),
>,
>
where
2019-03-05 05:37:57 +00:00
M: Transform<
T::Service,
ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
B: MessageBody,
2019-03-02 06:51:32 +00:00
{
Resource {
2019-11-20 17:33:22 +00:00
endpoint: apply(mw, self.endpoint),
rdef: self.rdef,
name: self.name,
guards: self.guards,
2019-03-02 06:51:32 +00:00
routes: self.routes,
default: self.default,
2021-01-09 03:36:58 +00:00
app_data: self.app_data,
2019-03-02 06:51:32 +00:00
factory_ref: self.factory_ref,
}
2018-01-10 04:00:18 +00:00
}
/// Registers a resource function middleware.
///
/// `mw` is a closure that runs during inbound and/or outbound processing in the request
/// life-cycle (request -> response), modifying request/response as necessary, across all
/// requests handled by the `Resource`.
///
/// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details.
#[doc(alias = "middleware")]
#[doc(alias = "use")] // nodejs terminology
pub fn wrap_fn<F, R, B>(
self,
mw: F,
) -> Resource<
2019-11-20 17:33:22 +00:00
impl ServiceFactory<
ServiceRequest,
2019-05-12 15:34:51 +00:00
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>
where
F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
R: Future<Output = Result<ServiceResponse<B>, Error>>,
B: MessageBody,
{
2019-11-20 17:33:22 +00:00
Resource {
endpoint: apply_fn_factory(self.endpoint, mw),
rdef: self.rdef,
name: self.name,
guards: self.guards,
routes: self.routes,
default: self.default,
2021-01-09 03:36:58 +00:00
app_data: self.app_data,
2019-11-20 17:33:22 +00:00
factory_ref: self.factory_ref,
}
}
/// Sets the default service to be used if no matching route is found.
///
/// Unlike [`Scope`]s, a `Resource` does _not_ inherit its parent's default service. You can
/// use a [`Route`] as default service.
///
/// If a custom default service is not registered, an empty `405 Method Not Allowed` response
/// with an appropriate Allow header will be sent instead.
///
/// # Examples
/// ```
/// use actix_web::{App, HttpResponse, web};
///
/// let resource = web::resource("/test")
/// .route(web::get().to(HttpResponse::Ok))
/// .default_service(web::to(|| {
/// HttpResponse::BadRequest()
/// }));
///
/// App::new().service(resource);
/// ```
2022-01-28 20:53:51 +00:00
///
/// [`Scope`]: crate::Scope
pub fn default_service<F, U>(mut self, f: F) -> Self
2019-03-02 06:51:32 +00:00
where
F: IntoServiceFactory<U, ServiceRequest>,
2023-07-17 01:38:12 +00:00
U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
+ 'static,
U::InitError: fmt::Debug,
2019-03-02 06:51:32 +00:00
{
// create and configure default resource
2021-02-11 23:03:17 +00:00
self.default = boxed::factory(
f.into_factory()
.map_init_err(|e| log::error!("Can not construct default service: {:?}", e)),
);
2019-03-02 06:51:32 +00:00
self
}
}
2019-03-02 06:51:32 +00:00
2023-03-02 08:22:22 +00:00
macro_rules! route_shortcut {
($method_fn:ident, $method_upper:literal) => {
#[doc = concat!(" Adds a ", $method_upper, " route.")]
///
/// Use [`route`](Self::route) if you need to add additional guards.
///
/// # Examples
///
/// ```
/// # use actix_web::web;
/// web::resource("/")
#[doc = concat!(" .", stringify!($method_fn), "(|| async { \"Hello World!\" })")]
/// # ;
/// ```
pub fn $method_fn<F, Args>(self, handler: F) -> Self
where
F: Handler<Args>,
Args: FromRequest + 'static,
F::Output: Responder + 'static,
{
self.route(web::$method_fn().to(handler))
}
};
}
/// Concise routes for well-known HTTP methods.
impl<T> Resource<T>
where
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
{
route_shortcut!(get, "GET");
route_shortcut!(post, "POST");
route_shortcut!(put, "PUT");
route_shortcut!(patch, "PATCH");
route_shortcut!(delete, "DELETE");
route_shortcut!(head, "HEAD");
route_shortcut!(trace, "TRACE");
}
impl<T, B> HttpServiceFactory for Resource<T>
where
2019-11-20 17:33:22 +00:00
T: ServiceFactory<
ServiceRequest,
2019-05-12 15:34:51 +00:00
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
> + 'static,
B: MessageBody + 'static,
{
2019-04-15 14:32:49 +00:00
fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() {
None
} else {
2020-05-17 01:54:42 +00:00
Some(std::mem::take(&mut self.guards))
};
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
2021-08-06 21:42:31 +00:00
ResourceDef::new(ensure_leading_slash(self.rdef.clone()))
} else {
ResourceDef::new(self.rdef.clone())
};
if let Some(ref name) = self.name {
2021-08-06 21:42:31 +00:00
rdef.set_name(name);
}
2019-03-02 06:51:32 +00:00
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes,
default: self.default,
});
let resource_data = self.app_data.map(Rc::new);
// wraps endpoint service (including middleware) call and injects app data for this scope
let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| {
if let Some(ref data) = resource_data {
req.add_data_container(Rc::clone(data));
}
let fut = srv.call(req);
async { Ok(fut.await?.map_into_boxed_body()) }
});
config.register_service(rdef, guards, endpoint, None)
2019-03-02 06:51:32 +00:00
}
}
pub struct ResourceFactory {
routes: Vec<Route>,
2021-12-04 19:40:47 +00:00
default: BoxedHttpServiceFactory,
2019-03-02 06:51:32 +00:00
}
impl ServiceFactory<ServiceRequest> for ResourceFactory {
2019-03-02 06:51:32 +00:00
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = ResourceService;
type InitError = ();
2021-01-09 03:36:58 +00:00
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
2019-03-02 06:51:32 +00:00
2019-12-02 15:37:13 +00:00
fn new_service(&self, _: ()) -> Self::Future {
2021-01-09 03:36:58 +00:00
// construct default service factory future.
let default_fut = self.default.new_service(());
2019-03-02 06:51:32 +00:00
2021-01-09 03:36:58 +00:00
// construct route service factory futures
2021-02-11 23:03:17 +00:00
let factory_fut = join_all(self.routes.iter().map(|route| route.new_service(())));
2019-03-02 06:51:32 +00:00
2021-01-09 03:36:58 +00:00
Box::pin(async move {
let default = default_fut.await?;
let routes = factory_fut
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
2019-03-02 06:51:32 +00:00
Ok(ResourceService { routes, default })
2021-01-09 03:36:58 +00:00
})
2017-10-07 04:48:14 +00:00
}
}
2018-07-15 09:12:21 +00:00
pub struct ResourceService {
routes: Vec<RouteService>,
2021-12-04 19:40:47 +00:00
default: BoxedHttpService,
2019-03-02 06:51:32 +00:00
}
impl Service<ServiceRequest> for ResourceService {
2019-03-02 06:51:32 +00:00
type Response = ServiceResponse;
type Error = Error;
2021-01-09 03:36:58 +00:00
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
2018-07-15 09:12:21 +00:00
2021-01-09 03:36:58 +00:00
actix_service::always_ready!();
2018-07-15 09:12:21 +00:00
fn call(&self, mut req: ServiceRequest) -> Self::Future {
for route in &self.routes {
2019-03-02 06:51:32 +00:00
if route.check(&mut req) {
return route.call(req);
2019-03-02 06:51:32 +00:00
}
}
2021-01-09 03:36:58 +00:00
self.default.call(req)
2018-07-15 09:12:21 +00:00
}
}
2019-03-02 06:51:32 +00:00
#[doc(hidden)]
pub struct ResourceEndpoint {
factory: Rc<RefCell<Option<ResourceFactory>>>,
2019-03-02 06:51:32 +00:00
}
impl ResourceEndpoint {
fn new(factory: Rc<RefCell<Option<ResourceFactory>>>) -> Self {
2019-03-02 06:51:32 +00:00
ResourceEndpoint { factory }
2018-07-15 09:12:21 +00:00
}
}
impl ServiceFactory<ServiceRequest> for ResourceEndpoint {
2019-03-02 06:51:32 +00:00
type Response = ServiceResponse;
type Error = Error;
2021-01-09 03:36:58 +00:00
type Config = ();
type Service = ResourceService;
2021-01-09 03:36:58 +00:00
type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
2019-03-02 06:51:32 +00:00
2019-12-02 15:37:13 +00:00
fn new_service(&self, _: ()) -> Self::Future {
2021-01-09 03:36:58 +00:00
self.factory.borrow().as_ref().unwrap().new_service(())
2018-07-15 09:12:21 +00:00
}
}
#[cfg(test)]
mod tests {
2019-03-25 21:33:34 +00:00
use std::time::Duration;
use actix_rt::time::sleep;
use actix_service::Service;
2021-04-01 14:26:13 +00:00
use actix_utils::future::ok;
use super::*;
use crate::{
guard,
http::{
header::{self, HeaderValue},
Method, StatusCode,
},
middleware::DefaultHeaders,
service::{ServiceRequest, ServiceResponse},
test::{call_service, init_service, TestRequest},
web, App, Error, HttpMessage, HttpResponse,
};
#[test]
fn can_be_returned_from_fn() {
fn my_resource_1() -> Resource {
web::resource("/test1").route(web::get().to(|| async { "hello" }))
}
fn my_resource_2() -> Resource<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<impl MessageBody>,
Error = Error,
InitError = (),
>,
> {
web::resource("/test2")
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async { Ok(fut.await?.map_into_right_body::<()>()) }
})
.route(web::get().to(|| async { "hello" }))
}
fn my_resource_3() -> impl HttpServiceFactory {
web::resource("/test3").route(web::get().to(|| async { "hello" }))
}
App::new()
.service(my_resource_1())
.service(my_resource_2())
.service(my_resource_3());
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_middleware() {
2021-02-11 23:03:17 +00:00
let srv = init_service(
App::new().service(
web::resource("/test")
.name("test")
.wrap(
DefaultHeaders::new()
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
2021-02-11 23:03:17 +00:00
)
.route(web::get().to(HttpResponse::Ok)),
),
)
.await;
2019-11-26 05:25:50 +00:00
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_middleware_fn() {
let srv = init_service(
2019-11-26 05:25:50 +00:00
App::new().service(
web::resource("/test")
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async {
fut.await.map(|mut res| {
2023-07-17 01:38:12 +00:00
res.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
2019-11-26 05:25:50 +00:00
res
})
}
})
.route(web::get().to(HttpResponse::Ok)),
2019-11-26 05:25:50 +00:00
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_to() {
2021-02-11 23:03:17 +00:00
let srv = init_service(App::new().service(web::resource("/test").to(|| async {
sleep(Duration::from_millis(100)).await;
Ok::<_, Error>(HttpResponse::Ok())
})))
.await;
2019-11-26 05:25:50 +00:00
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
2019-03-25 21:33:34 +00:00
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_pattern() {
2023-07-17 01:38:12 +00:00
let srv = init_service(App::new().service(
web::resource(["/test", "/test2"]).to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }),
))
2019-12-25 16:17:22 +00:00
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
2019-11-26 05:25:50 +00:00
async fn test_default_resource() {
let srv = init_service(
2019-11-26 05:25:50 +00:00
App::new()
.service(
web::resource("/test")
.route(web::get().to(HttpResponse::Ok))
.route(web::delete().to(HttpResponse::Ok)),
)
2019-11-26 05:25:50 +00:00
.default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::BadRequest()))
}),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
assert_eq!(
resp.headers().get(header::ALLOW).unwrap().as_bytes(),
b"GET, DELETE"
);
2019-11-26 05:25:50 +00:00
let srv = init_service(
2019-11-26 05:25:50 +00:00
App::new().service(
web::resource("/test")
.route(web::get().to(HttpResponse::Ok))
.default_service(|r: ServiceRequest| {
2019-11-20 17:33:22 +00:00
ok(r.into_response(HttpResponse::BadRequest()))
}),
2019-11-26 05:25:50 +00:00
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
2019-05-15 17:31:40 +00:00
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_resource_guards() {
let srv = init_service(
2019-11-26 05:25:50 +00:00
App::new()
.service(
web::resource("/test/{p}")
.guard(guard::Get())
.to(HttpResponse::Ok),
2019-11-26 05:25:50 +00:00
)
.service(
web::resource("/test/{p}")
.guard(guard::Put())
.to(HttpResponse::Created),
2019-11-26 05:25:50 +00:00
)
.service(
web::resource("/test/{p}")
.guard(guard::Delete())
.to(HttpResponse::NoContent),
2019-11-26 05:25:50 +00:00
),
)
.await;
let req = TestRequest::with_uri("/test/it")
.method(Method::GET)
.to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/it")
.method(Method::PUT)
.to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test/it")
.method(Method::DELETE)
.to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
2019-05-15 17:31:40 +00:00
}
2021-06-24 14:10:51 +00:00
// allow deprecated `{App, Resource}::data`
#[allow(deprecated)]
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_data() {
let srv = init_service(
2019-11-26 05:25:50 +00:00
App::new()
.data(1.0f64)
.data(1usize)
.app_data(web::Data::new('-'))
2019-11-26 05:25:50 +00:00
.service(
web::resource("/test")
.data(10usize)
.app_data(web::Data::new('*'))
2019-11-26 05:25:50 +00:00
.guard(guard::Get())
.to(
|data1: web::Data<usize>,
data2: web::Data<char>,
data3: web::Data<f64>| {
2019-12-20 11:45:35 +00:00
assert_eq!(**data1, 10);
assert_eq!(**data2, '*');
let error = std::f64::EPSILON;
assert!((**data3 - 1.0).abs() < error);
2019-11-26 05:25:50 +00:00
HttpResponse::Ok()
},
),
),
)
.await;
2019-11-20 17:33:22 +00:00
2019-11-26 05:25:50 +00:00
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&srv, req).await;
2019-11-26 05:25:50 +00:00
assert_eq!(resp.status(), StatusCode::OK);
}
2021-06-24 14:10:51 +00:00
// allow deprecated `{App, Resource}::data`
#[allow(deprecated)]
#[actix_rt::test]
async fn test_data_default_service() {
2023-07-17 01:38:12 +00:00
let srv =
init_service(
App::new().data(1usize).service(
web::resource("/test")
.data(10usize)
.default_service(web::to(|data: web::Data<usize>| {
assert_eq!(**data, 10);
HttpResponse::Ok()
})),
),
)
.await;
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_middleware_app_data() {
let srv = init_service(
App::new().service(
web::resource("test")
.app_data(1usize)
.wrap_fn(|req, srv| {
assert_eq!(req.app_data::<usize>(), Some(&1usize));
req.extensions_mut().insert(1usize);
srv.call(req)
})
.route(web::get().to(HttpResponse::Ok))
.default_service(|req: ServiceRequest| async move {
let (req, _) = req.into_parts();
assert_eq!(req.extensions().get::<usize>(), Some(&1));
Ok(ServiceResponse::new(
req,
HttpResponse::BadRequest().finish(),
))
}),
),
)
.await;
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::post().uri("/test").to_request();
let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_middleware_body_type() {
let srv = init_service(
App::new().service(
web::resource("/test")
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async { Ok(fut.await?.map_into_right_body::<()>()) }
})
.route(web::get().to(|| async { "hello" })),
),
)
.await;
// test if `try_into_bytes()` is preserved across scope layer
use actix_http::body::MessageBody as _;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
let body = resp.into_body();
assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref());
}
}