Skip to content

Commit

Permalink
add Route::wrap (#2725)
Browse files Browse the repository at this point in the history
* add `Route::wrap`

* add tests

* fix clippy

* fix doctests
  • Loading branch information
robjtede committed Apr 23, 2022
1 parent 8abcb94 commit 45592b3
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 35 deletions.
1 change: 1 addition & 0 deletions actix-http/benches/uninit-headers.rs
Expand Up @@ -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() };

Expand Down
4 changes: 3 additions & 1 deletion actix-web/CHANGES.md
Expand Up @@ -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


Expand Down
83 changes: 76 additions & 7 deletions 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,
};
Expand All @@ -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>> {
mem::take(Rc::get_mut(&mut self.guards).unwrap())
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
56 changes: 29 additions & 27 deletions actix-web/tests/test_server.rs
Expand Up @@ -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 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 = 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 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::<Vec<_>>();
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::<Vec<_>>();
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;
Expand Down

0 comments on commit 45592b3

Please sign in to comment.