rama_http_backend/client/
connect_request.rs1use rama_core::{
2 Layer, Service,
3 error::{BoxError, BoxErrorExt as _},
4 extensions::ExtensionsRef,
5};
6use rama_http_types::Request;
7use rama_net::{
8 AuthorityInputExt, ProtocolInputExt, TransportProtocolInputExt,
9 client::{
10 ConnectRequest, ConnectionError, ConnectionErrorKind, ConnectorService,
11 EstablishedClientConnection,
12 },
13 http::HttpRequestVersion,
14};
15use rama_utils::macros::define_inner_service_accessors;
16
17fn try_from_http_request<Body>(request: &Request<Body>) -> Result<ConnectRequest, ConnectionError> {
18 let application_protocol = request.protocol().cloned();
19 let authority = request
20 .authority()
21 .and_then(|authority| {
22 authority.into_host_with_port(
23 application_protocol
24 .as_ref()
25 .and_then(|protocol| protocol.default_port()),
26 )
27 })
28 .ok_or_else(|| {
29 ConnectionError::local(
30 BoxError::from_static_str("HTTP request authority is missing a host or port"),
31 ConnectionErrorKind::InvalidInput,
32 )
33 .context("HTTP connect request adapter: derive authority")
34 })?;
35
36 let extensions = request.extensions().fork();
40 extensions.insert(HttpRequestVersion(request.version()));
44
45 Ok(ConnectRequest::new_with_extensions(authority, extensions)
46 .maybe_with_application_protocol(application_protocol)
47 .maybe_with_transport_protocol(request.transport_protocol()))
48}
49
50#[derive(Debug, Clone)]
58#[non_exhaustive]
59pub struct HttpConnectRequestAdapter<S> {
60 inner: S,
61}
62
63impl<S> HttpConnectRequestAdapter<S> {
64 #[must_use]
66 pub const fn new(inner: S) -> Self {
67 Self { inner }
68 }
69
70 define_inner_service_accessors!();
71}
72
73impl<S, Body> Service<Request<Body>> for HttpConnectRequestAdapter<S>
74where
75 S: ConnectorService<ConnectRequest>,
76 Body: Send + 'static,
77{
78 type Output = EstablishedClientConnection<S::Connection, Request<Body>>;
79 type Error = ConnectionError;
80
81 async fn serve(&self, request: Request<Body>) -> Result<Self::Output, Self::Error> {
82 let connect_request = try_from_http_request(&request)?;
83 let EstablishedClientConnection {
84 conn,
85 input: connect_request,
86 } = Box::pin(self.inner.connect(connect_request)).await?;
87
88 let (mut parts, body) = request.into_parts();
89 parts.extensions = connect_request.extensions;
90
91 Ok(EstablishedClientConnection {
92 conn,
93 input: Request::from_parts(parts, body),
94 })
95 }
96}
97
98#[derive(Debug, Clone, Copy, Default)]
100#[non_exhaustive]
101pub struct HttpConnectRequestAdapterLayer;
102
103impl HttpConnectRequestAdapterLayer {
104 #[must_use]
106 pub const fn new() -> Self {
107 Self
108 }
109}
110
111impl<S> Layer<S> for HttpConnectRequestAdapterLayer {
112 type Service = HttpConnectRequestAdapter<S>;
113
114 fn layer(&self, inner: S) -> Self::Service {
115 HttpConnectRequestAdapter::new(inner)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::convert::Infallible;
122
123 use rama_core::{
124 ServiceInput,
125 extensions::{Extension, ExtensionsRef},
126 service::service_fn,
127 };
128 use rama_http_types::{Method, Request, Version};
129 use rama_net::{
130 Protocol, ProtocolInputExt, TransportProtocolInputExt,
131 client::{ConnectRequest, EstablishedClientConnection},
132 transport::TransportProtocol,
133 };
134
135 use super::*;
136
137 #[derive(Debug)]
138 struct NonCloneBody;
139
140 #[derive(Debug, Extension)]
141 struct RequestMarker;
142
143 #[derive(Debug, Extension)]
144 struct SelectedAttemptMarker;
145
146 #[tokio::test]
147 async fn adapts_without_cloning_body_and_restores_selected_extensions() {
148 let inner = service_fn(async |input: ConnectRequest| {
149 assert_eq!(input.authority.to_string(), "example.com:443");
150 assert_eq!(input.protocol(), Some(&Protocol::HTTPS));
151 assert_eq!(input.transport_protocol(), Some(TransportProtocol::Tcp));
152 assert_eq!(
153 input
154 .extensions
155 .get_ref::<HttpRequestVersion>()
156 .map(|v| v.0),
157 Some(Version::HTTP_2)
158 );
159 assert!(input.extensions.contains::<RequestMarker>());
160 assert!(!input.extensions.self_contains::<RequestMarker>());
161
162 input.extensions.insert(SelectedAttemptMarker);
163 Ok::<_, Infallible>(EstablishedClientConnection {
164 input,
165 conn: ServiceInput::new(()),
166 })
167 });
168 let adapter = HttpConnectRequestAdapter::new(inner);
169
170 let request = Request::builder()
171 .method(Method::POST)
172 .uri("https://example.com/upload")
173 .version(Version::HTTP_2)
174 .body(NonCloneBody)
175 .unwrap();
176 request.extensions().insert(RequestMarker);
177
178 let established = adapter.serve(request).await.unwrap();
179 assert_eq!(established.input.method(), Method::POST);
180 assert_eq!(established.input.uri().path_or_root().as_ref(), "/upload");
181 assert!(established.input.extensions().contains::<RequestMarker>());
182 assert!(
183 established
184 .input
185 .extensions()
186 .contains::<SelectedAttemptMarker>()
187 );
188 }
189
190 #[tokio::test]
191 async fn rejects_request_without_resolvable_authority() {
192 let inner = service_fn(async |input: ConnectRequest| {
193 Ok::<_, Infallible>(EstablishedClientConnection {
194 input,
195 conn: ServiceInput::new(()),
196 })
197 });
198 let adapter = HttpConnectRequestAdapter::new(inner);
199 let request = Request::builder().uri("/").body(NonCloneBody).unwrap();
200
201 let error = adapter.serve(request).await.unwrap_err();
202 assert_eq!(
203 error.domain(),
204 rama_net::client::ConnectionErrorDomain::Local
205 );
206 assert_eq!(error.kind(), ConnectionErrorKind::InvalidInput);
207 }
208}