1pub use crate::timing::RequestTiming;
4
5use std::{
8 fmt::Debug,
9 net::SocketAddr,
10 path::{Path, PathBuf},
11 pin::Pin,
12 sync::{
13 Arc,
14 atomic::{AtomicBool, Ordering},
15 },
16 task::{Context, Poll},
17 time::{Duration, Instant},
18};
19
20use bytes::Bytes;
21use futures::{Stream, StreamExt, stream};
22use http::header::{CONTENT_LENGTH, HeaderMap};
23use reqwest::{StatusCode, Url, Version};
24use serde::de::DeserializeOwned;
25use tokio::{io::AsyncWriteExt, sync::watch};
26
27pub use crate::body::BodyReader;
28use crate::{
29 body::Claim,
30 error::{FaithError, FaithErrorKind},
31 timing::TimingSlot,
32};
33
34use crate::integrity::{finish_integrity, integrity_checker, verify_integrity};
35
36#[derive(Debug)]
38pub struct PeerInformation {
39 pub address: Option<SocketAddr>,
41 pub certificate: Option<Vec<u8>>,
43}
44
45#[derive(Debug, Clone, Default)]
47pub struct FileDestination {
48 pub overwrite: bool,
51 pub mode: Option<u32>,
53}
54
55pub(crate) const PROGRESS_INTERVAL: Duration = Duration::from_millis(50);
59
60pub(crate) async fn open_destination(
63 path: &Path,
64 options: &FileDestination,
65) -> Result<tokio::fs::File, FaithError> {
66 let mut open = tokio::fs::OpenOptions::new();
67 open.write(true);
68 if options.overwrite {
69 open.create(true).truncate(true);
71 } else {
72 open.create_new(true);
74 }
75 #[cfg(unix)]
76 if let Some(mode) = options.mode {
77 open.mode(mode);
78 }
79
80 match open.open(path).await {
81 Ok(file) => Ok(file),
82 Err(err) => Err(classify_open_error(path, err).await),
83 }
84}
85
86pub(crate) async fn classify_open_error(path: &Path, err: std::io::Error) -> FaithError {
89 let kind = if err.kind() == std::io::ErrorKind::AlreadyExists {
90 match tokio::fs::symlink_metadata(path).await {
91 Ok(meta) if meta.is_dir() => FaithErrorKind::FileWrite,
92 _ => FaithErrorKind::FileExists,
93 }
94 } else {
95 FaithErrorKind::FileWrite
96 };
97 FaithError::new(kind, err.to_string())
98}
99
100#[derive(Clone, Debug, Default)]
102pub enum Trailers {
103 #[default]
105 NotYet,
106 None,
108 Some(HeaderMap),
110}
111
112#[derive(Debug)]
118pub(crate) struct TrailersSlot(watch::Sender<Trailers>);
119
120impl Default for TrailersSlot {
121 fn default() -> Self {
122 Self(watch::channel(Trailers::NotYet).0)
123 }
124}
125
126impl TrailersSlot {
127 pub fn arrived(&self, trailers: HeaderMap) {
129 self.0.send_replace(Trailers::Some(trailers));
130 }
131
132 pub fn ended(&self) {
137 self.0.send_if_modified(|state| {
138 if matches!(state, Trailers::NotYet) {
139 *state = Trailers::None;
140 true
141 } else {
142 false
143 }
144 });
145 }
146
147 pub async fn settled(&self) -> Trailers {
149 let mut rx = self.0.subscribe();
150 match rx
155 .wait_for(|state| !matches!(state, Trailers::NotYet))
156 .await
157 {
158 Ok(state) => state.clone(),
159 Err(_) => Trailers::None,
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use std::{
167 future::Future,
168 pin::pin,
169 sync::atomic::{AtomicUsize, Ordering},
170 task::{Context, Poll, Wake, Waker},
171 };
172
173 use super::*;
174
175 struct CountingWaker(AtomicUsize);
177
178 impl CountingWaker {
179 fn wakes(&self) -> usize {
180 self.0.load(Ordering::SeqCst)
181 }
182 }
183
184 impl Wake for CountingWaker {
185 fn wake(self: Arc<Self>) {
186 self.wake_by_ref();
187 }
188
189 fn wake_by_ref(self: &Arc<Self>) {
190 self.0.fetch_add(1, Ordering::SeqCst);
191 }
192 }
193
194 #[test]
197 fn a_bodyless_response_converts_to_an_http_response() {
198 let mut headers = HeaderMap::new();
199 headers.insert("x-test", "yes".parse().expect("a valid header value"));
200
201 let response = Response {
202 claim: None,
203 disturbed: Arc::new(AtomicBool::new(false)),
204 headers,
205 integrity: None,
206 peer: Arc::new(PeerInformation {
207 address: None,
208 certificate: None,
209 }),
210 redirected: false,
211 status_code: StatusCode::NO_CONTENT,
212 timing: Arc::new(TimingSlot::new(
213 Instant::now(),
214 crate::timing::RequestTiming::default(),
215 )),
216 trailers: Arc::new(TrailersSlot::default()),
217 url: Url::parse("https://example.com/").expect("a valid url"),
218 version: Version::HTTP_2,
219 };
220
221 let http = response.into_http().expect("an undisturbed body converts");
222 assert_eq!(http.status(), StatusCode::NO_CONTENT);
223 assert_eq!(http.version(), Version::HTTP_2);
224 assert_eq!(
225 http.headers().get("x-test").map(|v| v.as_bytes()),
226 Some(&b"yes"[..])
227 );
228
229 let collected =
231 futures::executor::block_on(http_body_util::BodyExt::collect(http.into_body()))
232 .expect("an empty body collects");
233 assert!(collected.to_bytes().is_empty());
234 }
235
236 #[test]
245 fn waiting_for_trailers_parks_rather_than_spinning() {
246 let slot = TrailersSlot::default();
247 let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
248 let waker = Waker::from(counter.clone());
249 let mut cx = Context::from_waker(&waker);
250 let mut settled = pin!(slot.settled());
251
252 assert!(matches!(settled.as_mut().poll(&mut cx), Poll::Pending));
254 assert_eq!(counter.wakes(), 0, "a parked wait asks for no wake-up");
256
257 assert!(matches!(settled.as_mut().poll(&mut cx), Poll::Pending));
259 assert_eq!(counter.wakes(), 0, "polling again does not arm a wake-up");
260
261 slot.ended();
263 assert!(counter.wakes() >= 1, "the body ending wakes the waiter");
264 assert!(matches!(
265 settled.as_mut().poll(&mut cx),
266 Poll::Ready(Trailers::None)
267 ));
268 }
269
270 #[test]
272 fn trailers_already_there_resolve_on_the_first_poll() {
273 let slot = TrailersSlot::default();
274 let mut headers = HeaderMap::new();
275 headers.insert("x-checksum", "abc123".parse().unwrap());
276 slot.arrived(headers);
277
278 let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
279 let waker = Waker::from(counter.clone());
280 let mut cx = Context::from_waker(&waker);
281 let mut settled = pin!(slot.settled());
282
283 assert!(matches!(
284 settled.as_mut().poll(&mut cx),
285 Poll::Ready(Trailers::Some(_))
286 ));
287 }
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292#[non_exhaustive]
293pub struct FileProgress {
294 pub bytes_written: u64,
296 pub content_length: Option<u64>,
299}
300
301#[derive(Debug, Clone, PartialEq, Eq)]
303#[non_exhaustive]
304pub struct FileWritten {
305 pub path: PathBuf,
307 pub bytes_written: u64,
309}
310
311#[derive(Debug, Clone)]
316pub struct Response {
317 pub(crate) claim: Option<Arc<Claim>>,
321 pub(crate) disturbed: Arc<AtomicBool>,
322 pub(crate) headers: HeaderMap,
323 pub(crate) integrity: Option<String>,
324 pub(crate) peer: Arc<PeerInformation>,
325 pub(crate) redirected: bool,
326 pub(crate) status_code: StatusCode,
327 pub(crate) timing: Arc<TimingSlot>,
328 pub(crate) trailers: Arc<TrailersSlot>,
329 pub(crate) url: Url,
330 pub(crate) version: Version,
331}
332
333impl Response {
334 pub fn status(&self) -> StatusCode {
336 self.status_code
337 }
338
339 pub fn status_text(&self) -> &'static str {
344 self.status_code.canonical_reason().unwrap_or_default()
345 }
346
347 pub fn ok(&self) -> bool {
349 self.status_code.is_success()
350 }
351
352 pub fn headers(&self) -> &HeaderMap {
354 &self.headers
355 }
356
357 pub fn url(&self) -> &Url {
359 &self.url
360 }
361
362 pub fn redirected(&self) -> bool {
364 self.redirected
365 }
366
367 pub fn version(&self) -> Version {
369 self.version
370 }
371
372 pub fn peer(&self) -> &PeerInformation {
374 &self.peer
375 }
376
377 pub fn try_clone(&self) -> Result<Self, FaithError> {
382 if self.body_used() {
385 return Err(FaithErrorKind::ResponseAlreadyDisturbed.into());
386 }
387
388 let claim = match &self.claim {
391 None => None,
392 Some(claim) => Some(
393 claim
394 .duplicate()
395 .ok_or(FaithErrorKind::ResponseAlreadyDisturbed)?,
396 ),
397 };
398
399 Ok(Self {
400 claim,
401 disturbed: Arc::new(AtomicBool::new(false)),
402 ..Clone::clone(self)
403 })
404 }
405
406 pub fn body_used(&self) -> bool {
408 self.disturbed.load(Ordering::SeqCst)
409 }
410
411 pub async fn bytes(&self) -> Result<Vec<u8>, FaithError> {
417 self.check_stream_disturbed()?;
418 self.gather_contiguous().await
419 }
420
421 pub async fn text(&self) -> Result<String, FaithError> {
425 let bytes = self.bytes().await?;
426 Ok(String::from_utf8(bytes)
427 .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned()))
428 }
429
430 pub async fn json<T: DeserializeOwned>(&self) -> Result<T, FaithError> {
435 let bytes = self.bytes().await?;
436 serde_json::from_slice(&bytes)
437 .map_err(|err| FaithError::new(FaithErrorKind::JsonParse, err.to_string()))
438 }
439
440 pub fn body_stream(&self) -> Result<Option<BodyReader>, FaithError> {
449 let _ = self.check_stream_disturbed();
451
452 match &self.claim {
453 None => Ok(None),
454 Some(claim) => claim.reader().map(Some),
455 }
456 }
457
458 pub async fn discard(&self) {
466 let Some(claim) = &self.claim else {
467 return;
468 };
469 claim.give_up();
470 if claim.body().claims_left() == 0 {
471 claim.body().settled().await;
472 }
473 }
474
475 pub async fn timing(&self) -> crate::timing::RequestTiming {
478 self.timing.settled().await
479 }
480
481 pub async fn trailers(&self) -> Trailers {
486 self.trailers.settled().await
487 }
488
489 pub(crate) fn check_stream_disturbed(&self) -> Result<(), FaithError> {
490 if self.disturbed.swap(true, Ordering::SeqCst) {
491 Err(FaithErrorKind::ResponseAlreadyDisturbed.into())
492 } else {
493 Ok(())
494 }
495 }
496
497 pub(crate) async fn gather(&self) -> Result<Arc<[Bytes]>, FaithError> {
501 let Some(claim) = &self.claim else {
502 return Ok(Default::default());
503 };
504
505 let mut stream = claim.reader()?;
506 let mut chunks = Vec::new();
507 while let Some(chunk) = stream.next().await {
508 chunks.push(chunk?);
509 }
510
511 Ok(Arc::from(chunks.into_boxed_slice()))
512 }
513
514 pub(crate) async fn gather_contiguous(&self) -> Result<Vec<u8>, FaithError> {
516 let body = self.gather().await?;
517 let length = body.iter().map(|chunk| chunk.len()).sum();
518 let mut bytes = Vec::with_capacity(length);
519 for chunk in body.into_iter() {
520 bytes.extend_from_slice(chunk);
521 }
522
523 if let Some(ref integrity) = self.integrity {
524 verify_integrity(&bytes, integrity)?;
525 }
526
527 Ok(bytes)
528 }
529
530 pub async fn write_to_file(
536 &self,
537 path: impl AsRef<Path>,
538 options: &FileDestination,
539 mut on_progress: impl FnMut(FileProgress),
540 ) -> Result<FileWritten, FaithError> {
541 let path = path.as_ref();
542 let Some(claim) = self.claim.clone() else {
545 return Err(FaithErrorKind::ResponseBodyNull.into());
546 };
547
548 if self.disturbed.load(Ordering::SeqCst) || claim.is_given_up() {
552 return Err(FaithErrorKind::ResponseAlreadyDisturbed.into());
553 }
554
555 let mut checker = integrity_checker(self.integrity.as_deref())?;
558
559 let content_length = self
563 .headers
564 .get(CONTENT_LENGTH)
565 .and_then(|value| value.to_str().ok())
566 .and_then(|value| value.trim().parse::<u64>().ok());
567
568 let mut file = open_destination(path, options).await?;
571
572 self.check_stream_disturbed()?;
575
576 let stream = claim.reader()?;
577
578 let mut report = |written: u64| {
582 on_progress(FileProgress {
583 bytes_written: written,
584 content_length,
585 });
586 };
587
588 let mut written: u64 = 0;
589 let mut reported_at = Instant::now();
590 futures::pin_mut!(stream);
591 while let Some(result) = stream.next().await {
592 let chunk = result?;
593 if let Some(checker) = checker.as_mut() {
594 checker.input(&chunk);
595 }
596 file.write_all(&chunk)
597 .await
598 .map_err(|err| FaithError::new(FaithErrorKind::FileWrite, err.to_string()))?;
599 written += chunk.len() as u64;
600 if let Some(limit) = content_length {
604 if written > limit {
605 return Err(FaithErrorKind::ContentLengthOverrun.into());
606 }
607 }
608 if reported_at.elapsed() >= PROGRESS_INTERVAL {
609 reported_at = Instant::now();
610 report(written);
611 }
612 }
613
614 file.flush()
615 .await
616 .map_err(|err| FaithError::new(FaithErrorKind::FileWrite, err.to_string()))?;
617
618 report(written);
622
623 if let Some(checker) = checker {
626 finish_integrity(checker)?;
627 }
628
629 Ok(FileWritten {
630 path: std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf()),
633 bytes_written: written,
634 })
635 }
636}
637
638#[cfg(feature = "unstable-internals")]
640impl Response {
641 pub fn check_disturbed(&self) -> Result<(), FaithError> {
643 self.check_stream_disturbed()
644 }
645
646 pub fn abort_body(&self) {
650 if let Some(claim) = &self.claim {
651 claim.body().abort();
652 }
653 }
654}
655
656pub struct ResponseBody {
660 chunks: Pin<Box<dyn Stream<Item = Result<Bytes, FaithError>> + Send>>,
661}
662
663impl Debug for ResponseBody {
664 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
665 f.debug_struct("ResponseBody").finish_non_exhaustive()
666 }
667}
668
669impl http_body::Body for ResponseBody {
670 type Data = Bytes;
671 type Error = FaithError;
672
673 fn poll_frame(
674 mut self: Pin<&mut Self>,
675 cx: &mut Context<'_>,
676 ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
677 self.chunks
678 .as_mut()
679 .poll_next(cx)
680 .map(|chunk| chunk.map(|chunk| chunk.map(http_body::Frame::data)))
681 }
682}
683
684impl Response {
685 pub fn into_http(self) -> Result<http::Response<ResponseBody>, FaithError> {
690 let chunks: Pin<Box<dyn Stream<Item = Result<Bytes, FaithError>> + Send>> =
691 match self.body_stream()? {
692 Some(stream) => Box::pin(stream),
693 None => Box::pin(stream::empty()),
694 };
695
696 let mut response = http::Response::new(ResponseBody { chunks });
697 *response.status_mut() = self.status_code;
698 *response.version_mut() = self.version;
699 *response.headers_mut() = self.headers.clone();
700 Ok(response)
701 }
702}
703
704impl TryFrom<Response> for http::Response<ResponseBody> {
705 type Error = FaithError;
706
707 fn try_from(response: Response) -> Result<Self, Self::Error> {
708 response.into_http()
709 }
710}