mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 03:21:08 +00:00
add Route::wrap
(#2725)
* add `Route::wrap` * add tests * fix clippy * fix doctests
This commit is contained in:
parent
8abcb94512
commit
45592b37b6
4 changed files with 108 additions and 34 deletions
|
@ -119,6 +119,7 @@ mod _original {
|
||||||
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
||||||
unsafe { MaybeUninit::uninit().assume_init() };
|
unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
|
||||||
|
#[allow(invalid_value)]
|
||||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
||||||
unsafe { MaybeUninit::uninit().assume_init() };
|
unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
### Added
|
### Added
|
||||||
- Add `ServiceRequest::extract` to make it easier to use extractors when writing middlewares. [#2647]
|
- Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647]
|
||||||
|
- Add `Route::wrap()` to allow individual routes to use middleware. [#2725]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Clear connection-level data on `HttpRequest` drop. [#2742]
|
- Clear connection-level data on `HttpRequest` drop. [#2742]
|
||||||
|
|
||||||
[#2647]: https://github.com/actix/actix-web/pull/2647
|
[#2647]: https://github.com/actix/actix-web/pull/2647
|
||||||
|
[#2725]: https://github.com/actix/actix-web/pull/2725
|
||||||
[#2742]: https://github.com/actix/actix-web/pull/2742
|
[#2742]: https://github.com/actix/actix-web/pull/2742
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
use std::{mem, rc::Rc};
|
use std::{mem, rc::Rc};
|
||||||
|
|
||||||
use actix_http::Method;
|
use actix_http::{body::MessageBody, Method};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
|
apply,
|
||||||
boxed::{self, BoxService},
|
boxed::{self, BoxService},
|
||||||
fn_service, Service, ServiceFactory, ServiceFactoryExt,
|
fn_service, Service, ServiceFactory, ServiceFactoryExt, Transform,
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
guard::{self, Guard},
|
guard::{self, Guard},
|
||||||
handler::{handler_service, Handler},
|
handler::{handler_service, Handler},
|
||||||
|
middleware::Compat,
|
||||||
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||||
Error, FromRequest, HttpResponse, Responder,
|
Error, FromRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
@ -35,6 +37,31 @@ impl Route {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a route middleware.
|
||||||
|
///
|
||||||
|
/// `mw` is a middleware component (type), that can modify the requests and responses handled by
|
||||||
|
/// this `Route`.
|
||||||
|
///
|
||||||
|
/// See [`App::wrap`](crate::App::wrap) for more details.
|
||||||
|
#[doc(alias = "middleware")]
|
||||||
|
#[doc(alias = "use")] // nodejs terminology
|
||||||
|
pub fn wrap<M, B>(self, mw: M) -> Route
|
||||||
|
where
|
||||||
|
M: Transform<
|
||||||
|
BoxService<ServiceRequest, ServiceResponse, Error>,
|
||||||
|
ServiceRequest,
|
||||||
|
Response = ServiceResponse<B>,
|
||||||
|
Error = Error,
|
||||||
|
InitError = (),
|
||||||
|
> + 'static,
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
Route {
|
||||||
|
service: boxed::factory(apply(Compat::new(mw), self.service)),
|
||||||
|
guards: self.guards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
|
pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
|
||||||
mem::take(Rc::get_mut(&mut self.guards).unwrap())
|
mem::take(Rc::get_mut(&mut self.guards).unwrap())
|
||||||
}
|
}
|
||||||
|
@ -246,11 +273,15 @@ mod tests {
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::dev::{always_ready, fn_factory, fn_service, Service};
|
use crate::{
|
||||||
use crate::http::{header, Method, StatusCode};
|
dev::{always_ready, fn_factory, fn_service, Service},
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
error,
|
||||||
use crate::test::{call_service, init_service, read_body, TestRequest};
|
http::{header, Method, StatusCode},
|
||||||
use crate::{error, web, App, HttpResponse};
|
middleware::{DefaultHeaders, Logger},
|
||||||
|
service::{ServiceRequest, ServiceResponse},
|
||||||
|
test::{call_service, init_service, read_body, TestRequest},
|
||||||
|
web, App, HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, PartialEq, Debug)]
|
#[derive(Serialize, PartialEq, Debug)]
|
||||||
struct MyObject {
|
struct MyObject {
|
||||||
|
@ -323,6 +354,44 @@ mod tests {
|
||||||
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
|
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn route_middleware() {
|
||||||
|
let srv = init_service(
|
||||||
|
App::new()
|
||||||
|
.route("/", web::get().to(HttpResponse::Ok).wrap(Logger::default()))
|
||||||
|
.service(
|
||||||
|
web::resource("/test")
|
||||||
|
.route(web::get().to(HttpResponse::Ok))
|
||||||
|
.route(
|
||||||
|
web::post()
|
||||||
|
.to(HttpResponse::Created)
|
||||||
|
.wrap(DefaultHeaders::new().add(("x-test", "x-posted"))),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
web::delete()
|
||||||
|
.to(HttpResponse::Accepted)
|
||||||
|
// logger changes body type, proving Compat is not needed
|
||||||
|
.wrap(Logger::default()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/test").to_request();
|
||||||
|
let res = call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert!(!res.headers().contains_key("x-test"));
|
||||||
|
|
||||||
|
let req = TestRequest::post().uri("/test").to_request();
|
||||||
|
let res = call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::CREATED);
|
||||||
|
assert_eq!(res.headers().get("x-test").unwrap(), "x-posted");
|
||||||
|
|
||||||
|
let req = TestRequest::delete().uri("/test").to_request();
|
||||||
|
let res = call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::ACCEPTED);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_service_handler() {
|
async fn test_service_handler() {
|
||||||
struct HelloWorld;
|
struct HelloWorld;
|
||||||
|
|
|
@ -799,34 +799,36 @@ async fn test_server_cookies() {
|
||||||
let res = req.send().await.unwrap();
|
let res = req.send().await.unwrap();
|
||||||
assert!(res.status().is_success());
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
let first_cookie = Cookie::build("first", "first_value")
|
{
|
||||||
.http_only(true)
|
let first_cookie = Cookie::build("first", "first_value")
|
||||||
.finish();
|
.http_only(true)
|
||||||
let second_cookie = Cookie::new("second", "first_value");
|
.finish();
|
||||||
|
let second_cookie = Cookie::new("second", "first_value");
|
||||||
|
|
||||||
let cookies = res.cookies().expect("To have cookies");
|
let cookies = res.cookies().expect("To have cookies");
|
||||||
assert_eq!(cookies.len(), 3);
|
assert_eq!(cookies.len(), 3);
|
||||||
if cookies[0] == first_cookie {
|
if cookies[0] == first_cookie {
|
||||||
assert_eq!(cookies[1], second_cookie);
|
assert_eq!(cookies[1], second_cookie);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(cookies[0], second_cookie);
|
assert_eq!(cookies[0], second_cookie);
|
||||||
assert_eq!(cookies[1], first_cookie);
|
assert_eq!(cookies[1], first_cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_cookie = first_cookie.to_string();
|
let first_cookie = first_cookie.to_string();
|
||||||
let second_cookie = second_cookie.to_string();
|
let second_cookie = second_cookie.to_string();
|
||||||
// Check that we have exactly two instances of raw cookie headers
|
// Check that we have exactly two instances of raw cookie headers
|
||||||
let cookies = res
|
let cookies = res
|
||||||
.headers()
|
.headers()
|
||||||
.get_all(http::header::SET_COOKIE)
|
.get_all(http::header::SET_COOKIE)
|
||||||
.map(|header| header.to_str().expect("To str").to_string())
|
.map(|header| header.to_str().expect("To str").to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(cookies.len(), 3);
|
assert_eq!(cookies.len(), 3);
|
||||||
if cookies[0] == first_cookie {
|
if cookies[0] == first_cookie {
|
||||||
assert_eq!(cookies[1], second_cookie);
|
assert_eq!(cookies[1], second_cookie);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(cookies[0], second_cookie);
|
assert_eq!(cookies[0], second_cookie);
|
||||||
assert_eq!(cookies[1], first_cookie);
|
assert_eq!(cookies[1], first_cookie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.stop().await;
|
srv.stop().await;
|
||||||
|
|
Loading…
Reference in a new issue