aws_smithy_http_server/routing/
mod.rs1mod into_make_service;
11mod into_make_service_with_connect_info;
12#[cfg(feature = "aws-lambda")]
13#[cfg_attr(docsrs, doc(cfg(feature = "aws-lambda")))]
14mod lambda_handler;
15
16#[doc(hidden)]
17pub mod request_spec;
18
19mod route;
20
21pub(crate) mod tiny_map;
22
23use std::{
24 error::Error,
25 fmt,
26 future::{ready, Future, Ready},
27 marker::PhantomData,
28 pin::Pin,
29 task::{Context, Poll},
30};
31
32use bytes::Bytes;
33use futures_util::{
34 future::{Either, MapOk},
35 TryFutureExt,
36};
37use http::Response;
38use http_body::Body as HttpBody;
39use tower::{util::Oneshot, Service, ServiceExt};
40
41use crate::{
42 body::{boxed, BoxBody},
43 error::BoxError,
44 response::IntoResponse,
45};
46
47#[cfg(feature = "aws-lambda")]
48#[cfg_attr(docsrs, doc(cfg(feature = "aws-lambda")))]
49pub use self::lambda_handler::LambdaHandler;
50
51#[allow(deprecated)]
52pub use self::{
53 into_make_service::IntoMakeService,
54 into_make_service_with_connect_info::{Connected, IntoMakeServiceWithConnectInfo},
55 route::Route,
56};
57
58pub(crate) const UNKNOWN_OPERATION_EXCEPTION: &str = "UnknownOperationException";
59
60pub(crate) fn method_disallowed() -> http::Response<BoxBody> {
62 let mut responses = http::Response::default();
63 *responses.status_mut() = http::StatusCode::METHOD_NOT_ALLOWED;
64 responses
65}
66
67pub trait Router<B> {
69 type Service;
70 type Error;
71
72 fn match_route(&self, request: &http::Request<B>) -> Result<Self::Service, Self::Error>;
74}
75
76pub struct RoutingService<R, Protocol> {
80 router: R,
81 _protocol: PhantomData<Protocol>,
82}
83
84impl<R, P> fmt::Debug for RoutingService<R, P>
85where
86 R: fmt::Debug,
87{
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.debug_struct("RoutingService")
90 .field("router", &self.router)
91 .field("_protocol", &self._protocol)
92 .finish()
93 }
94}
95
96impl<R, P> Clone for RoutingService<R, P>
97where
98 R: Clone,
99{
100 fn clone(&self) -> Self {
101 Self {
102 router: self.router.clone(),
103 _protocol: PhantomData,
104 }
105 }
106}
107
108impl<R, P> RoutingService<R, P> {
109 pub fn new(router: R) -> Self {
111 Self {
112 router,
113 _protocol: PhantomData,
114 }
115 }
116
117 pub fn map<RNew, F>(self, f: F) -> RoutingService<RNew, P>
119 where
120 F: FnOnce(R) -> RNew,
121 {
122 RoutingService {
123 router: f(self.router),
124 _protocol: PhantomData,
125 }
126 }
127}
128
129type EitherOneshotReady<S, B> = Either<
130 MapOk<Oneshot<S, http::Request<B>>, fn(<S as Service<http::Request<B>>>::Response) -> http::Response<BoxBody>>,
131 Ready<Result<http::Response<BoxBody>, <S as Service<http::Request<B>>>::Error>>,
132>;
133
134pin_project_lite::pin_project! {
135 pub struct RoutingFuture<S, B> where S: Service<http::Request<B>> {
136 #[pin]
137 inner: EitherOneshotReady<S, B>
138 }
139}
140
141impl<S, B> RoutingFuture<S, B>
142where
143 S: Service<http::Request<B>>,
144{
145 pub(super) fn from_oneshot<RespB>(future: Oneshot<S, http::Request<B>>) -> Self
147 where
148 S: Service<http::Request<B>, Response = http::Response<RespB>>,
149 RespB: HttpBody<Data = Bytes> + Send + 'static,
150 RespB::Error: Into<BoxError>,
151 {
152 Self {
153 inner: Either::Left(future.map_ok(|x| x.map(boxed))),
154 }
155 }
156
157 pub(super) fn from_response(response: http::Response<BoxBody>) -> Self {
159 Self {
160 inner: Either::Right(ready(Ok(response))),
161 }
162 }
163}
164
165impl<S, B> Future for RoutingFuture<S, B>
166where
167 S: Service<http::Request<B>>,
168{
169 type Output = Result<http::Response<BoxBody>, S::Error>;
170
171 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
172 self.project().inner.poll(cx)
173 }
174}
175
176impl<R, P, B, RespB> Service<http::Request<B>> for RoutingService<R, P>
177where
178 R: Router<B>,
179 R::Service: Service<http::Request<B>, Response = http::Response<RespB>> + Clone,
180 R::Error: IntoResponse<P> + Error,
181 RespB: HttpBody<Data = Bytes> + Send + 'static,
182 RespB::Error: Into<BoxError>,
183{
184 type Response = Response<BoxBody>;
185 type Error = <R::Service as Service<http::Request<B>>>::Error;
186 type Future = RoutingFuture<R::Service, B>;
187
188 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
189 Poll::Ready(Ok(()))
190 }
191
192 fn call(&mut self, req: http::Request<B>) -> Self::Future {
193 tracing::debug!("inside routing service call");
194 match self.router.match_route(&req) {
195 Ok(ok) => RoutingFuture::from_oneshot(ok.oneshot(req)),
197 Err(error) => {
199 tracing::debug!(%error, "failed to route");
200 RoutingFuture::from_response(error.into_response())
201 }
202 }
203 }
204}