1
0
Fork 0
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:
Rob Ede 2022-04-23 21:01:55 +01:00 committed by GitHub
parent 8abcb94512
commit 45592b37b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 34 deletions

View file

@ -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() };

View file

@ -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

View file

@ -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;

View file

@ -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;