diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs index 3eda96be8..688c64d6e 100644 --- a/actix-http/benches/uninit-headers.rs +++ b/actix-http/benches/uninit-headers.rs @@ -119,6 +119,7 @@ mod _original { let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; + #[allow(invalid_value)] let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index eac40e38d..f89189402 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,12 +2,14 @@ ## Unreleased - 2021-xx-xx ### 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 - Clear connection-level data on `HttpRequest` drop. [#2742] [#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 diff --git a/actix-web/src/route.rs b/actix-web/src/route.rs index 0410b99dd..b37128f2c 100644 --- a/actix-web/src/route.rs +++ b/actix-web/src/route.rs @@ -1,15 +1,17 @@ use std::{mem, rc::Rc}; -use actix_http::Method; +use actix_http::{body::MessageBody, Method}; use actix_service::{ + apply, boxed::{self, BoxService}, - fn_service, Service, ServiceFactory, ServiceFactoryExt, + fn_service, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use crate::{ guard::{self, Guard}, handler::{handler_service, Handler}, + middleware::Compat, service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse}, 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(self, mw: M) -> Route + where + M: Transform< + BoxService, + ServiceRequest, + Response = ServiceResponse, + 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> { mem::take(Rc::get_mut(&mut self.guards).unwrap()) } @@ -246,11 +273,15 @@ mod tests { use futures_core::future::LocalBoxFuture; use serde::Serialize; - use crate::dev::{always_ready, fn_factory, fn_service, Service}; - use crate::http::{header, Method, StatusCode}; - use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{error, web, App, HttpResponse}; + use crate::{ + dev::{always_ready, fn_factory, fn_service, Service}, + error, + http::{header, Method, StatusCode}, + middleware::{DefaultHeaders, Logger}, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, read_body, TestRequest}, + web, App, HttpResponse, + }; #[derive(Serialize, PartialEq, Debug)] struct MyObject { @@ -323,6 +354,44 @@ mod tests { 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] async fn test_service_handler() { struct HelloWorld; diff --git a/actix-web/tests/test_server.rs b/actix-web/tests/test_server.rs index bd8934061..270223d69 100644 --- a/actix-web/tests/test_server.rs +++ b/actix-web/tests/test_server.rs @@ -799,34 +799,36 @@ async fn test_server_cookies() { let res = req.send().await.unwrap(); assert!(res.status().is_success()); - let first_cookie = Cookie::build("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = Cookie::new("second", "first_value"); + { + let first_cookie = Cookie::build("first", "first_value") + .http_only(true) + .finish(); + let second_cookie = Cookie::new("second", "first_value"); - let cookies = res.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 3); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } + let cookies = res.cookies().expect("To have cookies"); + assert_eq!(cookies.len(), 3); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } - let first_cookie = first_cookie.to_string(); - let second_cookie = second_cookie.to_string(); - // Check that we have exactly two instances of raw cookie headers - let cookies = res - .headers() - .get_all(http::header::SET_COOKIE) - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); - assert_eq!(cookies.len(), 3); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); + let first_cookie = first_cookie.to_string(); + let second_cookie = second_cookie.to_string(); + // Check that we have exactly two instances of raw cookie headers + let cookies = res + .headers() + .get_all(http::header::SET_COOKIE) + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); + assert_eq!(cookies.len(), 3); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } } srv.stop().await;