ehttpd_range/
responseext.rs

1//! An extension trait for HTTP requests to work with range requests
2
3use crate::rangeext::RangeExt;
4use ehttpd::{
5    bytes::{Data, DataSliceExt, Source},
6    error,
7    error::Error,
8    http::{Response, ResponseExt},
9};
10use std::{
11    fs::File,
12    io::{BufReader, Read, Seek, SeekFrom},
13    ops::{Range, RangeBounds, RangeInclusive},
14};
15
16/// An extension trait for HTTP responses to work with range requests
17pub trait ResponseRangeExt
18where
19    Self: Sized,
20{
21    /// Creates a new `206 Partial Content` HTTP response
22    fn new_206_partial_content() -> Self;
23
24    /// Sets the `Accept-Ranges` to `bytes`
25    fn set_accept_ranges_bytes(&mut self);
26    /// Sets the `Accept-Ranges` to `none`
27    fn set_accept_ranges_none(&mut self);
28
29    /// Sets the `Content-Range` header
30    fn set_content_range<T>(&mut self, range: T, total: u64) -> Result<(), Error>
31    where
32        T: RangeBounds<u64>;
33
34    /// Sets the body for a `Partial Range` response
35    ///
36    /// # Note
37    /// This function also sets the `Content-Length` and the `Content-Range` headers. Furthermore, it raises an error if
38    /// `self.status` is not `206`
39    fn set_body_data_range<T, R>(&mut self, data: T, range: R) -> Result<(), Error>
40    where
41        T: Into<Data>,
42        R: RangeBounds<usize>;
43    /// Sets the body for a `Partial Range` response
44    ///
45    /// # Note
46    /// This function also sets the `Content-Length` and the `Content-Range` headers. Furthermore, it raises an error if
47    /// `self.status` is not `206`
48    fn set_body_file_range<T, R>(&mut self, file: T, range: R) -> Result<(), Error>
49    where
50        T: Into<File>,
51        R: RangeBounds<u64>;
52}
53impl<const HEADER_SIZE_MAX: usize> ResponseRangeExt for Response<HEADER_SIZE_MAX> {
54    fn new_206_partial_content() -> Self {
55        Self::new_status_reason(206, "Partial Content")
56    }
57
58    fn set_accept_ranges_bytes(&mut self) {
59        self.set_field("Accept-Ranges", "bytes")
60    }
61    fn set_accept_ranges_none(&mut self) {
62        self.set_field("Accept-Ranges", "none")
63    }
64
65    fn set_content_range<T>(&mut self, range: T, total: u64) -> Result<(), Error>
66    where
67        T: RangeBounds<u64>,
68    {
69        // Compute the bounds
70        let range = RangeInclusive::from_range_bounds(range, 0, total)
71            .ok_or_else(|| error!("Range would exceed total limit"))?;
72        let range_string = format!("bytes {}-{}/{total}", range.start(), range.end());
73
74        // Set the range
75        self.set_field("Content-Range", range_string);
76        Ok(())
77    }
78
79    fn set_body_data_range<T, R>(&mut self, data: T, range: R) -> Result<(), Error>
80    where
81        T: Into<Data>,
82        R: RangeBounds<usize>,
83    {
84        // Ensure that we are a 206
85        if !self.status.eq(b"206") {
86            return Err(error!("Response is not a 206 response"));
87        }
88
89        // Prepare data and range
90        let data: Data = data.into();
91        let Range { start, end } =
92            Range::from_range_bounds(range, 0, data.len()).ok_or_else(|| error!("Range would exceed data size"))?;
93        let subdata = data.subcopy(start..end).expect("range would exceed data size");
94
95        // Set content-range header and body data
96        self.set_content_range(start as u64..end as u64, data.len() as u64)?;
97        self.set_body_data(subdata);
98        Ok(())
99    }
100    fn set_body_file_range<T, R>(&mut self, file: T, range: R) -> Result<(), Error>
101    where
102        T: Into<File>,
103        R: RangeBounds<u64>,
104    {
105        // Ensure that we are a 206
106        if !self.status.eq(b"206") {
107            return Err(error!("Response is not a 206 response"));
108        }
109
110        // Open the file and get the file size
111        let mut file: File = file.into();
112        let file_size = file.metadata()?.len();
113        let Range { start, end } =
114            Range::from_range_bounds(range, 0, file_size).ok_or_else(|| error!("Range would exceed file size"))?;
115
116        // Get the length and virtually truncate the file
117        let len = end.saturating_sub(start);
118        file.seek(SeekFrom::Start(start))?;
119        let file = file.take(end.saturating_sub(start));
120
121        // Set content-range and content-length header
122        self.set_content_range(start..end, file_size)?;
123        self.set_content_length(len);
124
125        // Buffer the file and set the raw body
126        let file = BufReader::new(file);
127        self.body = Source::new(file);
128        Ok(())
129    }
130}