actix_web_middleware_redirect_https/
lib.rs

1//! # actix-web-middleware-redirect-https
2//!
3//! Provides a middleware for `actix-web` to redirect all `http` requests to `https`.
4
5use actix_service::{Service, Transform};
6use actix_web::{
7    dev::{ServiceRequest, ServiceResponse},
8    http, Error, HttpResponse,
9};
10use futures::future::{ok, Either, Ready};
11use std::task::{Context, Poll};
12
13/// Middleware for `actix-web` which redirects all `http` requests to `https` with optional url
14/// string replacements.
15///
16/// ## Usage
17/// ```
18/// use actix_web::{App, web, HttpResponse};
19/// use actix_web_middleware_redirect_https::RedirectHTTPS;
20///
21/// App::new()
22///     .wrap(RedirectHTTPS::default())
23///     .route("/", web::get().to(|| HttpResponse::Ok()
24///                                     .content_type("text/plain")
25///                                     .body("Always HTTPS!")));
26/// ```
27#[derive(Default, Clone)]
28pub struct RedirectHTTPS {
29    replacements: Vec<(String, String)>,
30}
31
32impl RedirectHTTPS {
33    /// Creates a RedirectHTTPS middleware which also performs string replacement on the final url.
34    /// This is useful when not running on the default web and ssl ports (80 and 443) since we will
35    /// need to change the development web port in the hostname to the development ssl port.
36    ///
37    /// ## Usage
38    /// ```
39    /// use actix_web::{App, web, HttpResponse};
40    /// use actix_web_middleware_redirect_https::RedirectHTTPS;
41    ///
42    /// App::new()
43    ///     .wrap(RedirectHTTPS::with_replacements(&[(":8080".to_owned(), ":8443".to_owned())]))
44    ///     .route("/", web::get().to(|| HttpResponse::Ok()
45    ///                                     .content_type("text/plain")
46    ///                                     .body("Always HTTPS on non-default ports!")));
47    /// ```
48    pub fn with_replacements(replacements: &[(String, String)]) -> Self {
49        RedirectHTTPS {
50            replacements: replacements.to_vec(),
51        }
52    }
53}
54
55impl<S, B> Transform<S> for RedirectHTTPS
56where
57    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
58    S::Future: 'static,
59{
60    type Request = ServiceRequest;
61    type Response = ServiceResponse<B>;
62    type Error = Error;
63    type InitError = ();
64    type Transform = RedirectHTTPSService<S>;
65    type Future = Ready<Result<Self::Transform, Self::InitError>>;
66
67    fn new_transform(&self, service: S) -> Self::Future {
68        ok(RedirectHTTPSService {
69            service,
70            replacements: self.replacements.clone(),
71        })
72    }
73}
74
75pub struct RedirectHTTPSService<S> {
76    service: S,
77    replacements: Vec<(String, String)>,
78}
79
80impl<S, B> Service for RedirectHTTPSService<S>
81where
82    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
83    S::Future: 'static,
84{
85    type Request = ServiceRequest;
86    type Response = ServiceResponse<B>;
87    type Error = Error;
88    type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
89
90    fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
91        self.service.poll_ready(cx)
92    }
93
94    fn call(&mut self, req: ServiceRequest) -> Self::Future {
95        if req.connection_info().scheme() == "https" {
96            Either::Left(self.service.call(req))
97        } else {
98            let host = req.connection_info().host().to_owned();
99            let uri = req.uri().to_owned();
100            let mut url = format!("https://{}{}", host, uri);
101            for (s1, s2) in self.replacements.iter() {
102                url = url.replace(s1, s2);
103            }
104            Either::Right(ok(req.into_response(
105                HttpResponse::MovedPermanently()
106                    .header(http::header::LOCATION, url)
107                    .finish()
108                    .into_body(),
109            )))
110        }
111    }
112}