1use bytes::Bytes;
7use futures::StreamExt;
8
9use super::{Exchange, Opened, Opening, Transport};
10use crate::error::ProviderError;
11use crate::http_client::framing::{Framer, Framing};
12use crate::http_client::{self, HttpClientExt};
13use crate::observe::{AdapterContext, AdapterErrorBoundary, AdapterSlot};
14use crate::wasm_compat::{WasmCompatSend, WasmCompatSync};
15use crate::wire::{Body, Encoded, Mode, Projector, Wire, WireFrame};
16
17impl<W, H> Transport<W> for H
18where
19 W: Wire<Payload = Encoded, Frame = WireFrame>,
20 H: HttpClientExt + Clone + WasmCompatSend + WasmCompatSync + 'static,
21{
22 fn send(&self, payload: Encoded, exchange: Exchange) -> Opening<WireFrame> {
23 let Encoded {
24 mut request,
25 framing,
26 request_id_header,
27 relaxed_content_type,
28 route,
29 project,
30 analysis_only,
31 } = payload;
32 let Exchange {
33 mode,
34 observation,
35 extensions,
36 } = exchange;
37 request.extensions_mut().extend(extensions);
38 let streamed = framing.framer().filter(|_| mode == Mode::Streaming);
41 if streamed.is_some() && matches!(request.body(), Body::Multipart(_)) {
42 return Opening::failed(ProviderError::request(
43 "a multipart request cannot open a streamed reply",
44 ));
45 }
46 accept_header(&mut request, framing);
47 let path = request.uri().path().to_owned();
50 let declared = route.map_or_else(|| path.clone(), str::to_owned);
51 let exchange = HttpExchange {
52 framing,
53 request_id_header,
54 relaxed_content_type,
55 path,
56 observation: observation.map(|context| (context, AdapterSlot::default())),
57 project,
58 };
59 let http = self.clone();
60 Opening::new(async move {
61 exchange.install(&request, &declared);
63 let slot = exchange.slot().cloned();
64 let mut opened = match streamed {
65 Some(framer) => match byte_request(request) {
66 Ok(request) => exchange.streaming(&http, request, framer).await,
67 Err(error) => Opened::failed(error),
68 },
69 None => exchange.unary(&http, request).await,
70 };
71 opened.slot = slot;
72 opened.analysis_only = analysis_only;
73 Ok(opened)
74 })
75 }
76}
77
78struct HttpExchange {
80 framing: Framing,
81 request_id_header: Option<&'static str>,
82 relaxed_content_type: bool,
83 path: String,
84 observation: Option<(AdapterContext, AdapterSlot)>,
85 project: Option<Projector>,
86}
87
88impl HttpExchange {
89 fn slot(&self) -> Option<&AdapterSlot> {
90 self.observation.as_ref().map(|(_, slot)| slot)
91 }
92
93 fn project(&self, payload: &[u8]) {
94 project(self.slot(), self.project, payload);
95 }
96
97 fn install<B>(&self, request: &http::Request<B>, declared: &str) {
98 if let Some((context, slot)) = &self.observation {
99 slot.install(context.attempt_for(request, declared));
100 }
101 }
102
103 fn failed(self, error: ProviderError, request_id: Option<String>) -> Opened<WireFrame> {
104 Opened::failed(error)
105 .with_request_id(request_id)
106 .with_route(self.path)
107 }
108
109 async fn unary<H: HttpClientExt>(
112 self,
113 http: &H,
114 request: http::Request<Body>,
115 ) -> Opened<WireFrame> {
116 let sent = match send(http, request, self.request_id_header, self.slot()).await {
117 Ok(sent) => sent,
118 Err(error) => {
119 if let Some(body) = error.provider_response_body() {
122 self.project(body.as_bytes());
123 }
124 return self.failed(error, None);
125 }
126 };
127 if let Some(rejected) =
132 wrong_content_type(&sent.headers, self.framing, self.relaxed_content_type)
133 {
134 let error = ProviderError::from_transport_error(rejected)
135 .with_provider_status(Some(sent.status))
136 .with_provider_request_id(sent.provider_request_id.clone())
137 .with_response_headers(Some(sent.headers.clone()));
138 self.project(&sent.body);
139 return self.failed(error, sent.provider_request_id);
140 }
141 let document = serde_json::from_slice(&sent.body).ok();
145 let frames = self.framing.split(&sent.body);
146 let slot = self.slot().cloned();
147 let projector = self.project;
148 let frames = futures::stream::iter(frames).map(move |frame| {
149 project(slot.as_ref(), projector, frame.as_bytes());
150 Ok(frame)
151 });
152 Opened {
153 document,
154 ..Opened::new(frames)
155 .with_request_id(sent.provider_request_id)
156 .with_http(sent.status, sent.headers)
157 .with_route(self.path)
158 }
159 }
160
161 async fn streaming<H: HttpClientExt>(
165 self,
166 http: &H,
167 request: http::Request<Vec<u8>>,
168 mut framer: Framer,
169 ) -> Opened<WireFrame> {
170 let response = match http.send_streaming(request).await {
171 Ok(response) if response.status() != http::StatusCode::OK => {
174 Err(reject_response(response).await)
175 }
176 Ok(response) => {
177 match wrong_content_type(
178 response.headers(),
179 self.framing,
180 self.relaxed_content_type,
181 ) {
182 Some(error) => Err(error),
183 None => Ok(response),
184 }
185 }
186 other => other,
187 };
188 let response = match response {
189 Ok(response) => response,
190 Err(error) => {
191 if let Some(slot) = self.slot() {
192 slot.error_boundary(AdapterErrorBoundary::from_http(&error));
193 if let Some(status) = error.non_success_status() {
194 slot.response_with_headers(status, error.non_success_headers());
195 }
196 if let Some(body) = error.non_success_body() {
197 self.project(body.as_bytes());
198 }
199 }
200 let request_id = error
201 .non_success_headers()
202 .and_then(|headers| request_id_from(headers, self.request_id_header));
203 let error = ProviderError::from_transport_error(error)
204 .with_provider_request_id(request_id.clone());
205 return self.failed(error, request_id);
206 }
207 };
208 if let Some(slot) = self.slot() {
209 slot.response_with_headers(response.status(), Some(response.headers()));
210 }
211 let request_id = request_id_from(response.headers(), self.request_id_header);
212 let status = response.status();
213 let headers = response.headers().clone();
214 let slot = self.slot().cloned();
215 let Self {
216 path,
217 project: projector,
218 ..
219 } = self;
220 let mut body = response.into_body();
221 let frames = async_stream::stream! {
222 while let Some(chunk) = body.next().await {
223 let chunk = match chunk {
224 Ok(chunk) => chunk,
225 Err(error) => {
226 if let Some(slot) = &slot {
227 slot.error_boundary(AdapterErrorBoundary::Transport);
228 }
229 yield Err(ProviderError::from_transport_error(error));
230 return;
231 }
232 };
233 for frame in framer.push(&chunk) {
234 project(slot.as_ref(), projector, frame.as_bytes());
235 yield Ok(frame);
236 }
237 }
238 if let Some(slot) = &slot {
239 slot.unframed(framer.pending());
240 }
241 if let Some(frame) = framer.finish() {
242 project(slot.as_ref(), projector, frame.as_bytes());
243 yield Ok(frame);
244 }
245 };
246 Opened::new(frames)
247 .with_request_id(request_id)
248 .with_http(status, headers)
249 .with_route(path)
250 }
251}
252
253fn project(slot: Option<&AdapterSlot>, project: Option<Projector>, payload: &[u8]) {
256 if let (Some(slot), Some(project)) = (slot, project) {
257 slot.project(|sink| project(payload, sink));
258 }
259}
260
261struct Sent {
263 status: http::StatusCode,
264 headers: http::HeaderMap,
265 body: Bytes,
266 provider_request_id: Option<String>,
267}
268
269async fn send<H>(
271 http: &H,
272 request: http::Request<Body>,
273 request_id_header: Option<&'static str>,
274 observation: Option<&AdapterSlot>,
275) -> Result<Sent, ProviderError>
276where
277 H: HttpClientExt,
278{
279 let (parts, body) = request.into_parts();
280 let response = match body {
281 Body::Bytes(bytes) => {
282 http.send::<_, Bytes>(http::Request::from_parts(parts, bytes))
283 .await
284 }
285 Body::Multipart(form) => {
286 http.send_multipart::<Bytes>(http::Request::from_parts(parts, form))
287 .await
288 }
289 };
290 let response = match response {
291 Ok(response) => response,
292 Err(error) => {
297 if let Some(observation) = observation
298 && let Some(status) = error.non_success_status()
299 {
300 observation.response_with_headers(status, error.non_success_headers());
301 }
302 let request_id = error
303 .non_success_headers()
304 .and_then(|headers| request_id_from(headers, request_id_header));
305 return Err(
306 ProviderError::from_transport_error(error).with_provider_request_id(request_id)
307 );
308 }
309 };
310
311 let (parts, body) = response.into_parts();
315 let status = parts.status;
316 if let Some(observation) = observation {
317 observation.response_with_headers(status, Some(&parts.headers));
318 }
319 let provider_request_id = request_id_from(&parts.headers, request_id_header);
320 let body = body.await.map_err(ProviderError::from_transport_error)?;
321
322 if !status.is_success() {
323 return Err(
324 ProviderError::from_http_response(status, String::from_utf8_lossy(&body))
325 .with_provider_request_id(provider_request_id)
326 .with_response_headers(Some(parts.headers)),
327 );
328 }
329 Ok(Sent {
330 status,
331 headers: parts.headers,
332 body,
333 provider_request_id,
334 })
335}
336
337pub(crate) fn request_id_from(headers: &http::HeaderMap, header: Option<&str>) -> Option<String> {
340 header.and_then(|header| {
341 headers
342 .get(header)
343 .and_then(|value| value.to_str().ok())
344 .filter(|value| !value.is_empty())
345 .map(str::to_string)
346 })
347}
348
349fn content_type(request: &mut http::Request<Body>) {
352 if matches!(request.body(), Body::Bytes(_)) {
353 request
354 .headers_mut()
355 .entry(http::header::CONTENT_TYPE)
356 .or_insert(http::HeaderValue::from_static("application/json"));
357 }
358}
359
360fn accept_header(request: &mut http::Request<Body>, framing: Framing) {
363 content_type(request);
364 if framing == Framing::Sse {
365 request
366 .headers_mut()
367 .entry("Accept")
368 .or_insert(http::HeaderValue::from_static("text/event-stream"));
369 }
370}
371
372fn wrong_content_type(
380 headers: &http::HeaderMap,
381 framing: Framing,
382 relaxed: bool,
383) -> Option<http_client::Error> {
384 if framing != Framing::Sse {
385 return None;
386 }
387 let Some(content_type) = headers.get(&http::header::CONTENT_TYPE) else {
388 return (!relaxed)
389 .then(|| http_client::Error::InvalidContentType(http::HeaderValue::from_static("")));
390 };
391 let event_stream = content_type
392 .to_str()
393 .ok()
394 .and_then(|value| value.parse::<mime::Mime>().ok())
395 .is_some_and(|mime_type| {
396 matches!(
397 (mime_type.type_(), mime_type.subtype()),
398 (mime::TEXT, mime::EVENT_STREAM)
399 )
400 });
401 (!event_stream).then(|| http_client::Error::InvalidContentType(content_type.clone()))
402}
403
404fn byte_request(request: http::Request<Body>) -> Result<http::Request<Vec<u8>>, ProviderError> {
406 let (parts, body) = request.into_parts();
407 match body {
408 Body::Bytes(bytes) => Ok(http::Request::from_parts(parts, bytes)),
409 Body::Multipart(_) => Err(ProviderError::request(
410 "a multipart request cannot open a streamed reply",
411 )),
412 }
413}
414
415const REJECTED_BODY_LIMIT: usize = 1 << 20;
418
419const REJECTED_CHUNK_LIMIT: usize = 4096;
422
423async fn reject_response(
428 response: http::Response<crate::http_client::BoxedStream>,
429) -> http_client::Error {
430 let status = response.status();
431 let headers = response.headers().clone();
432 let mut body = response.into_body();
433 let mut bytes: Vec<u8> = Vec::new();
434 let mut chunks = 0usize;
435 while let Some(chunk) = body.next().await {
436 chunks += 1;
437 if let Ok(chunk) = chunk {
438 let room = REJECTED_BODY_LIMIT.saturating_sub(bytes.len());
439 bytes.extend_from_slice(chunk.get(..chunk.len().min(room)).unwrap_or_default());
440 }
441 if bytes.len() >= REJECTED_BODY_LIMIT || chunks >= REJECTED_CHUNK_LIMIT {
442 break;
443 }
444 }
445 http_client::Error::InvalidStatusCodeWithDetails {
446 status,
447 body: String::from_utf8_lossy(&bytes).into_owned(),
448 headers,
449 }
450}