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