1use bytes::Bytes;
7use futures::StreamExt;
8
9use super::{Exchange, Opened, Opening, Transport};
10use crate::error::ProviderError;
11use crate::http_client::framing::{Framing, NdjsonFramer, SseFramer};
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 { mode, observation } = exchange;
33 let streamed = mode == Mode::Streaming && framing != Framing::Whole;
36 if streamed && matches!(request.body(), Body::Multipart(_)) {
37 return Opening::failed(ProviderError::request(
38 "a multipart request cannot open a streamed reply",
39 ));
40 }
41 accept_header(&mut request, framing);
42 let path = request.uri().path().to_owned();
45 let declared = route.map_or_else(|| path.clone(), str::to_owned);
46 let exchange = HttpExchange {
47 framing,
48 request_id_header,
49 relaxed_content_type,
50 path,
51 observation: observation.map(|context| (context, AdapterSlot::default())),
52 project,
53 };
54 let http = self.clone();
55 Opening::new(async move {
56 exchange.install(&request, &declared);
58 let slot = exchange.slot().cloned();
59 let mut opened = if streamed {
60 match byte_request(request) {
61 Ok(request) => exchange.streaming(&http, request).await,
62 Err(error) => Opened::failed(error),
63 }
64 } else {
65 exchange.unary(&http, request).await
66 };
67 opened.slot = slot;
68 opened.analysis_only = analysis_only;
69 Ok(opened)
70 })
71 }
72}
73
74struct HttpExchange {
76 framing: Framing,
77 request_id_header: Option<&'static str>,
78 relaxed_content_type: bool,
79 path: String,
80 observation: Option<(AdapterContext, AdapterSlot)>,
81 project: Option<Projector>,
82}
83
84impl HttpExchange {
85 fn slot(&self) -> Option<&AdapterSlot> {
86 self.observation.as_ref().map(|(_, slot)| slot)
87 }
88
89 fn project(&self, payload: &[u8]) {
90 project(self.slot(), self.project, payload);
91 }
92
93 fn install<B>(&self, request: &http::Request<B>, declared: &str) {
94 if let Some((context, slot)) = &self.observation {
95 slot.install(context.attempt_for(request, declared));
96 }
97 }
98
99 fn failed(self, error: ProviderError, request_id: Option<String>) -> Opened<WireFrame> {
100 Opened::failed(error)
101 .with_request_id(request_id)
102 .with_route(self.path)
103 }
104
105 async fn unary<H: HttpClientExt>(
108 self,
109 http: &H,
110 request: http::Request<Body>,
111 ) -> Opened<WireFrame> {
112 let sent = match send(http, request, self.request_id_header, self.slot()).await {
113 Ok(sent) => sent,
114 Err(error) => {
115 if let Some(body) = error.provider_response_body() {
118 self.project(body.as_bytes());
119 }
120 return self.failed(error, None);
121 }
122 };
123 if let Some(rejected) =
128 wrong_content_type(&sent.headers, self.framing, self.relaxed_content_type)
129 {
130 let error = ProviderError::from_transport_error(rejected)
131 .with_provider_status(Some(sent.status))
132 .with_provider_request_id(sent.provider_request_id.clone())
133 .with_response_headers(Some(sent.headers.clone()));
134 self.project(&sent.body);
135 return self.failed(error, sent.provider_request_id);
136 }
137 let document = serde_json::from_slice(&sent.body).ok();
141 let mut framer = Framer::new(self.framing);
142 let payloads: Vec<Framed> = framer
143 .push(&sent.body)
144 .into_iter()
145 .chain(framer.finish())
146 .collect();
147 let slot = self.slot().cloned();
148 let projector = self.project;
149 let frames = futures::stream::iter(payloads).filter_map(move |payload| {
150 project(slot.as_ref(), projector, payload.payload());
151 futures::future::ready(payload.into_frame().map(Ok))
152 });
153 Opened {
154 document,
155 ..Opened::new(frames)
156 .with_request_id(sent.provider_request_id)
157 .with_http(sent.status, sent.headers)
158 .with_route(self.path)
159 }
160 }
161
162 async fn streaming<H: HttpClientExt>(
166 self,
167 http: &H,
168 request: http::Request<Vec<u8>>,
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 framing,
217 path,
218 project: projector,
219 ..
220 } = self;
221 let mut body = response.into_body();
222 let frames = async_stream::stream! {
223 let mut framer = Framer::new(framing);
224 while let Some(chunk) = body.next().await {
225 let chunk = match chunk {
226 Ok(chunk) => chunk,
227 Err(error) => {
228 if let Some(slot) = &slot {
229 slot.error_boundary(AdapterErrorBoundary::Transport);
230 }
231 yield Err(ProviderError::from_transport_error(error));
232 return;
233 }
234 };
235 if let Some(slot) = &slot {
236 slot.bytes(&chunk);
237 }
238 for payload in framer.push(&chunk) {
239 project(slot.as_ref(), projector, payload.payload());
240 if let Some(frame) = payload.into_frame() {
241 yield Ok(frame);
242 }
243 }
244 }
245 for payload in framer.finish() {
246 project(slot.as_ref(), projector, payload.payload());
247 if let Some(frame) = payload.into_frame() {
248 yield Ok(frame);
249 }
250 }
251 };
252 Opened::new(frames)
253 .with_request_id(request_id)
254 .with_http(status, headers)
255 .with_route(path)
256 }
257}
258
259fn project(slot: Option<&AdapterSlot>, project: Option<Projector>, payload: &[u8]) {
262 if let (Some(slot), Some(project)) = (slot, project) {
263 slot.project(|sink| project(payload, sink));
264 }
265}
266
267struct Sent {
269 status: http::StatusCode,
270 headers: http::HeaderMap,
271 body: Bytes,
272 provider_request_id: Option<String>,
273}
274
275async fn send<H>(
277 http: &H,
278 request: http::Request<Body>,
279 request_id_header: Option<&'static str>,
280 observation: Option<&AdapterSlot>,
281) -> Result<Sent, ProviderError>
282where
283 H: HttpClientExt,
284{
285 let (parts, body) = request.into_parts();
286 let response = match body {
287 Body::Bytes(bytes) => {
288 http.send::<_, Bytes>(http::Request::from_parts(parts, bytes))
289 .await
290 }
291 Body::Multipart(form) => {
292 http.send_multipart::<Bytes>(http::Request::from_parts(parts, form))
293 .await
294 }
295 };
296 let response = match response {
297 Ok(response) => response,
298 Err(error) => {
303 if let Some(observation) = observation
304 && let Some(status) = error.non_success_status()
305 {
306 observation.response_with_headers(status, error.non_success_headers());
307 }
308 let request_id = error
309 .non_success_headers()
310 .and_then(|headers| request_id_from(headers, request_id_header));
311 return Err(
312 ProviderError::from_transport_error(error).with_provider_request_id(request_id)
313 );
314 }
315 };
316
317 let (parts, body) = response.into_parts();
321 let status = parts.status;
322 if let Some(observation) = observation {
323 observation.response_with_headers(status, Some(&parts.headers));
324 }
325 let provider_request_id = request_id_from(&parts.headers, request_id_header);
326 let body = body.await.map_err(ProviderError::from_transport_error)?;
327
328 if !status.is_success() {
329 return Err(
330 ProviderError::from_http_response(status, String::from_utf8_lossy(&body))
331 .with_provider_request_id(provider_request_id)
332 .with_response_headers(Some(parts.headers)),
333 );
334 }
335 Ok(Sent {
336 status,
337 headers: parts.headers,
338 body,
339 provider_request_id,
340 })
341}
342
343fn request_id_from(headers: &http::HeaderMap, header: Option<&str>) -> Option<String> {
346 crate::providers::internal::request_id_from_headers(headers, header)
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
415struct Framed {
418 payload: Vec<u8>,
419 frame: bool,
420}
421
422impl Framed {
423 fn payload(&self) -> &[u8] {
424 &self.payload
425 }
426
427 fn into_frame(self) -> Option<WireFrame> {
428 self.frame.then(|| match String::from_utf8(self.payload) {
429 Ok(text) => WireFrame::Text(text),
430 Err(error) => WireFrame::Bytes(error.into_bytes()),
431 })
432 }
433}
434
435enum Framer {
437 Sse(SseFramer),
438 Ndjson(NdjsonFramer),
439 Whole(Vec<u8>),
440}
441
442impl Framer {
443 fn new(framing: Framing) -> Self {
444 match framing {
445 Framing::Sse => Self::Sse(SseFramer::new()),
446 Framing::Ndjson => Self::Ndjson(NdjsonFramer::new()),
447 Framing::Whole => Self::Whole(Vec::new()),
448 }
449 }
450
451 fn push(&mut self, chunk: &[u8]) -> Vec<Framed> {
452 match self {
453 Self::Sse(framer) => framer
454 .push(chunk)
455 .map(|event| Framed {
456 frame: !event.data.trim().is_empty(),
457 payload: event.data.into_bytes(),
458 })
459 .collect(),
460 Self::Ndjson(framer) => framer
461 .push(chunk)
462 .map(|line| Framed {
463 frame: true,
464 payload: line,
465 })
466 .collect(),
467 Self::Whole(buffer) => {
468 buffer.extend_from_slice(chunk);
469 Vec::new()
470 }
471 }
472 }
473
474 fn finish(&mut self) -> Vec<Framed> {
475 match self {
476 Self::Sse(_) => Vec::new(),
479 Self::Ndjson(framer) => framer
480 .finish()
481 .map(|line| Framed {
482 frame: true,
483 payload: line,
484 })
485 .into_iter()
486 .collect(),
487 Self::Whole(buffer) => {
488 let payload = std::mem::take(buffer);
489 if payload.is_empty() {
490 Vec::new()
491 } else {
492 vec![Framed {
493 frame: true,
494 payload,
495 }]
496 }
497 }
498 }
499 }
500}
501
502const REJECTED_BODY_LIMIT: usize = 1 << 20;
505
506const REJECTED_CHUNK_LIMIT: usize = 4096;
509
510async fn reject_response(
515 response: http::Response<crate::http_client::BoxedStream>,
516) -> http_client::Error {
517 let status = response.status();
518 let headers = response.headers().clone();
519 let mut body = response.into_body();
520 let mut bytes: Vec<u8> = Vec::new();
521 let mut chunks = 0usize;
522 while let Some(chunk) = body.next().await {
523 chunks += 1;
524 if let Ok(chunk) = chunk {
525 let room = REJECTED_BODY_LIMIT.saturating_sub(bytes.len());
526 bytes.extend_from_slice(chunk.get(..chunk.len().min(room)).unwrap_or_default());
527 }
528 if bytes.len() >= REJECTED_BODY_LIMIT || chunks >= REJECTED_CHUNK_LIMIT {
529 break;
530 }
531 }
532 http_client::Error::InvalidStatusCodeWithDetails {
533 status,
534 body: String::from_utf8_lossy(&bytes).into_owned(),
535 headers,
536 }
537}