Skip to main content

aws_smithy_legacy_http_server/operation/
upgrade.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::{
7    convert::Infallible,
8    future::{Future, Ready},
9    marker::PhantomData,
10    pin::Pin,
11    task::{Context, Poll},
12    time::Duration,
13};
14
15use futures_util::ready;
16use http::{header, StatusCode, Version};
17use pin_project_lite::pin_project;
18use tower::{util::Oneshot, Service, ServiceExt};
19use tracing::error;
20
21use crate::{
22    body::BoxBody, plugin::Plugin, request::FromRequest, response::IntoResponse,
23    runtime_error::InternalFailureException, service::ServiceShape, settings::RequestBodyReadTimeoutConfig,
24};
25
26use super::OperationShape;
27
28/// A [`Plugin`] responsible for taking an operation [`Service`], accepting and returning Smithy
29/// types and converting it into a [`Service`] taking and returning [`http`] types.
30///
31/// See [`Upgrade`].
32#[derive(Debug, Clone)]
33pub struct UpgradePlugin<Extractors> {
34    _extractors: PhantomData<Extractors>,
35    request_body_read_timeout: Option<RequestBodyReadTimeoutConfig>,
36}
37
38impl<Extractors> Default for UpgradePlugin<Extractors> {
39    fn default() -> Self {
40        Self {
41            _extractors: PhantomData,
42            request_body_read_timeout: None,
43        }
44    }
45}
46
47impl<Extractors> UpgradePlugin<Extractors> {
48    /// Creates a new [`UpgradePlugin`].
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    /// Selects the runtime body-read default appropriate to the modeled operation.
54    #[doc(hidden)]
55    pub fn with_default_request_body_read_timeout(mut self, payload: bool, operation: &'static str) -> Self {
56        self.request_body_read_timeout = RequestBodyReadTimeoutConfig::default_for_operation(payload, operation);
57        self
58    }
59
60    /// Applies a deadline while the request body is read and the operation input is constructed.
61    ///
62    /// The deadline is removed before the operation handler is invoked. Zero disables it.
63    #[doc(hidden)]
64    pub fn with_request_body_read_timeout(mut self, timeout: Duration, operation: &'static str) -> Self {
65        self.request_body_read_timeout = RequestBodyReadTimeoutConfig::new(timeout, operation);
66        self
67    }
68}
69
70impl<Ser, Op, T, Extractors> Plugin<Ser, Op, T> for UpgradePlugin<Extractors>
71where
72    Ser: ServiceShape,
73    Op: OperationShape,
74{
75    type Output = Upgrade<Ser::Protocol, (Op::Input, Extractors), T>;
76
77    fn apply(&self, inner: T) -> Self::Output {
78        Upgrade {
79            _protocol: PhantomData,
80            _input: PhantomData,
81            inner,
82            request_body_read_timeout: self.request_body_read_timeout,
83        }
84    }
85}
86
87/// A [`Service`] responsible for wrapping an operation [`Service`] accepting and returning Smithy
88/// types, and converting it into a [`Service`] accepting and returning [`http`] types.
89pub struct Upgrade<Protocol, Input, S> {
90    _protocol: PhantomData<Protocol>,
91    _input: PhantomData<Input>,
92    inner: S,
93    request_body_read_timeout: Option<RequestBodyReadTimeoutConfig>,
94}
95
96impl<P, Input, S> Clone for Upgrade<P, Input, S>
97where
98    S: Clone,
99{
100    fn clone(&self) -> Self {
101        Self {
102            _protocol: PhantomData,
103            _input: PhantomData,
104            inner: self.inner.clone(),
105            request_body_read_timeout: self.request_body_read_timeout,
106        }
107    }
108}
109
110struct RequestBodyReadTimeoutState {
111    // Created on the first pending poll rather than per request, so requests whose
112    // input is ready immediately never need a timer. This keeps runtimes without a
113    // tokio time driver working and avoids a per-request allocation.
114    sleep: Option<Pin<Box<tokio::time::Sleep>>>,
115    timeout: Duration,
116    version: Version,
117    operation: &'static str,
118    #[cfg(feature = "request-id")]
119    request_id: Option<String>,
120}
121
122impl RequestBodyReadTimeoutState {
123    fn is_expired(&mut self, cx: &mut Context<'_>) -> bool {
124        self.sleep.as_mut().is_some_and(|sleep| {
125            // The timer driver may not have delivered expiry yet, so also check
126            // the clock. Immediately ready inputs never allocate a timer.
127            tokio::time::Instant::now() >= sleep.deadline() || sleep.as_mut().poll(cx).is_ready()
128        })
129    }
130
131    fn response(&self) -> http::Response<crate::body::BoxBody> {
132        #[cfg(feature = "request-id")]
133        if let Some(request_id) = self.request_id.as_deref() {
134            tracing::debug!(
135                operation = self.operation,
136                request_id,
137                timeout_millis = self.timeout.as_millis(),
138                "request body read timed out"
139            );
140        } else {
141            tracing::debug!(
142                operation = self.operation,
143                timeout_millis = self.timeout.as_millis(),
144                "request body read timed out"
145            );
146        }
147        #[cfg(not(feature = "request-id"))]
148        tracing::debug!(
149            operation = self.operation,
150            timeout_millis = self.timeout.as_millis(),
151            "request body read timed out"
152        );
153
154        let mut response = http::Response::builder().status(StatusCode::REQUEST_TIMEOUT);
155        if matches!(self.version, Version::HTTP_10 | Version::HTTP_11) {
156            response = response.header(header::CONNECTION, "close");
157        }
158        response.body(crate::body::empty()).expect("valid response")
159    }
160}
161
162#[cfg(feature = "request-id")]
163fn request_id<B>(req: &http::Request<B>) -> Option<String> {
164    req.extensions()
165        .get::<crate::request::request_id::ServerRequestId>()
166        .map(ToString::to_string)
167}
168
169pin_project! {
170    #[project = InnerProj]
171    #[project_replace = InnerProjReplace]
172    enum Inner<FromFut, HandlerFut> {
173        FromRequest {
174            #[pin]
175            inner: FromFut
176        },
177        Inner {
178            #[pin]
179            call: HandlerFut
180        }
181    }
182}
183
184type InnerAlias<Input, Protocol, B, S> = Inner<<Input as FromRequest<Protocol, B>>::Future, Oneshot<S, Input>>;
185
186pin_project! {
187    /// The [`Service::Future`] of [`Upgrade`].
188    pub struct UpgradeFuture<Protocol, Input, B, S>
189    where
190        Input: FromRequest<Protocol, B>,
191        S: Service<Input>,
192    {
193        service: Option<S>,
194        #[pin]
195        inner: InnerAlias<Input, Protocol, B, S>,
196        request_body_read_timeout: Option<RequestBodyReadTimeoutState>,
197    }
198}
199
200impl<P, Input, B, S> Future for UpgradeFuture<P, Input, B, S>
201where
202    Input: FromRequest<P, B>,
203    <Input as FromRequest<P, B>>::Rejection: std::fmt::Display,
204    S: Service<Input>,
205    S::Response: IntoResponse<P>,
206    S::Error: IntoResponse<P>,
207{
208    type Output = Result<http::Response<crate::body::BoxBody>, Infallible>;
209
210    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
211        loop {
212            let mut this = self.as_mut().project();
213            let this2 = this.inner.as_mut().project();
214
215            let call = match this2 {
216                InnerProj::FromRequest { inner } => {
217                    if let Some(timeout) = this.request_body_read_timeout {
218                        if timeout.is_expired(cx) {
219                            return Poll::Ready(Ok(timeout.response()));
220                        }
221                    }
222                    let result = inner.poll(cx);
223                    if let Some(timeout) = this.request_body_read_timeout {
224                        if result.is_pending() && timeout.sleep.is_none() {
225                            timeout.sleep = Some(Box::pin(tokio::time::sleep(timeout.timeout)));
226                        }
227                        // Input may become ready on the same poll that crosses an
228                        // armed deadline. Expiry must win before calling the handler.
229                        if timeout.is_expired(cx) {
230                            return Poll::Ready(Ok(timeout.response()));
231                        }
232                    }
233                    let result = match result {
234                        Poll::Ready(result) => result,
235                        Poll::Pending => return Poll::Pending,
236                    };
237                    *this.request_body_read_timeout = None;
238                    match result {
239                        Ok(ok) => this
240                            .service
241                            .take()
242                            .expect("futures cannot be polled after completion")
243                            .oneshot(ok),
244                        Err(err) => {
245                            // The error may arise either from a `FromRequest` failure for any user-defined
246                            // handler's additional input parameters, or from a de-serialization failure
247                            // of an input parameter specific to the operation.
248                            tracing::trace!(error = %err, "parameter for the handler cannot be constructed");
249                            return Poll::Ready(Ok(err.into_response()));
250                        }
251                    }
252                }
253                InnerProj::Inner { call } => {
254                    let result = ready!(call.poll(cx));
255                    let output = match result {
256                        Ok(ok) => ok.into_response(),
257                        Err(err) => err.into_response(),
258                    };
259                    return Poll::Ready(Ok(output));
260                }
261            };
262
263            this.inner.as_mut().project_replace(Inner::Inner { call });
264        }
265    }
266}
267
268impl<P, Input, B, S> Service<http::Request<B>> for Upgrade<P, Input, S>
269where
270    Input: FromRequest<P, B>,
271    <Input as FromRequest<P, B>>::Rejection: std::fmt::Display,
272    S: Service<Input> + Clone,
273    S::Response: IntoResponse<P>,
274    S::Error: IntoResponse<P>,
275{
276    type Response = http::Response<crate::body::BoxBody>;
277    type Error = Infallible;
278    type Future = UpgradeFuture<P, Input, B, S>;
279
280    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
281        // The check that the inner service is ready is done by `Oneshot` in `UpgradeFuture`'s
282        // implementation.
283        Poll::Ready(Ok(()))
284    }
285
286    fn call(&mut self, req: http::Request<B>) -> Self::Future {
287        let clone = self.inner.clone();
288        let service = std::mem::replace(&mut self.inner, clone);
289        let request_body_read_timeout = self
290            .request_body_read_timeout
291            .map(|config| RequestBodyReadTimeoutState {
292                sleep: None,
293                timeout: config.timeout,
294                version: req.version(),
295                operation: config.operation,
296                #[cfg(feature = "request-id")]
297                request_id: request_id(&req),
298            });
299        UpgradeFuture {
300            service: Some(service),
301            inner: Inner::FromRequest {
302                inner: <Input as FromRequest<P, B>>::from_request(req),
303            },
304            request_body_read_timeout,
305        }
306    }
307}
308
309/// A [`Service`] which always returns an internal failure message and logs an error.
310#[derive(Copy)]
311pub struct MissingFailure<P> {
312    _protocol: PhantomData<fn(P)>,
313}
314
315impl<P> Default for MissingFailure<P> {
316    fn default() -> Self {
317        Self { _protocol: PhantomData }
318    }
319}
320
321impl<P> Clone for MissingFailure<P> {
322    fn clone(&self) -> Self {
323        MissingFailure { _protocol: PhantomData }
324    }
325}
326
327impl<R, P> Service<R> for MissingFailure<P>
328where
329    InternalFailureException: IntoResponse<P>,
330{
331    type Response = http::Response<BoxBody>;
332    type Error = Infallible;
333    type Future = Ready<Result<Self::Response, Self::Error>>;
334
335    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
336        Poll::Ready(Ok(()))
337    }
338
339    fn call(&mut self, _request: R) -> Self::Future {
340        error!("the operation has not been set");
341        std::future::ready(Ok(InternalFailureException.into_response()))
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use std::sync::{
349        atomic::{AtomicUsize, Ordering},
350        Arc,
351    };
352    use std::task::Waker;
353    use tokio::sync::oneshot;
354
355    struct TestProtocol;
356    struct TestInput;
357    type InputResult = Result<TestInput, StatusCode>;
358    type ResponseFuture = Pin<Box<dyn Future<Output = Result<http::Response<BoxBody>, Infallible>>>>;
359
360    impl IntoResponse<TestProtocol> for StatusCode {
361        fn into_response(self) -> http::Response<BoxBody> {
362            http::Response::builder()
363                .status(self)
364                .body(crate::body::empty())
365                .unwrap()
366        }
367    }
368
369    impl FromRequest<TestProtocol, oneshot::Receiver<InputResult>> for TestInput {
370        type Rejection = StatusCode;
371        type Future = Pin<Box<dyn Future<Output = InputResult> + Send>>;
372
373        fn from_request(request: http::Request<oneshot::Receiver<InputResult>>) -> Self::Future {
374            Box::pin(async { request.into_body().await.expect("input sender must remain alive") })
375        }
376    }
377
378    fn request(
379        timeout: Option<Duration>,
380        version: Version,
381        handler_delay: Option<Duration>,
382    ) -> (ResponseFuture, oneshot::Sender<InputResult>, Arc<AtomicUsize>) {
383        let calls = Arc::new(AtomicUsize::new(0));
384        let handler_calls = calls.clone();
385        let mut upgrade = Upgrade::<TestProtocol, TestInput, _> {
386            _protocol: PhantomData,
387            _input: PhantomData,
388            inner: tower::service_fn(move |_input| {
389                handler_calls.fetch_add(1, Ordering::SeqCst);
390                async move {
391                    if let Some(delay) = handler_delay {
392                        tokio::time::sleep(delay).await;
393                    }
394                    Ok::<_, Infallible>(StatusCode::OK)
395                }
396            }),
397            request_body_read_timeout: timeout.map(|timeout| RequestBodyReadTimeoutConfig {
398                timeout,
399                operation: "test#Operation",
400            }),
401        };
402        let (sender, receiver) = oneshot::channel();
403        let request = http::Request::builder().version(version).body(receiver).unwrap();
404        (Box::pin(upgrade.call(request)), sender, calls)
405    }
406
407    fn assert_pending(future: &mut ResponseFuture) {
408        let mut cx = Context::from_waker(Waker::noop());
409        assert!(future.as_mut().poll(&mut cx).is_pending());
410    }
411
412    #[tokio::test(start_paused = true)]
413    async fn expired_deadline_rejects_ready_input() {
414        for version in [Version::HTTP_11, Version::HTTP_2] {
415            for succeeds in [true, false] {
416                let (mut response, sender, calls) = request(Some(Duration::from_millis(10)), version, None);
417                assert_pending(&mut response);
418                tokio::time::advance(Duration::from_millis(10)).await;
419                // The input is ready before the next poll, so expiry must win over
420                // both successful deserialization and an input rejection.
421                assert!(sender
422                    .send(if succeeds {
423                        Ok(TestInput)
424                    } else {
425                        Err(StatusCode::BAD_REQUEST)
426                    })
427                    .is_ok());
428                let response = response.await.unwrap();
429                assert_eq!(response.status(), StatusCode::REQUEST_TIMEOUT);
430                assert_eq!(calls.load(Ordering::SeqCst), 0);
431                if version == Version::HTTP_11 {
432                    assert_eq!(response.headers()[header::CONNECTION], "close");
433                } else {
434                    assert!(!response.headers().contains_key(header::CONNECTION));
435                }
436            }
437        }
438    }
439
440    #[tokio::test(start_paused = true)]
441    async fn input_ready_before_deadline_reaches_handler() {
442        let (mut response, sender, calls) = request(Some(Duration::from_millis(10)), Version::HTTP_11, None);
443        assert_pending(&mut response);
444        tokio::time::advance(Duration::from_millis(9)).await;
445        assert!(sender.send(Ok(TestInput)).is_ok());
446        assert_eq!(response.await.unwrap().status(), StatusCode::OK);
447        assert_eq!(calls.load(Ordering::SeqCst), 1);
448    }
449
450    #[tokio::test(start_paused = true)]
451    async fn body_deadline_does_not_time_out_handler() {
452        let (mut response, sender, calls) = request(
453            Some(Duration::from_millis(10)),
454            Version::HTTP_11,
455            Some(Duration::from_millis(20)),
456        );
457        assert_pending(&mut response);
458        tokio::time::advance(Duration::from_millis(9)).await;
459        assert!(sender.send(Ok(TestInput)).is_ok());
460        assert_pending(&mut response);
461        tokio::time::advance(Duration::from_millis(100)).await;
462        assert_eq!(response.await.unwrap().status(), StatusCode::OK);
463        assert_eq!(calls.load(Ordering::SeqCst), 1);
464    }
465
466    #[tokio::test(start_paused = true)]
467    async fn disabled_deadline_allows_late_input() {
468        let (mut response, sender, calls) = request(None, Version::HTTP_11, None);
469        assert_pending(&mut response);
470        tokio::time::advance(Duration::from_secs(60)).await;
471        assert!(sender.send(Ok(TestInput)).is_ok());
472        assert_eq!(response.await.unwrap().status(), StatusCode::OK);
473        assert_eq!(calls.load(Ordering::SeqCst), 1);
474    }
475
476    #[test]
477    fn immediately_ready_input_does_not_require_timer_driver() {
478        let (mut response, sender, calls) = request(Some(Duration::from_millis(10)), Version::HTTP_11, None);
479        assert!(sender.send(Ok(TestInput)).is_ok());
480        let mut cx = Context::from_waker(Waker::noop());
481        match response.as_mut().poll(&mut cx) {
482            Poll::Ready(Ok(response)) => assert_eq!(response.status(), StatusCode::OK),
483            _ => panic!("immediately ready input should complete without a timer driver"),
484        }
485        assert_eq!(calls.load(Ordering::SeqCst), 1);
486    }
487}