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
343pub(crate) fn request_id_from(headers: &http::HeaderMap, header: Option<&str>) -> Option<String> {
346 header.and_then(|header| {
347 headers
348 .get(header)
349 .and_then(|value| value.to_str().ok())
350 .filter(|value| !value.is_empty())
351 .map(str::to_string)
352 })
353}
354
355fn content_type(request: &mut http::Request<Body>) {
358 if matches!(request.body(), Body::Bytes(_)) {
359 request
360 .headers_mut()
361 .entry(http::header::CONTENT_TYPE)
362 .or_insert(http::HeaderValue::from_static("application/json"));
363 }
364}
365
366fn accept_header(request: &mut http::Request<Body>, framing: Framing) {
369 content_type(request);
370 if framing == Framing::Sse {
371 request
372 .headers_mut()
373 .entry("Accept")
374 .or_insert(http::HeaderValue::from_static("text/event-stream"));
375 }
376}
377
378fn wrong_content_type(
386 headers: &http::HeaderMap,
387 framing: Framing,
388 relaxed: bool,
389) -> Option<http_client::Error> {
390 if framing != Framing::Sse {
391 return None;
392 }
393 let Some(content_type) = headers.get(&http::header::CONTENT_TYPE) else {
394 return (!relaxed)
395 .then(|| http_client::Error::InvalidContentType(http::HeaderValue::from_static("")));
396 };
397 let event_stream = content_type
398 .to_str()
399 .ok()
400 .and_then(|value| value.parse::<mime::Mime>().ok())
401 .is_some_and(|mime_type| {
402 matches!(
403 (mime_type.type_(), mime_type.subtype()),
404 (mime::TEXT, mime::EVENT_STREAM)
405 )
406 });
407 (!event_stream).then(|| http_client::Error::InvalidContentType(content_type.clone()))
408}
409
410fn byte_request(request: http::Request<Body>) -> Result<http::Request<Vec<u8>>, ProviderError> {
412 let (parts, body) = request.into_parts();
413 match body {
414 Body::Bytes(bytes) => Ok(http::Request::from_parts(parts, bytes)),
415 Body::Multipart(_) => Err(ProviderError::request(
416 "a multipart request cannot open a streamed reply",
417 )),
418 }
419}
420
421struct Framed {
424 payload: Vec<u8>,
425 frame: bool,
426}
427
428impl Framed {
429 fn payload(&self) -> &[u8] {
430 &self.payload
431 }
432
433 fn into_frame(self) -> Option<WireFrame> {
434 self.frame.then(|| match String::from_utf8(self.payload) {
435 Ok(text) => WireFrame::Text(text),
436 Err(error) => WireFrame::Bytes(error.into_bytes()),
437 })
438 }
439}
440
441enum Framer {
443 Sse(SseFramer),
444 Ndjson(NdjsonFramer),
445 Whole(Vec<u8>),
446}
447
448impl Framer {
449 fn new(framing: Framing) -> Self {
450 match framing {
451 Framing::Sse => Self::Sse(SseFramer::new()),
452 Framing::Ndjson => Self::Ndjson(NdjsonFramer::new()),
453 Framing::Whole => Self::Whole(Vec::new()),
454 }
455 }
456
457 fn push(&mut self, chunk: &[u8]) -> Vec<Framed> {
458 match self {
459 Self::Sse(framer) => framer
460 .push(chunk)
461 .map(|event| Framed {
462 frame: !event.data.trim().is_empty(),
463 payload: event.data.into_bytes(),
464 })
465 .collect(),
466 Self::Ndjson(framer) => framer
467 .push(chunk)
468 .map(|line| Framed {
469 frame: true,
470 payload: line,
471 })
472 .collect(),
473 Self::Whole(buffer) => {
474 buffer.extend_from_slice(chunk);
475 Vec::new()
476 }
477 }
478 }
479
480 fn finish(&mut self) -> Vec<Framed> {
481 match self {
482 Self::Sse(_) => Vec::new(),
485 Self::Ndjson(framer) => framer
486 .finish()
487 .map(|line| Framed {
488 frame: true,
489 payload: line,
490 })
491 .into_iter()
492 .collect(),
493 Self::Whole(buffer) => {
494 let payload = std::mem::take(buffer);
495 if payload.is_empty() {
496 Vec::new()
497 } else {
498 vec![Framed {
499 frame: true,
500 payload,
501 }]
502 }
503 }
504 }
505 }
506}
507
508const REJECTED_BODY_LIMIT: usize = 1 << 20;
511
512const REJECTED_CHUNK_LIMIT: usize = 4096;
515
516async fn reject_response(
521 response: http::Response<crate::http_client::BoxedStream>,
522) -> http_client::Error {
523 let status = response.status();
524 let headers = response.headers().clone();
525 let mut body = response.into_body();
526 let mut bytes: Vec<u8> = Vec::new();
527 let mut chunks = 0usize;
528 while let Some(chunk) = body.next().await {
529 chunks += 1;
530 if let Ok(chunk) = chunk {
531 let room = REJECTED_BODY_LIMIT.saturating_sub(bytes.len());
532 bytes.extend_from_slice(chunk.get(..chunk.len().min(room)).unwrap_or_default());
533 }
534 if bytes.len() >= REJECTED_BODY_LIMIT || chunks >= REJECTED_CHUNK_LIMIT {
535 break;
536 }
537 }
538 http_client::Error::InvalidStatusCodeWithDetails {
539 status,
540 body: String::from_utf8_lossy(&bytes).into_owned(),
541 headers,
542 }
543}