Skip to main content

objectstore_server/extractors/
byte_range.rs

1//! Axum extractor for range requests.
2
3use axum::extract::FromRequestParts;
4use http::request::Parts;
5use objectstore_types::range::{ByteRange, RangeError};
6
7use crate::{endpoints::common::ApiError, state::ServiceState};
8
9/// Extractor that parses the `Range` request header into an optional [`ByteRange`].
10#[derive(Debug, Clone)]
11pub struct OptionalByteRange(pub Option<ByteRange>);
12
13impl FromRequestParts<ServiceState> for OptionalByteRange {
14    type Rejection = ApiError;
15
16    async fn from_request_parts(
17        parts: &mut Parts,
18        _state: &ServiceState,
19    ) -> Result<Self, Self::Rejection> {
20        let headers = &parts.headers;
21        let Some(range) = headers.get(http::header::RANGE) else {
22            return Ok(Self(None));
23        };
24        let range = range
25            .to_str()
26            .map_err(|_| ApiError::Client("invalid Range header".into()))?;
27
28        match range.parse::<ByteRange>() {
29            Ok(range) => Ok(Self(Some(range))),
30            // Per RFC 9110:
31            // > A server that supports range requests MAY ignore or reject a Range header
32            //   field that contains an invalid ranges-specifier [...]
33            //
34            // If the client wants multiple ranges, fall back to returning the whole object.
35            // We might support multiple ranges in the future, so log a warning to let us know
36            // clients are trying to do this.
37            Err(RangeError::MultiRange) => {
38                objectstore_log::warn!(
39                    "received range request with multiple range specifiers, ignoring"
40                );
41                Ok(Self(None))
42            }
43            // The client requested an invalid unit or sent a malformed header.
44            // We could fall back, but better fail hard and let them know they sent something
45            // invalid.
46            Err(err) => Err(ApiError::Client(format!("invalid Range header: {err}"))),
47        }
48    }
49}