Skip to main content

product_os_router/
dual_protocol.rs

1//! HTTP to HTTPS upgrade layer
2//!
3//! This module provides middleware for automatically redirecting HTTP requests to HTTPS.
4
5use std::prelude::v1::*;
6
7use std::fmt::{self, Debug, Formatter};
8use std::future::Future;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11
12use pin_project::pin_project;
13use product_os_http::header::{HOST, LOCATION, UPGRADE};
14use product_os_http::uri::{Authority, Scheme};
15use product_os_http::{HeaderValue, Request, Response, StatusCode, Uri};
16use product_os_http_body::Bytes;
17use product_os_http_body::{Either, Empty};
18use tower_layer::Layer;
19use tower_service::Service as TowerService;
20
21/// Protocol indicator for HTTP/HTTPS connections
22///
23/// Used to identify whether a connection is encrypted (TLS) or plain (HTTP).
24#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
25pub enum Protocol {
26    /// This connection is encrypted with TLS.
27    Tls,
28    /// This connection is unencrypted.
29    Plain,
30}
31
32/// Layer for upgrading HTTP connections to HTTPS
33///
34/// Automatically redirects HTTP requests to HTTPS using a 301 redirect.
35/// WebSocket connections (`ws://`) are redirected to `wss://`.
36///
37/// # Examples
38///
39/// ```rust
40/// use product_os_router::UpgradeHttpLayer;
41/// use tower::Layer;
42///
43/// let layer = UpgradeHttpLayer;
44/// // Apply to your service
45/// ```
46#[derive(Clone, Copy, Debug)]
47pub struct UpgradeHttpLayer;
48
49impl<Service> Layer<Service> for UpgradeHttpLayer {
50    type Service = UpgradeHttp<Service>;
51
52    fn layer(&self, inner: Service) -> Self::Service {
53        UpgradeHttp::new(inner)
54    }
55}
56
57/// [`Service`](TowerService) upgrading HTTP requests to HTTPS by using a
58/// [301 "Moved Permanently"](https://tools.ietf.org/html/rfc7231#section-6.4.2)
59/// status code.
60///
61/// Note that this [`Service`](TowerService) always redirects with the given
62/// path and query. Depending on how you apply this [`Service`](TowerService) it
63/// will redirect even in the case of a resulting 404 "Not Found" status code at
64/// the destination.
65#[derive(Clone, Debug)]
66pub struct UpgradeHttp<Service> {
67    /// Wrapped user-provided [`Service`](TowerService).
68    service: Service,
69}
70
71impl<Service> UpgradeHttp<Service> {
72    /// Creates a new [`UpgradeHttp`].
73    pub const fn new(service: Service) -> Self {
74        Self { service }
75    }
76
77    /// Consumes the [`UpgradeHttp`], returning the wrapped
78    /// [`Service`](TowerService).
79    pub fn into_inner(self) -> Service {
80        self.service
81    }
82
83    /// Return a reference to the wrapped [`Service`](TowerService).
84    pub const fn get_ref(&self) -> &Service {
85        &self.service
86    }
87
88    /// Return a mutable reference to the wrapped [`Service`](TowerService).
89    pub fn get_mut(&mut self) -> &mut Service {
90        &mut self.service
91    }
92}
93
94impl<Service, RequestBody, ResponseBody> TowerService<Request<RequestBody>> for UpgradeHttp<Service>
95where
96    Service: TowerService<Request<RequestBody>, Response = Response<ResponseBody>>,
97{
98    type Response = Response<Either<ResponseBody, Empty<Bytes>>>;
99    type Error = Service::Error;
100    type Future = UpgradeHttpFuture<Service, Request<RequestBody>>;
101
102    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
103        self.service.poll_ready(cx)
104    }
105
106    fn call(&mut self, req: Request<RequestBody>) -> Self::Future {
107        let protocol = req.extensions().get::<Protocol>().copied();
108
109        match protocol {
110            None => {
111                // Protocol extension was not set; return a 500 Internal Server Error
112                // instead of panicking. This can happen if the layer is used without
113                // `DualProtocolService` setting the extension.
114                let response = Response::builder()
115                    .status(StatusCode::INTERNAL_SERVER_ERROR)
116                    .body(Empty::new())
117                    .expect("building empty 500 response");
118                UpgradeHttpFuture::new_upgrade(response)
119            }
120            Some(Protocol::Tls) => UpgradeHttpFuture::new_service(self.service.call(req)),
121            Some(Protocol::Plain) => {
122                let response = Response::builder();
123
124                let response = if let Some((authority, scheme)) =
125                    extract_authority(&req).and_then(|authority| {
126                        let uri = req.uri();
127
128                        // Depending on the scheme we need a different scheme to redirect to.
129
130                        // WebSocket handshakes often don't send a scheme, so we check the "Upgrade"
131                        // header as well.
132                        if uri.scheme_str() == Some("ws")
133                            || req.headers().get(UPGRADE)
134                                == Some(&HeaderValue::from_static("websocket"))
135                        {
136                            Some((
137                                authority,
138                                Scheme::try_from("wss").expect("ASCII string is valid"),
139                            ))
140                        }
141                        // HTTP requests often don't send a scheme.
142                        else if uri.scheme() == Some(&Scheme::HTTP) || uri.scheme_str().is_none()
143                        {
144                            Some((authority, Scheme::HTTPS))
145                        }
146                        // Unknown scheme, abort.
147                        else {
148                            None
149                        }
150                    }) {
151                    // Build URI to redirect to.
152                    let mut uri = Uri::builder().scheme(scheme).authority(authority);
153
154                    if let Some(path_and_query) = req.uri().path_and_query() {
155                        uri = uri.path_and_query(path_and_query.clone());
156                    }
157
158                    let uri = uri.build().expect("invalid path and query");
159
160                    response
161                        .status(StatusCode::MOVED_PERMANENTLY)
162                        .header(LOCATION, uri.to_string())
163                } else {
164                    // If we can't extract the host or have an unknown scheme, tell the client there
165                    // is something wrong with their request.
166                    response.status(StatusCode::BAD_REQUEST)
167                }
168                .body(Empty::new())
169                .expect("invalid header or body");
170
171                UpgradeHttpFuture::new_upgrade(response)
172            }
173        }
174    }
175}
176
177/// [`Future`](TowerService::Future) type for [`UpgradeHttp`].
178#[pin_project]
179pub struct UpgradeHttpFuture<Service, Request>(#[pin] FutureServe<Service, Request>)
180where
181    Service: TowerService<Request>;
182
183/// Holds [`Future`] to serve for [`UpgradeHttpFuture`].
184#[derive(Debug)]
185#[pin_project(project = UpgradeHttpFutureProj)]
186enum FutureServe<Service, Request>
187where
188    Service: TowerService<Request>,
189{
190    /// The request was using the HTTPS protocol, so we
191    /// will pass-through the wrapped [`Service`](TowerService).
192    Service(#[pin] Service::Future),
193    /// The request was using the HTTP protocol, so we
194    /// will upgrade the connection.
195    Upgrade(Option<Response<Empty<Bytes>>>),
196}
197
198// Rust can't figure out the correct bounds.
199impl<Service, Request> Debug for UpgradeHttpFuture<Service, Request>
200where
201    Service: TowerService<Request>,
202    FutureServe<Service, Request>: Debug,
203{
204    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
205        formatter
206            .debug_tuple("UpgradeHttpFuture")
207            .field(&self.0)
208            .finish()
209    }
210}
211
212impl<Service, Request> UpgradeHttpFuture<Service, Request>
213where
214    Service: TowerService<Request>,
215{
216    /// Create a [`UpgradeHttpFuture`] in the [`Service`](FutureServe::Service)
217    /// state.
218    const fn new_service(future: Service::Future) -> Self {
219        Self(FutureServe::Service(future))
220    }
221
222    /// Create a [`UpgradeHttpFuture`] in the [`Upgrade`](FutureServe::Upgrade)
223    /// state.
224    const fn new_upgrade(response: Response<Empty<Bytes>>) -> Self {
225        Self(FutureServe::Upgrade(Some(response)))
226    }
227}
228
229impl<Service, Request, ResponseBody> Future for UpgradeHttpFuture<Service, Request>
230where
231    Service: TowerService<Request, Response = Response<ResponseBody>>,
232{
233    type Output = Result<Response<Either<ResponseBody, Empty<Bytes>>>, Service::Error>;
234
235    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
236        match self.project().0.project() {
237            UpgradeHttpFutureProj::Service(future) => {
238                future.poll(cx).map_ok(|result| result.map(Either::Left))
239            }
240            UpgradeHttpFutureProj::Upgrade(response) => Poll::Ready(Ok(response
241                .take()
242                .expect("polled again after `Poll::Ready`")
243                .map(Either::Right))),
244        }
245    }
246}
247
248/// Extracts the host from a request, converting it to an [`Authority`].
249fn extract_authority<Body>(request: &Request<Body>) -> Option<Authority> {
250    /// `X-Forwarded-Host` header string.
251    const X_FORWARDED_HOST: &str = "x-forwarded-host";
252
253    let headers = request.headers();
254
255    headers
256        .get(X_FORWARDED_HOST)
257        .or_else(|| headers.get(HOST))
258        .and_then(|header| header.to_str().ok())
259        .or_else(|| request.uri().host())
260        .and_then(|host| Authority::try_from(host).ok())
261}