actix_web_lab/
redirect_to_www.rs

1use actix_web::{
2    body::MessageBody,
3    dev::{ServiceRequest, ServiceResponse},
4    web::Redirect,
5    Error, Responder,
6};
7
8use crate::middleware_from_fn::Next;
9
10/// A function middleware to redirect traffic to `www.` if not already there.
11///
12/// # Examples
13/// ```
14/// # use actix_web::App;
15/// use actix_web_lab::middleware::{from_fn, redirect_to_www};
16///
17/// App::new().wrap(from_fn(redirect_to_www))
18///     # ;
19/// ```
20pub async fn redirect_to_www(
21    req: ServiceRequest,
22    next: Next<impl MessageBody + 'static>,
23) -> Result<ServiceResponse<impl MessageBody>, Error> {
24    #![allow(clippy::await_holding_refcell_ref)] // RefCell is dropped before await
25
26    let (req, pl) = req.into_parts();
27    let conn_info = req.connection_info();
28
29    if !conn_info.host().starts_with("www.") {
30        let scheme = conn_info.scheme();
31        let host = conn_info.host();
32        let path = req.uri().path();
33        let uri = format!("{scheme}://www.{host}{path}");
34
35        let res = Redirect::to(uri).respond_to(&req);
36
37        drop(conn_info);
38        return Ok(ServiceResponse::new(req, res).map_into_right_body());
39    }
40
41    drop(conn_info);
42    let req = ServiceRequest::from_parts(req, pl);
43    Ok(next.call(req).await?.map_into_left_body())
44}
45
46#[cfg(test)]
47mod test_super {
48    use actix_web::{
49        dev::ServiceFactory,
50        http::{header, StatusCode},
51        test, web, App, HttpResponse,
52    };
53
54    use super::*;
55    use crate::middleware::from_fn;
56
57    fn test_app() -> App<
58        impl ServiceFactory<
59            ServiceRequest,
60            Response = ServiceResponse<impl MessageBody>,
61            Config = (),
62            InitError = (),
63            Error = Error,
64        >,
65    > {
66        App::new().wrap(from_fn(redirect_to_www)).route(
67            "/",
68            web::get().to(|| async { HttpResponse::Ok().body("content") }),
69        )
70    }
71
72    #[actix_web::test]
73    async fn redirect_non_www() {
74        let app = test::init_service(test_app()).await;
75
76        let req = test::TestRequest::default().to_request();
77        let res = test::call_service(&app, req).await;
78        assert_eq!(res.status(), StatusCode::TEMPORARY_REDIRECT);
79
80        let loc = res.headers().get(header::LOCATION);
81        assert!(loc.is_some());
82        assert!(loc.unwrap().as_bytes().starts_with(b"http://www"));
83
84        let body = test::read_body(res).await;
85        assert!(body.is_empty());
86    }
87
88    #[actix_web::test]
89    async fn do_not_redirect_already_www() {
90        let app = test::init_service(test_app()).await;
91
92        let req = test::TestRequest::default()
93            .uri("http://www.localhost/")
94            .to_request();
95        let res = test::call_service(&app, req).await;
96        assert_eq!(res.status(), StatusCode::OK);
97
98        let loc = res.headers().get(header::LOCATION);
99        assert!(loc.is_none());
100
101        let body = test::read_body(res).await;
102        assert_eq!(body, "content");
103    }
104}