1use std::sync::{
13 Arc,
14 Mutex,
15};
16use std::time::Duration;
17
18use async_stream::stream;
19use bytes::Bytes;
20use futures_util::{
21 stream as futures_stream,
22 StreamExt,
23};
24use http::header::{
25 CONTENT_LENGTH,
26 CONTENT_TYPE,
27};
28use http::{
29 HeaderMap,
30 Method,
31 StatusCode,
32};
33use serde::de::DeserializeOwned;
34use tokio_util::sync::CancellationToken;
35use url::Url;
36
37use crate::error::{
38 backend_error_mapper::map_reqwest_error,
39 ReqwestErrorPhase,
40};
41use crate::sse::{
42 DoneMarkerPolicy,
43 SseChunkStream,
44 SseEventStream,
45 SseJsonMode,
46};
47use crate::{
48 HttpByteStream,
49 HttpError,
50 HttpErrorKind,
51 HttpResult,
52};
53
54use super::{
55 HttpResponseMeta,
56 HttpResponseOptions,
57};
58
59#[derive(Debug, Clone)]
61struct BodyReadFailure {
62 kind: HttpErrorKind,
64 message: String,
66 method: Option<Method>,
68 url: Option<Url>,
70 status: Option<StatusCode>,
72 response_body_preview: Option<String>,
74 retry_after: Option<Duration>,
76}
77
78impl BodyReadFailure {
79 fn from_error(error: &HttpError) -> Self {
87 Self {
88 kind: error.kind,
89 message: error.message.clone(),
90 method: error.method.clone(),
91 url: error.url.clone(),
92 status: error.status,
93 response_body_preview: error.response_body_preview.clone(),
94 retry_after: error.retry_after,
95 }
96 }
97
98 fn to_error(&self) -> HttpError {
103 let mut error = HttpError::new(
104 self.kind,
105 format!("previous response body read failed: {}", self.message),
106 );
107 if let Some(method) = self.method.as_ref() {
108 error = error.with_method(method);
109 }
110 if let Some(url) = self.url.as_ref() {
111 error = error.with_url(url);
112 }
113 if let Some(status) = self.status {
114 error = error.with_status(status);
115 }
116 if let Some(preview) = self.response_body_preview.as_ref() {
117 error = error.with_response_body_preview(preview.clone());
118 }
119 if let Some(retry_after) = self.retry_after {
120 error = error.with_retry_after(retry_after);
121 }
122 error
123 }
124}
125
126type BodyReadFailureState = Arc<Mutex<Option<BodyReadFailure>>>;
128
129fn map_response_read_error(error: reqwest::Error, method: Method, url: Url) -> HttpError {
140 if error.is_timeout() {
141 return map_reqwest_error(
142 error,
143 HttpErrorKind::Transport,
144 ReqwestErrorPhase::Read,
145 Some(method),
146 Some(url),
147 );
148 }
149
150 HttpError::transport(format!("Failed to read response body: {}", error))
151 .with_method(&method)
152 .with_url(&url)
153 .with_source(error)
154}
155
156#[derive(Debug, Clone)]
158struct HttpResponseRuntime {
159 read_timeout: Duration,
161 cancellation_token: Option<CancellationToken>,
163 request_url: Url,
165 body_read_failure: BodyReadFailureState,
168}
169
170impl HttpResponseRuntime {
171 fn new(
172 read_timeout: Duration,
173 cancellation_token: Option<CancellationToken>,
174 request_url: Url,
175 ) -> Self {
176 Self {
177 read_timeout,
178 cancellation_token,
179 request_url,
180 body_read_failure: Arc::new(Mutex::new(None)),
181 }
182 }
183}
184
185#[derive(Debug)]
187pub struct HttpResponse {
188 pub meta: HttpResponseMeta,
190 backend: Option<reqwest::Response>,
192 buffered_body: Option<Bytes>,
194 runtime: HttpResponseRuntime,
196 options: HttpResponseOptions,
198}
199
200impl HttpResponse {
201 pub fn new(
203 status: StatusCode,
204 headers: HeaderMap,
205 body: Bytes,
206 url: Url,
207 method: Method,
208 ) -> Self {
209 Self {
210 meta: HttpResponseMeta::new(status, headers, url.clone(), method),
211 backend: None,
212 buffered_body: Some(body),
213 runtime: HttpResponseRuntime::new(Duration::from_secs(30), None, url),
214 options: HttpResponseOptions::default(),
215 }
216 }
217
218 pub(crate) fn from_backend(
220 meta: HttpResponseMeta,
221 backend: reqwest::Response,
222 read_timeout: Duration,
223 cancellation_token: Option<CancellationToken>,
224 request_url: Url,
225 options: HttpResponseOptions,
226 ) -> Self {
227 Self {
228 meta,
229 backend: Some(backend),
230 buffered_body: None,
231 runtime: HttpResponseRuntime::new(read_timeout, cancellation_token, request_url),
232 options,
233 }
234 }
235
236 #[inline]
238 pub fn meta(&self) -> &HttpResponseMeta {
239 &self.meta
240 }
241
242 #[inline]
244 pub fn status(&self) -> StatusCode {
245 self.meta.status
246 }
247
248 #[inline]
250 pub fn headers(&self) -> &HeaderMap {
251 &self.meta.headers
252 }
253
254 #[inline]
256 pub fn url(&self) -> &Url {
257 &self.meta.url
258 }
259
260 #[inline]
262 pub fn request_url(&self) -> &Url {
263 &self.runtime.request_url
264 }
265
266 #[inline]
268 pub fn is_success(&self) -> bool {
269 self.status().is_success()
270 }
271
272 #[inline]
274 pub fn retry_after_hint(&self) -> Option<Duration> {
275 self.meta.retry_after_hint()
276 }
277
278 fn previous_body_read_error(&self) -> Option<HttpError> {
284 match self.runtime.body_read_failure.lock() {
285 Ok(guard) => guard.as_ref().map(BodyReadFailure::to_error),
286 Err(_) => Some(
287 HttpError::other("Response body read failure state lock poisoned")
288 .with_method(&self.meta.method)
289 .with_url(&self.runtime.request_url),
290 ),
291 }
292 }
293
294 fn remember_body_read_failure(&self, error: &HttpError) {
299 Self::remember_body_read_failure_state(&self.runtime.body_read_failure, error);
300 }
301
302 fn remember_body_read_failure_state(state: &BodyReadFailureState, error: &HttpError) {
308 if let Ok(mut guard) = state.lock() {
309 if guard.is_none() {
310 *guard = Some(BodyReadFailure::from_error(error));
311 }
312 }
313 }
314
315 pub(crate) async fn into_success_or_status_error(
318 self,
319 message_prefix: &str,
320 ) -> HttpResult<Self> {
321 let status = self.status();
322 if status.is_success() {
323 return Ok(self);
324 }
325 let retry_after = self.retry_after_hint();
326 let method = self.meta.method.clone();
327 let url = self.request_url().clone();
328 let error_preview_limit = self.options.error_response_preview_limit;
329 let body_preview = self.into_error_body_preview(error_preview_limit).await?;
330 let message = format!(
331 "{} with status {} for {} {}; response body preview: {}",
332 message_prefix, status, method, url, body_preview
333 );
334 let mut mapped = HttpError::status(status, message)
335 .with_method(&method)
336 .with_url(&url)
337 .with_response_body_preview(body_preview);
338 if let Some(retry_after) = retry_after {
339 mapped = mapped.with_retry_after(retry_after);
340 }
341 Err(mapped)
342 }
343
344 pub(crate) async fn into_error_body_preview(mut self, max_bytes: usize) -> HttpResult<String> {
351 let limit = max_bytes.max(1);
352 if let Some(error) = self.cancelled_error_if_needed(
353 "Request cancelled while reading status error response body preview",
354 ) {
355 return Err(error);
356 }
357 if let Some(body) = self.buffered_body.take() {
358 let end = body.len().min(limit);
359 return Ok(Self::render_error_body_preview(
360 &body[..end],
361 body.len() > limit,
362 ));
363 }
364 let Some(backend) = self.backend.take() else {
365 return Ok("<empty>".to_string());
366 };
367 Self::read_error_body_preview(
368 backend,
369 self.runtime.read_timeout,
370 self.runtime.cancellation_token.clone(),
371 self.meta.method.clone(),
372 self.runtime.request_url.clone(),
373 limit,
374 )
375 .await
376 }
377
378 pub async fn bytes(&mut self) -> HttpResult<Bytes> {
380 if let Some(body) = &self.buffered_body {
381 return Ok(body.clone());
382 }
383 if let Some(error) = self.previous_body_read_error() {
384 return Err(error);
385 }
386 let Some(mut backend) = self.backend.take() else {
387 self.buffered_body = Some(Bytes::new());
388 return Ok(Bytes::new());
389 };
390
391 let method = self.meta.method.clone();
392 let url = self.runtime.request_url.clone();
393 let read_timeout = self.runtime.read_timeout;
394 let cancellation_token = self.runtime.cancellation_token.clone();
395 let mut body = bytes::BytesMut::new();
396
397 loop {
398 let next = if let Some(token) = &cancellation_token {
399 tokio::select! {
400 _ = token.cancelled() => {
401 let error = HttpError::cancelled("Request cancelled while reading response body")
402 .with_method(&method)
403 .with_url(&url);
404 self.remember_body_read_failure(&error);
405 return Err(error);
406 }
407 item = tokio::time::timeout(read_timeout, backend.chunk()) => item,
408 }
409 } else {
410 tokio::time::timeout(read_timeout, backend.chunk()).await
411 };
412
413 match next {
414 Ok(Ok(Some(chunk))) => body.extend_from_slice(&chunk),
415 Ok(Ok(None)) => {
416 let body = body.freeze();
417 self.buffered_body = Some(body.clone());
418 return Ok(body);
419 }
420 Ok(Err(error)) => {
421 let error = map_response_read_error(error, method, url);
422 self.remember_body_read_failure(&error);
423 return Err(error);
424 }
425 Err(_) => {
426 let error = HttpError::read_timeout(format!(
427 "Read timeout after {:?} while reading response body",
428 read_timeout
429 ))
430 .with_method(&self.meta.method)
431 .with_url(&self.runtime.request_url);
432 self.remember_body_read_failure(&error);
433 return Err(error);
434 }
435 }
436 }
437 }
438
439 pub fn stream(&mut self) -> HttpResult<HttpByteStream> {
441 if let Some(body) = self.buffered_body.as_ref() {
442 let bytes = body.clone();
443 return Ok(Box::pin(futures_stream::once(async move { Ok(bytes) })));
444 }
445 if let Some(error) = self.previous_body_read_error() {
446 return Err(error);
447 }
448 if let Some(error) = self
449 .cancelled_error_if_needed("Streaming response cancelled before reading response body")
450 {
451 return Err(error);
452 }
453 let Some(backend) = self.backend.take() else {
454 return Ok(Box::pin(futures_stream::empty()));
455 };
456
457 let method = self.meta.method.clone();
458 let url = self.runtime.request_url.clone();
459 let read_timeout = self.runtime.read_timeout;
460 let cancellation_token = self.runtime.cancellation_token.clone();
461 let body_read_failure = self.runtime.body_read_failure.clone();
462 let mut stream = backend.bytes_stream();
463 let wrapped = stream! {
464 loop {
465 let next = if let Some(token) = &cancellation_token {
466 tokio::select! {
467 _ = token.cancelled() => {
468 let error = HttpError::cancelled("Streaming response cancelled while reading body")
469 .with_method(&method)
470 .with_url(&url);
471 Self::remember_body_read_failure_state(&body_read_failure, &error);
472 yield Err(error);
473 break;
474 }
475 item = tokio::time::timeout(read_timeout, stream.next()) => item,
476 }
477 } else {
478 tokio::time::timeout(read_timeout, stream.next()).await
479 };
480 match next {
481 Ok(Some(Ok(bytes))) => yield Ok(bytes),
482 Ok(Some(Err(error))) => {
483 let mapped = map_response_read_error(error, method.clone(), url.clone());
484 Self::remember_body_read_failure_state(&body_read_failure, &mapped);
485 yield Err(mapped);
486 break;
487 }
488 Ok(None) => break,
489 Err(_) => {
490 let error = HttpError::read_timeout(format!(
491 "Read timeout after {:?} while streaming response",
492 read_timeout
493 ))
494 .with_method(&method)
495 .with_url(&url);
496 Self::remember_body_read_failure_state(&body_read_failure, &error);
497 yield Err(error);
498 break;
499 }
500 }
501 }
502 };
503 Ok(Box::pin(wrapped))
504 }
505
506 pub async fn text(&mut self) -> HttpResult<String> {
508 let body = self.bytes().await?;
509 String::from_utf8(body.to_vec()).map_err(|error| {
510 HttpError::decode(format!(
511 "Failed to decode response body as UTF-8: {}",
512 error
513 ))
514 .with_status(self.meta.status)
515 .with_url(&self.meta.url)
516 })
517 }
518
519 pub async fn json<T>(&mut self) -> HttpResult<T>
521 where
522 T: DeserializeOwned,
523 {
524 let body = self.bytes().await?;
525 serde_json::from_slice(&body).map_err(|error| {
526 HttpError::decode(format!("Failed to decode response JSON: {}", error))
527 .with_status(self.meta.status)
528 .with_url(&self.meta.url)
529 })
530 }
531
532 #[inline]
538 pub fn sse_max_line_bytes(mut self, max_line_bytes: usize) -> Self {
539 self.options.sse_max_line_bytes = max_line_bytes.max(1);
540 self
541 }
542
543 #[inline]
547 pub fn sse_max_frame_bytes(mut self, max_frame_bytes: usize) -> Self {
548 self.options.sse_max_frame_bytes = max_frame_bytes.max(1);
549 self
550 }
551
552 #[inline]
554 pub fn sse_json_mode(mut self, mode: SseJsonMode) -> Self {
555 self.options.sse_json_mode = mode;
556 self
557 }
558
559 #[inline]
561 pub fn sse_done_marker_policy(mut self, policy: DoneMarkerPolicy) -> Self {
562 self.options.sse_done_marker_policy = policy;
563 self
564 }
565
566 pub fn sse_events(mut self) -> SseEventStream {
570 let max_line_bytes = self.options.sse_max_line_bytes;
571 let max_frame_bytes = self.options.sse_max_frame_bytes;
572 match self.stream() {
573 Ok(stream) => crate::sse::decode_events_from_stream_with_limits(
574 stream,
575 max_line_bytes,
576 max_frame_bytes,
577 ),
578 Err(error) => Box::pin(futures_stream::once(async move { Err(error) })),
579 }
580 }
581
582 pub fn sse_chunks<T>(mut self) -> SseChunkStream<T>
586 where
587 T: DeserializeOwned + Send + 'static,
588 {
589 let done_policy = self.options.sse_done_marker_policy.clone();
590 let mode = self.options.sse_json_mode;
591 let max_line_bytes = self.options.sse_max_line_bytes;
592 let max_frame_bytes = self.options.sse_max_frame_bytes;
593 match self.stream() {
594 Ok(stream) => crate::sse::decode_json_chunks_from_stream_with_limits(
595 stream,
596 done_policy,
597 mode,
598 max_line_bytes,
599 max_frame_bytes,
600 ),
601 Err(error) => Box::pin(futures_stream::once(async move { Err(error) })),
602 }
603 }
604
605 pub(crate) fn buffered_body_for_logging(&self) -> Option<&Bytes> {
610 self.buffered_body.as_ref()
611 }
612
613 pub(crate) fn can_buffer_body_for_logging(&self, body_log_limit: usize) -> bool {
622 if self.backend.is_none() {
623 return false;
624 }
625 if self.is_sse_response() {
626 return false;
627 }
628 self.content_length_hint()
629 .is_some_and(|content_length| content_length <= body_log_limit as u64)
630 }
631
632 async fn read_error_body_preview(
639 mut response: reqwest::Response,
640 read_timeout: Duration,
641 cancellation_token: Option<CancellationToken>,
642 method: Method,
643 url: Url,
644 max_bytes: usize,
645 ) -> HttpResult<String> {
646 let limit = max_bytes.max(1);
647 let mut preview = Vec::new();
648 let mut truncated = false;
649
650 loop {
651 let next = if let Some(token) = cancellation_token.as_ref() {
652 tokio::select! {
653 _ = token.cancelled() => {
654 return Err(HttpError::cancelled(
655 "Request cancelled while reading status error response body preview",
656 )
657 .with_method(&method)
658 .with_url(&url));
659 }
660 item = tokio::time::timeout(read_timeout, response.chunk()) => item,
661 }
662 } else {
663 tokio::time::timeout(read_timeout, response.chunk()).await
664 };
665 match next {
666 Ok(Ok(Some(chunk))) => {
667 if preview.len() >= limit {
668 truncated = true;
669 break;
670 }
671 let remaining = limit - preview.len();
672 if chunk.len() > remaining {
673 preview.extend_from_slice(&chunk[..remaining]);
674 truncated = true;
675 break;
676 }
677 preview.extend_from_slice(&chunk);
678 }
679 Ok(Ok(None)) => break,
680 Ok(Err(error)) => {
681 return Ok(format!(
682 "<error body unavailable: failed to read response body: {}>",
683 error
684 ));
685 }
686 Err(_) => {
687 return Ok(format!(
688 "<error body unavailable: read timeout after {:?}>",
689 read_timeout
690 ));
691 }
692 }
693 }
694 Ok(Self::render_error_body_preview(&preview, truncated))
695 }
696
697 fn cancelled_error_if_needed(&self, message: &str) -> Option<HttpError> {
706 if self
707 .runtime
708 .cancellation_token
709 .as_ref()
710 .is_some_and(CancellationToken::is_cancelled)
711 {
712 Some(
713 HttpError::cancelled(message.to_string())
714 .with_method(&self.meta.method)
715 .with_url(&self.runtime.request_url),
716 )
717 } else {
718 None
719 }
720 }
721
722 fn content_length_hint(&self) -> Option<u64> {
724 self.meta
725 .headers
726 .get(CONTENT_LENGTH)
727 .and_then(|value| value.to_str().ok())
728 .and_then(|value| value.parse::<u64>().ok())
729 }
730
731 fn is_sse_response(&self) -> bool {
733 self.meta
734 .headers
735 .get(CONTENT_TYPE)
736 .and_then(|value| value.to_str().ok())
737 .is_some_and(|content_type| {
738 content_type
739 .to_ascii_lowercase()
740 .starts_with("text/event-stream")
741 })
742 }
743
744 fn render_error_body_preview(bytes: &[u8], truncated: bool) -> String {
745 if bytes.is_empty() {
746 return "<empty>".to_string();
747 }
748 let suffix = if truncated { "...<truncated>" } else { "" };
749 match std::str::from_utf8(bytes) {
750 Ok(text) => format!("{text}{suffix}"),
751 Err(_) => format!("<binary {} bytes>{suffix}", bytes.len()),
752 }
753 }
754}