Skip to main content

ehttpd_range/
rangeresponse.rs

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