Skip to main content

aws_smithy_http_server/routing/
route.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6// This code was copied and then modified from Tokio's Axum.
7
8/* Copyright (c) 2021 Tower Contributors
9 *
10 * Permission is hereby granted, free of charge, to any
11 * person obtaining a copy of this software and associated
12 * documentation files (the "Software"), to deal in the
13 * Software without restriction, including without
14 * limitation the rights to use, copy, modify, merge,
15 * publish, distribute, sublicense, and/or sell copies of
16 * the Software, and to permit persons to whom the Software
17 * is furnished to do so, subject to the following
18 * conditions:
19 *
20 * The above copyright notice and this permission notice
21 * shall be included in all copies or substantial portions
22 * of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
25 * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
26 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
27 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
28 * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
31 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32 * DEALINGS IN THE SOFTWARE.
33 */
34
35use crate::body::BoxBody;
36use http::{Request, Response};
37use std::{
38    convert::Infallible,
39    fmt,
40    future::Future,
41    pin::Pin,
42    task::{Context, Poll},
43};
44use tower::{
45    util::{BoxCloneService, BoxCloneSyncService, Oneshot},
46    Service, ServiceExt,
47};
48
49/// A HTTP [`Service`] representing a single route.
50///
51/// The construction of [`Route`] from a named HTTP [`Service`] `S`, erases the type of `S`.
52pub struct Route<B = hyper::body::Incoming> {
53    service: BoxCloneService<Request<B>, Response<BoxBody>, Infallible>,
54}
55
56impl<B> Route<B> {
57    /// Constructs a new [`Route`] from a well-formed HTTP service which is cloneable.
58    pub fn new<T>(svc: T) -> Self
59    where
60        T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone + Send + 'static,
61        T::Future: Send + 'static,
62    {
63        Self {
64            service: BoxCloneService::new(svc),
65        }
66    }
67}
68
69impl<ReqBody> Clone for Route<ReqBody> {
70    fn clone(&self) -> Self {
71        Self {
72            service: self.service.clone(),
73        }
74    }
75}
76
77impl<ReqBody> fmt::Debug for Route<ReqBody> {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.debug_struct("Route").finish()
80    }
81}
82
83impl<B> Service<Request<B>> for Route<B> {
84    type Response = Response<BoxBody>;
85    type Error = Infallible;
86    type Future = RouteFuture<B>;
87
88    #[inline]
89    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
90        Poll::Ready(Ok(()))
91    }
92
93    #[inline]
94    fn call(&mut self, req: Request<B>) -> Self::Future {
95        RouteFuture::new(self.service.clone().oneshot(req))
96    }
97}
98
99pin_project_lite::pin_project! {
100    /// Response future for [`Route`].
101    pub struct RouteFuture<B> {
102        #[pin]
103        future: Oneshot<BoxCloneService<Request<B>, Response<BoxBody>, Infallible>, Request<B>>,
104    }
105}
106
107impl<B> RouteFuture<B> {
108    pub(crate) fn new(future: Oneshot<BoxCloneService<Request<B>, Response<BoxBody>, Infallible>, Request<B>>) -> Self {
109        RouteFuture { future }
110    }
111}
112
113impl<B> Future for RouteFuture<B> {
114    type Output = Result<Response<BoxBody>, Infallible>;
115
116    #[inline]
117    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
118        self.project().future.poll(cx)
119    }
120}
121
122/// A `Sync` HTTP [`Service`] representing a single route, used by the schema-driven router
123/// ([`MultiProtocolRoutingService`](crate::schema::routing::MultiProtocolRoutingService)).
124///
125/// Like [`Route`], constructing it erases the type of the wrapped service. Unlike [`Route`], it is
126/// `Sync`, so the schema router can share one handler set across threads instead of copying it for
127/// every request; the wrapped service must therefore be `Send + Sync`. The protocol routers used
128/// by the generated serde code path keep using [`Route`], which does not require `Sync`.
129pub struct SyncRoute<B = hyper::body::Incoming> {
130    service: BoxCloneSyncService<Request<B>, Response<BoxBody>, Infallible>,
131}
132
133impl<B> SyncRoute<B> {
134    /// Constructs a new [`SyncRoute`] from a well-formed HTTP service which is cloneable.
135    ///
136    /// A service that is already a `SyncRoute` (for example, one passed through
137    /// [`Identity`](tower::layer::util::Identity)) is returned as is rather than boxed again, so
138    /// cloning it stays a single boxed clone.
139    pub fn new<T>(svc: T) -> Self
140    where
141        B: 'static,
142        T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone + Send + Sync + 'static,
143        T::Future: Send + 'static,
144    {
145        match try_downcast::<Self, T>(svc) {
146            Ok(route) => route,
147            Err(svc) => Self {
148                service: BoxCloneSyncService::new(svc),
149            },
150        }
151    }
152
153    /// Calls an owned route without the extra clone [`Service::call`] makes through `&mut self`.
154    pub(crate) fn call_owned(self, req: Request<B>) -> SyncRouteFuture<B> {
155        SyncRouteFuture {
156            future: self.service.oneshot(req),
157        }
158    }
159}
160
161/// Moves `value` out as a `T` when it is one, without allocating.
162fn try_downcast<T: 'static, K: 'static>(value: K) -> Result<T, K> {
163    let mut slot = Some(value);
164    if let Some(slot) = (&mut slot as &mut dyn std::any::Any).downcast_mut::<Option<T>>() {
165        return Ok(slot.take().expect("slot is filled"));
166    }
167    Err(slot.expect("slot is filled"))
168}
169
170impl<ReqBody> Clone for SyncRoute<ReqBody> {
171    fn clone(&self) -> Self {
172        Self {
173            service: self.service.clone(),
174        }
175    }
176}
177
178impl<ReqBody> fmt::Debug for SyncRoute<ReqBody> {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.debug_struct("SyncRoute").finish()
181    }
182}
183
184impl<B> Service<Request<B>> for SyncRoute<B> {
185    type Response = Response<BoxBody>;
186    type Error = Infallible;
187    type Future = SyncRouteFuture<B>;
188
189    #[inline]
190    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
191        Poll::Ready(Ok(()))
192    }
193
194    #[inline]
195    fn call(&mut self, req: Request<B>) -> Self::Future {
196        self.clone().call_owned(req)
197    }
198}
199
200pin_project_lite::pin_project! {
201    /// Response future for [`SyncRoute`].
202    pub struct SyncRouteFuture<B> {
203        #[pin]
204        future: Oneshot<BoxCloneSyncService<Request<B>, Response<BoxBody>, Infallible>, Request<B>>,
205    }
206}
207
208impl<B> Future for SyncRouteFuture<B> {
209    type Output = Result<Response<BoxBody>, Infallible>;
210
211    #[inline]
212    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
213        self.project().future.poll(cx)
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn traits() {
223        use crate::test_helpers::*;
224
225        assert_send::<Route<()>>();
226        assert_send::<SyncRoute<()>>();
227        assert_sync::<SyncRoute<()>>();
228    }
229
230    /// `Route` must not require `Sync`: generated serde handlers and layers may hold `!Sync` state.
231    #[test]
232    fn route_accepts_a_non_sync_service() {
233        let hits = std::cell::Cell::new(0u32); // Send + Clone, not Sync
234        let _route: Route<()> = Route::new(tower::service_fn(move |_: Request<()>| {
235            hits.set(hits.get() + 1);
236            async { Ok::<_, Infallible>(Response::new(crate::body::empty())) }
237        }));
238    }
239
240    #[test]
241    fn try_downcast_moves_out_only_the_matching_type() {
242        assert_eq!(
243            try_downcast::<String, _>(String::from("route")),
244            Ok(String::from("route"))
245        );
246        assert_eq!(try_downcast::<String, _>(7u8), Err(7u8));
247    }
248
249    #[tokio::test]
250    async fn wrapping_a_sync_route_keeps_serving_it() {
251        let route: SyncRoute<()> = SyncRoute::new(tower::service_fn(|_: Request<()>| async {
252            Ok::<_, Infallible>(Response::builder().status(204).body(crate::body::empty()).unwrap())
253        }));
254        let rewrapped = SyncRoute::new(tower::Layer::layer(&tower::layer::util::Identity::new(), route));
255        let response = rewrapped.oneshot(Request::new(())).await.unwrap();
256        assert_eq!(response.status(), 204);
257    }
258}