Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Route::wrap #2725

Merged
merged 5 commits into from Apr 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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