Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Space-separated key-value pairs to override S3 request configuration per page mi
- `ma=<num>` Maximum attempts
- `ib=<ms>` Initial backoff duration
- `mb=<ms>` Maximum backoff duration
- `fps=<bool>` Force path-style addressing

#### Example Request

Expand Down
2 changes: 2 additions & 0 deletions src/object_store/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct RequestConfig {
pub max_attempts: Option<u32>,
pub initial_backoff: Option<Duration>,
pub max_backoff: Option<Duration>,
pub force_path_style: Option<bool>,
}

impl RequestConfig {
Expand All @@ -23,6 +24,7 @@ impl RequestConfig {
&& self.max_attempts.is_none()
&& self.initial_backoff.is_none()
&& self.max_backoff.is_none()
&& self.force_path_style.is_none()
}

fn has_timeout_overrides(&self) -> bool {
Expand Down
3 changes: 3 additions & 0 deletions src/object_store/downloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ impl Downloader {
{
config_override = config_override.retry_config(retry_config);
}
if let Some(force_path_style) = req_config.force_path_style {
config_override = config_override.force_path_style(force_path_style);
}

request
.customize()
Expand Down
46 changes: 35 additions & 11 deletions src/service/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ where

let mut config = Self::default();

let parse_duration = |v: &str| -> Result<Duration, Self::Rejection> {
v.parse::<u64>().map(Duration::from_millis).map_err(|_| {
(
StatusCode::BAD_REQUEST,
"Invalid duration value in C0-Config header",
)
})
};

for pair in header_str.split_whitespace() {
let Some((key, value)) = pair.split_once('=') else {
return Err((
Expand All @@ -165,15 +174,6 @@ where
));
};

let parse_duration = |v: &str| -> Result<Duration, Self::Rejection> {
v.parse::<u64>().map(Duration::from_millis).map_err(|_| {
(
StatusCode::BAD_REQUEST,
"Invalid duration value in C0-Config header",
)
})
};

match key {
"ct" => config.connect_timeout = Some(parse_duration(value)?),
"rt" => config.read_timeout = Some(parse_duration(value)?),
Expand All @@ -183,12 +183,20 @@ where
config.max_attempts = Some(value.parse().map_err(|_| {
(
StatusCode::BAD_REQUEST,
"Invalid max_attempts value in C0-Config hheader",
"Invalid value for ma in C0-Config header",
)
})?);
}
"ib" => config.initial_backoff = Some(parse_duration(value)?),
"mb" => config.max_backoff = Some(parse_duration(value)?),
"fps" => {
config.force_path_style = Some(value.parse().map_err(|_| {
(
StatusCode::BAD_REQUEST,
"Invalid value for fps in C0-Config header",
)
})?);
}
_ => {} // Ignore unrecognized keys
}
}
Expand Down Expand Up @@ -542,6 +550,12 @@ mod tests {
assert_eq!(config.max_attempts, Some(3));
}

#[tokio::test]
async fn test_c0_config_force_path_style() {
let config = parse_c0_config("fps=true").await.unwrap();
assert_eq!(config.force_path_style, Some(true));
}

#[tokio::test]
async fn test_c0_config_mixed_settings() {
let config = parse_c0_config("ct=1000 ma=5 ib=10 oat=1500")
Expand Down Expand Up @@ -591,7 +605,17 @@ mod tests {
assert!(result.is_err());
assert_eq!(
result.unwrap_err().1,
"Invalid max_attempts value in C0-Config hheader"
"Invalid value for ma in C0-Config header"
);
}

#[tokio::test]
async fn test_c0_config_invalid_force_path_style() {
let result = parse_c0_config("fps=1").await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().1,
"Invalid value for fps in C0-Config header"
);
}

Expand Down