1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-30 10:42:55 +01:00

allow any body type in Resource (#2526)

This commit is contained in:
Rob Ede 2021-12-22 15:00:32 +00:00 committed by GitHub
parent 1769812d0b
commit cd025f5c0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 25 deletions

View File

@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Changed
- No longer require `Resource` service body type to be boxed. [#2526]
[#2526]: https://github.com/actix/actix-web/pull/2526
## 4.0.0-beta.15 - 2021-12-17 ## 4.0.0-beta.15 - 2021-12-17

View File

@ -55,7 +55,6 @@ bytestring = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] }
h2 = "0.3.9" h2 = "0.3.9"
http = "0.2.5" http = "0.2.5"
httparse = "1.5.1" httparse = "1.5.1"

View File

@ -38,6 +38,15 @@ pub struct Compat<T> {
transform: T, transform: T,
} }
#[cfg(test)]
impl Compat<super::Noop> {
pub(crate) fn noop() -> Self {
Self {
transform: super::Noop,
}
}
}
impl<T> Compat<T> { impl<T> Compat<T> {
/// Wrap a middleware to give it broader compatibility. /// Wrap a middleware to give it broader compatibility.
pub fn new(middleware: T) -> Self { pub fn new(middleware: T) -> Self {

View File

@ -5,6 +5,8 @@ mod condition;
mod default_headers; mod default_headers;
mod err_handlers; mod err_handlers;
mod logger; mod logger;
#[cfg(test)]
mod noop;
mod normalize; mod normalize;
pub use self::compat::Compat; pub use self::compat::Compat;
@ -12,6 +14,8 @@ pub use self::condition::Condition;
pub use self::default_headers::DefaultHeaders; pub use self::default_headers::DefaultHeaders;
pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers}; pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers};
pub use self::logger::Logger; pub use self::logger::Logger;
#[cfg(test)]
pub(crate) use self::noop::Noop;
pub use self::normalize::{NormalizePath, TrailingSlash}; pub use self::normalize::{NormalizePath, TrailingSlash};
#[cfg(feature = "__compress")] #[cfg(feature = "__compress")]

37
src/middleware/noop.rs Normal file
View File

@ -0,0 +1,37 @@
//! A no-op middleware. See [Noop] for docs.
use actix_utils::future::{ready, Ready};
use crate::dev::{Service, Transform};
/// A no-op middleware that passes through request and response untouched.
pub(crate) struct Noop;
impl<S: Service<Req>, Req> Transform<S, Req> for Noop {
type Response = S::Response;
type Error = S::Error;
type Transform = NoopService<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(NoopService { service }))
}
}
#[doc(hidden)]
pub(crate) struct NoopService<S> {
service: S,
}
impl<S: Service<Req>, Req> Service<Req> for NoopService<S> {
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
crate::dev::forward_ready!(service);
fn call(&self, req: Req) -> Self::Future {
self.service.call(req)
}
}

View File

@ -1,6 +1,6 @@
use std::{cell::RefCell, fmt, future::Future, rc::Rc}; use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
use actix_http::Extensions; use actix_http::{body::BoxBody, Extensions};
use actix_router::{IntoPatterns, Patterns}; use actix_router::{IntoPatterns, Patterns};
use actix_service::{ use actix_service::{
apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory, apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
@ -45,7 +45,7 @@ use crate::{
/// ///
/// If no matching route could be found, *405* response code get returned. /// If no matching route could be found, *405* response code get returned.
/// Default behavior could be overridden with `default_resource()` method. /// Default behavior could be overridden with `default_resource()` method.
pub struct Resource<T = ResourceEndpoint> { pub struct Resource<T = ResourceEndpoint, B = BoxBody> {
endpoint: T, endpoint: T,
rdef: Patterns, rdef: Patterns,
name: Option<String>, name: Option<String>,
@ -54,6 +54,7 @@ pub struct Resource<T = ResourceEndpoint> {
guards: Vec<Box<dyn Guard>>, guards: Vec<Box<dyn Guard>>,
default: BoxedHttpServiceFactory, default: BoxedHttpServiceFactory,
factory_ref: Rc<RefCell<Option<ResourceFactory>>>, factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
_phantom: PhantomData<B>,
} }
impl Resource { impl Resource {
@ -71,19 +72,21 @@ impl Resource {
default: boxed::factory(fn_service(|req: ServiceRequest| async { default: boxed::factory(fn_service(|req: ServiceRequest| async {
Ok(req.into_response(HttpResponse::MethodNotAllowed())) Ok(req.into_response(HttpResponse::MethodNotAllowed()))
})), })),
_phantom: PhantomData,
} }
} }
} }
impl<T> Resource<T> impl<T, B> Resource<T, B>
where where
T: ServiceFactory< T: ServiceFactory<
ServiceRequest, ServiceRequest,
Config = (), Config = (),
Response = ServiceResponse, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
B: MessageBody,
{ {
/// Set resource name. /// Set resource name.
/// ///
@ -252,26 +255,28 @@ where
/// type (i.e modify response's body). /// type (i.e modify response's body).
/// ///
/// **Note**: middlewares get called in opposite order of middlewares registration. /// **Note**: middlewares get called in opposite order of middlewares registration.
pub fn wrap<M>( pub fn wrap<M, B1>(
self, self,
mw: M, mw: M,
) -> Resource< ) -> Resource<
impl ServiceFactory< impl ServiceFactory<
ServiceRequest, ServiceRequest,
Config = (), Config = (),
Response = ServiceResponse, Response = ServiceResponse<B1>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
B1,
> >
where where
M: Transform< M: Transform<
T::Service, T::Service,
ServiceRequest, ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse<B1>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
B1: MessageBody,
{ {
Resource { Resource {
endpoint: apply(mw, self.endpoint), endpoint: apply(mw, self.endpoint),
@ -282,6 +287,7 @@ where
default: self.default, default: self.default,
app_data: self.app_data, app_data: self.app_data,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
_phantom: PhantomData,
} }
} }
@ -319,21 +325,23 @@ where
/// .route(web::get().to(index))); /// .route(web::get().to(index)));
/// } /// }
/// ``` /// ```
pub fn wrap_fn<F, R>( pub fn wrap_fn<F, R, B1>(
self, self,
mw: F, mw: F,
) -> Resource< ) -> Resource<
impl ServiceFactory< impl ServiceFactory<
ServiceRequest, ServiceRequest,
Config = (), Config = (),
Response = ServiceResponse, Response = ServiceResponse<B1>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
B1,
> >
where where
F: Fn(ServiceRequest, &T::Service) -> R + Clone, F: Fn(ServiceRequest, &T::Service) -> R + Clone,
R: Future<Output = Result<ServiceResponse, Error>>, R: Future<Output = Result<ServiceResponse<B1>, Error>>,
B1: MessageBody,
{ {
Resource { Resource {
endpoint: apply_fn_factory(self.endpoint, mw), endpoint: apply_fn_factory(self.endpoint, mw),
@ -344,6 +352,7 @@ where
default: self.default, default: self.default,
app_data: self.app_data, app_data: self.app_data,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
_phantom: PhantomData,
} }
} }
@ -371,15 +380,16 @@ where
} }
} }
impl<T> HttpServiceFactory for Resource<T> impl<T, B> HttpServiceFactory for Resource<T, B>
where where
T: ServiceFactory< T: ServiceFactory<
ServiceRequest, ServiceRequest,
Config = (), Config = (),
Response = ServiceResponse, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
> + 'static, > + 'static,
B: MessageBody + 'static,
{ {
fn register(mut self, config: &mut AppService) { fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() { let guards = if self.guards.is_empty() {
@ -411,7 +421,9 @@ where
req.add_data_container(Rc::clone(data)); req.add_data_container(Rc::clone(data));
} }
srv.call(req) let fut = srv.call(req);
async { Ok(fut.await?.map_into_boxed_body()) }
}); });
config.register_service(rdef, guards, endpoint, None) config.register_service(rdef, guards, endpoint, None)
@ -534,11 +546,11 @@ mod tests {
>, >,
> { > {
web::resource("/test-compat") web::resource("/test-compat")
// .wrap_fn(|req, srv| { .wrap_fn(|req, srv| {
// let fut = srv.call(req); let fut = srv.call(req);
// async { Ok(fut.await?.map_into_right_body::<()>()) } async { Ok(fut.await?.map_into_right_body::<()>()) }
// }) })
.wrap(Compat::new(DefaultHeaders::new())) .wrap(Compat::noop())
.route(web::get().to(|| async { "hello" })) .route(web::get().to(|| async { "hello" }))
} }
@ -801,4 +813,26 @@ mod tests {
let resp = call_service(&srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); 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());
}
} }

View File

@ -616,11 +616,11 @@ mod tests {
>, >,
> { > {
web::scope("/test-compat") web::scope("/test-compat")
// .wrap_fn(|req, srv| { .wrap_fn(|req, srv| {
// let fut = srv.call(req); let fut = srv.call(req);
// async { Ok(fut.await?.map_into_right_body::<()>()) } async { Ok(fut.await?.map_into_right_body::<()>()) }
// }) })
.wrap(Compat::new(DefaultHeaders::new())) .wrap(Compat::noop())
.service(web::resource("").route(web::get().to(|| async { "hello" }))) .service(web::resource("").route(web::get().to(|| async { "hello" })))
} }