mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 22:33:22 +00:00
fix: stabilize head metadata responses and heal tests (#1732)
Signed-off-by: LoganZ2 <103290230+LoganZ2@users.noreply.github.com> Co-authored-by: houseme <housemecn@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -426,8 +426,19 @@ impl Drop for GetObjectGuard {
|
||||
/// This ensures accurate tracking even in error/panic scenarios, as Drop
|
||||
/// is called during stack unwinding (unless explicitly forgotten).
|
||||
fn drop(&mut self) {
|
||||
// Decrement concurrent request counter
|
||||
ACTIVE_GET_REQUESTS.fetch_sub(1, Ordering::Relaxed);
|
||||
// Decrement concurrent request counter without underflow.
|
||||
// If the counter is already 0, `checked_sub` returns None and `fetch_update`
|
||||
// yields Err(previous). We treat that as a lifecycle bug (e.g., unmatched drop)
|
||||
// and surface it in debug builds instead of silently discarding it.
|
||||
if let Err(previous) =
|
||||
ACTIVE_GET_REQUESTS.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| current.checked_sub(1))
|
||||
{
|
||||
debug_assert_eq!(
|
||||
previous, 0,
|
||||
"ACTIVE_GET_REQUESTS underflow attempt in GetObjectGuard::drop; previous value = {}",
|
||||
previous
|
||||
);
|
||||
}
|
||||
|
||||
// Record Prometheus metrics for monitoring and alerting
|
||||
#[cfg(feature = "metrics")]
|
||||
|
||||
@@ -3428,6 +3428,30 @@ impl S3 for FS {
|
||||
// takes precedence for these read operations.
|
||||
let mut response = wrap_response_with_cors(&bucket, &req.method, &req.headers, output).await;
|
||||
|
||||
if let Some(content_disposition) = metadata_map.get("content-disposition") {
|
||||
// Only set Content-Disposition from metadata if it has not already been set
|
||||
if response.headers.get(http::header::CONTENT_DISPOSITION).is_none() {
|
||||
match HeaderValue::from_str(content_disposition) {
|
||||
Ok(header_value) => {
|
||||
response.headers.insert(http::header::CONTENT_DISPOSITION, header_value);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to parse content-disposition metadata value `{}` into HeaderValue: {}",
|
||||
content_disposition, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !response.headers.contains_key(http::header::CONTENT_TYPE)
|
||||
&& let Some(content_type) = metadata_map.get("content-type")
|
||||
&& let Ok(header_value) = HeaderValue::from_str(content_type)
|
||||
{
|
||||
response.headers.insert(http::header::CONTENT_TYPE, header_value);
|
||||
}
|
||||
|
||||
// Add x-amz-tagging-count header if object has tags
|
||||
// Per S3 API spec, this header should be present in HEAD object response when tags exist
|
||||
if tag_count > 0 {
|
||||
|
||||
@@ -390,12 +390,16 @@ pub fn extract_metadata_from_mime_with_object_name(
|
||||
}
|
||||
|
||||
pub(crate) fn filter_object_metadata(metadata: &HashMap<String, String>) -> Option<HashMap<String, String>> {
|
||||
// Standard HTTP headers that should NOT be returned in the Metadata field
|
||||
// These are returned as separate response headers, not user metadata
|
||||
// HTTP headers that should NOT be returned in the Metadata field.
|
||||
// These headers are returned as separate response headers, not user metadata.
|
||||
//
|
||||
// Note: content-type and content-disposition are intentionally NOT excluded here.
|
||||
// They remain in the filtered metadata so they can continue to be exposed via
|
||||
// x-amz-meta-* style user metadata for backward compatibility, while the HEAD
|
||||
// implementation also mirrors their values into the standard Content-Type and
|
||||
// Content-Disposition response headers where appropriate.
|
||||
const EXCLUDED_HEADERS: &[&str] = &[
|
||||
"content-type",
|
||||
"content-encoding",
|
||||
"content-disposition",
|
||||
"content-language",
|
||||
"cache-control",
|
||||
"expires",
|
||||
@@ -432,7 +436,8 @@ pub(crate) fn filter_object_metadata(metadata: &HashMap<String, String>) -> Opti
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip standard HTTP headers (they are returned as separate headers, not metadata)
|
||||
// Skip excluded HTTP headers (they are returned as separate headers, not metadata)
|
||||
// Note: content-type and content-disposition are NOT excluded and will be treated as user metadata
|
||||
if EXCLUDED_HEADERS.contains(&lower_key.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user