This is an automated email from the ASF dual-hosted git repository.
Gargi-jais11 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 182aaa77994 HDDS-15515. Support object Content-Type end-to-end in S3
Gateway (#10472).
182aaa77994 is described below
commit 182aaa77994e77a20c2b801f8fda4d453346bf2a
Author: KUAN-HAO HUANG <[email protected]>
AuthorDate: Wed Jun 24 17:54:23 2026 +0800
HDDS-15515. Support object Content-Type end-to-end in S3 Gateway (#10472).
---
.../hadoop/ozone/s3/endpoint/EndpointBase.java | 59 +++++++++++++++-------
.../hadoop/ozone/s3/endpoint/ObjectEndpoint.java | 35 ++++++++++++-
.../s3/endpoint/TestMultipartUploadComplete.java | 18 +++++++
.../hadoop/ozone/s3/endpoint/TestObjectGet.java | 53 ++++++++++++++++++-
.../hadoop/ozone/s3/endpoint/TestObjectHead.java | 35 +++++++++++++
.../hadoop/ozone/s3/endpoint/TestObjectPut.java | 37 ++++++++++++++
6 files changed, 216 insertions(+), 21 deletions(-)
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
index 224a823e14d..052dd34faa5 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
@@ -48,6 +48,7 @@
import static org.apache.hadoop.ozone.s3.util.S3Utils.validateSignatureHeader;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@@ -118,6 +119,22 @@
public abstract class EndpointBase {
protected static final String ETAG_CUSTOM = "etag-custom";
+ protected static final String CONTENT_TYPE_CUSTOM = "content-type-custom";
+
+ // System metadata key -> custom key. A user x-amz-meta-{etag,content-type}
+ // collides with the system ETag / Content-Type stored under the same key, so
+ // it is remapped on write and rebuilt on read; the system value is returned
+ // via its own ETag / Content-Type response header.
+ private static final Map<String, String> RESERVED_METADATA_KEYS =
+ ImmutableMap.of(
+ ETAG, ETAG_CUSTOM,
+ HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_CUSTOM);
+
+ // Custom key -> lower-cased header, to rebuild remapped user metadata on
read.
+ private static final Map<String, String> REBUILT_RESERVED_KEYS =
+ RESERVED_METADATA_KEYS.entrySet().stream()
+ .collect(ImmutableMap.toImmutableMap(
+ Map.Entry::getValue, e -> e.getKey().toLowerCase()));
private static final ThreadLocal<MessageDigest> MD5_PROVIDER;
private static final ThreadLocal<MessageDigest> SHA_256_PROVIDER;
@@ -319,35 +336,41 @@ protected Map<String, String>
getCustomMetadataFromHeaders(
}
}
- // If the request contains a custom metadata header "x-amz-meta-ETag",
- // replace the metadata key to "etag-custom" to prevent key metadata
collision with
- // the ETag calculated by hashing the object when storing the key in OM
table.
- // The custom ETag metadata header will be rebuilt during the headObject
operation.
- if (customMetadata.containsKey(HttpHeaders.ETAG)
- || customMetadata.containsKey(HttpHeaders.ETAG.toLowerCase())) {
- String customETag = customMetadata.get(HttpHeaders.ETAG) != null ?
- customMetadata.get(HttpHeaders.ETAG) :
customMetadata.get(HttpHeaders.ETAG.toLowerCase());
- customMetadata.remove(HttpHeaders.ETAG);
- customMetadata.remove(HttpHeaders.ETAG.toLowerCase());
- customMetadata.put(ETAG_CUSTOM, customETag);
- }
+ // Remap user values that collide with a reserved system key.
+ RESERVED_METADATA_KEYS.forEach((headerName, customKey) ->
+ remapReservedMetadataKey(customMetadata, headerName, customKey));
return customMetadata;
}
+ /**
+ * Move a user value under {@code headerName} (canonical or lower-case) to
+ * {@code customKey} so it does not collide with the system value.
+ */
+ private static void remapReservedMetadataKey(Map<String, String> metadata,
+ String headerName, String customKey) {
+ String lowerName = headerName.toLowerCase();
+ if (metadata.containsKey(headerName) || metadata.containsKey(lowerName)) {
+ String value = metadata.get(headerName) != null
+ ? metadata.get(headerName) : metadata.get(lowerName);
+ metadata.remove(headerName);
+ metadata.remove(lowerName);
+ metadata.put(customKey, value);
+ }
+ }
+
protected void addCustomMetadataHeaders(
Response.ResponseBuilder responseBuilder, OzoneKey key) {
Map<String, String> metadata = key.getMetadata();
for (Map.Entry<String, String> entry : metadata.entrySet()) {
- if (entry.getKey().equals(ETAG)) {
- continue;
- }
String metadataKey = entry.getKey();
- if (metadataKey.equals(ETAG_CUSTOM)) {
- // Rebuild the ETag custom metadata header
- metadataKey = ETAG.toLowerCase();
+ // System ETag / Content-Type are returned via their own response header.
+ if (RESERVED_METADATA_KEYS.containsKey(metadataKey)) {
+ continue;
}
+ // Rebuild a remapped user value (e.g. content-type-custom ->
content-type).
+ metadataKey = REBUILT_RESERVED_KEYS.getOrDefault(metadataKey,
metadataKey);
responseBuilder
.header(CUSTOM_METADATA_HEADER_PREFIX + metadataKey,
entry.getValue());
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
index acf358e3ce8..72f0c672bd3 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
@@ -119,6 +119,8 @@ public class ObjectEndpoint extends ObjectOperationHandler {
private static final String BUCKET = "bucket";
private static final String PATH = "path";
+ // Default Content-Type for objects stored without one, matching S3.
+ private static final String DEFAULT_CONTENT_TYPE = "binary/octet-stream";
private static final Logger LOG =
LoggerFactory.getLogger(ObjectEndpoint.class);
@@ -131,7 +133,6 @@ public class ObjectEndpoint extends ObjectOperationHandler {
public ObjectEndpoint() {
overrideQueryParameter = ImmutableMap.<String, String>builder()
- .put(HttpHeaders.CONTENT_TYPE, "response-content-type")
.put(HttpHeaders.CONTENT_LANGUAGE, "response-content-language")
.put(HttpHeaders.EXPIRES, "response-expires")
.put(HttpHeaders.CACHE_CONTROL, "response-cache-control")
@@ -267,6 +268,7 @@ Response handlePutRequest(ObjectRequestContext context,
String keyPath, InputStr
Map<String, String> customMetadata =
getCustomMetadataFromHeaders(getHeaders().getRequestHeaders());
+ putContentType(customMetadata);
Map<String, String> tags = getTaggingFromHeaders(getHeaders());
long putLength;
@@ -461,6 +463,14 @@ Response handleGetRequest(ObjectRequestContext context,
String keyPath)
MultivaluedMap<String, String> queryParams =
getContext().getUriInfo().getQueryParameters();
+ // Content-Type comes from the stored object, not the request header;
+ // response-content-type still overrides it.
+ String contentType = queryParams.getFirst("response-content-type");
+ if (contentType == null) {
+ contentType = contentTypeOf(keyDetails);
+ }
+ responseBuilder.header(HttpHeaders.CONTENT_TYPE, contentType);
+
for (Map.Entry<String, String> entry :
overrideQueryParameter.entrySet()) {
String headerValue = getHeaders().getHeaderString(entry.getKey());
String queryValue = queryParams.getFirst(entry.getValue());
@@ -497,6 +507,24 @@ static void addLastModifiedDate(
RFC1123Util.FORMAT.format(lastModificationTime));
}
+ /**
+ * Store the request's Content-Type (preserved by {@link HeaderPreprocessor}
+ * as {@code X-Ozone-Original-Content-Type}) in the key metadata.
+ */
+ private void putContentType(Map<String, String> metadata) {
+ String contentType =
+ getHeaders().getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE);
+ if (contentType != null) {
+ metadata.put(HttpHeaders.CONTENT_TYPE, contentType);
+ }
+ }
+
+ /** Returns the object's stored Content-Type, or the default if absent. */
+ private static String contentTypeOf(OzoneKey key) {
+ String contentType = key.getMetadata().get(HttpHeaders.CONTENT_TYPE);
+ return contentType != null ? contentType : DEFAULT_CONTENT_TYPE;
+ }
+
static void addTagCountIfAny(
ResponseBuilder responseBuilder, OzoneKey key) {
// See x-amz-tagging-count in
https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
@@ -572,7 +600,7 @@ public Response head(
ResponseBuilder response = Response.ok().status(HttpStatus.SC_OK)
.header(HttpHeaders.CONTENT_LENGTH, key.getDataSize())
- .header(HttpHeaders.CONTENT_TYPE, "binary/octet-stream")
+ .header(HttpHeaders.CONTENT_TYPE, contentTypeOf(key))
.header(STORAGE_CLASS_HEADER, s3StorageType.toString());
addEntityTagHeader(response, key);
@@ -675,6 +703,7 @@ public Response initializeMultipartUpload(
Map<String, String> customMetadata =
getCustomMetadataFromHeaders(getHeaders().getRequestHeaders());
+ putContentType(customMetadata);
Map<String, String> tags = getTaggingFromHeaders(getHeaders());
@@ -1089,6 +1118,8 @@ private CopyObjectResponse copyObject(OzoneVolume volume,
} else if (metadataCopyDirective.equals(CopyDirective.REPLACE.name())) {
// Replace the metadata with the metadata form the request headers
customMetadata =
getCustomMetadataFromHeaders(getHeaders().getRequestHeaders());
+ // REPLACE: Content-Type comes from the request, not the source.
+ putContentType(customMetadata);
} else {
OS3Exception ex = newError(INVALID_ARGUMENT, metadataCopyDirective);
ex.setErrorMessage("An error occurred (InvalidArgument) " +
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
index 53500bbd2e5..66baf871b5a 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
@@ -41,6 +41,7 @@
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.HeaderPreprocessor;
import org.apache.hadoop.ozone.s3.endpoint.CompleteMultipartUploadRequest.Part;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
@@ -122,6 +123,23 @@ public void testMultipartWithCustomMetadata() throws
Exception {
assertEquals("custom-value2",
headResponse.getHeaderString(CUSTOM_METADATA_HEADER_PREFIX + "custom-key2"));
}
+ @Test
+ public void testMultipartStoresContentType() throws Exception {
+ String key = UUID.randomUUID().toString();
+
+ // Content-Type set on CreateMultipartUpload is preserved as
+ // ORIGINAL_CONTENT_TYPE and must survive to the completed object.
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn("video/webm");
+
+ String uploadID = initiateMultipartUpload(key);
+ Part part1 = uploadPart(rest, OzoneConsts.S3_BUCKET, key, 1, uploadID,
"Multipart Upload 1");
+ completeMultipartUpload(rest, OzoneConsts.S3_BUCKET, key, uploadID,
singletonList(part1));
+
+ assertEquals("video/webm", rest.head(OzoneConsts.S3_BUCKET, key)
+ .getHeaderString(HttpHeaders.CONTENT_TYPE));
+ }
+
@Test
public void testMultipartInvalidPartOrderError() throws Exception {
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
index 8f310b74062..54d28c08b8b 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
@@ -52,6 +52,7 @@
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientStub;
import org.apache.hadoop.ozone.client.OzoneClientTestUtils;
+import org.apache.hadoop.ozone.s3.HeaderPreprocessor;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.util.RFC1123Util;
import org.junit.jupiter.api.BeforeEach;
@@ -216,7 +217,8 @@ public void inheritRequestHeader() throws IOException,
OS3Exception {
Response response = get(rest, BUCKET_NAME, KEY_NAME);
- assertEquals(CONTENT_TYPE1,
+ // Content-Type is not inherited from the request; key1 has none stored.
+ assertEquals("binary/octet-stream",
response.getHeaderString("Content-Type"));
assertEquals(CONTENT_LANGUAGE1,
response.getHeaderString("Content-Language"));
@@ -298,6 +300,55 @@ public void getStatusCode() throws IOException,
OS3Exception {
assertNull(response.getHeaderString(TAG_COUNT_HEADER));
}
+ @Test
+ public void getAndHeadReturnStoredContentType()
+ throws IOException, OS3Exception {
+ // Store a Content-Type, then read with no request Content-Type.
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn(CONTENT_TYPE1);
+ assertSucceeds(() -> put(rest, BUCKET_NAME, "typed-key", CONTENT));
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn(null);
+
+ // GET and HEAD return the stored Content-Type.
+ assertEquals(CONTENT_TYPE1,
+ get(rest, BUCKET_NAME, "typed-key").getHeaderString("Content-Type"));
+ assertEquals(CONTENT_TYPE1,
+ rest.head(BUCKET_NAME, "typed-key").getHeaderString("Content-Type"));
+
+ // An object stored without a Content-Type falls back to the default.
+ assertEquals("binary/octet-stream",
+ get(rest, BUCKET_NAME, KEY_NAME).getHeaderString("Content-Type"));
+ assertEquals("binary/octet-stream",
+ rest.head(BUCKET_NAME, KEY_NAME).getHeaderString("Content-Type"));
+ }
+
+ @Test
+ public void getIgnoresRequestContentTypeUsesStored()
+ throws IOException, OS3Exception {
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn(CONTENT_TYPE1);
+ assertSucceeds(() -> put(rest, BUCKET_NAME, "typed-key", CONTENT));
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn(null);
+
+ // A request Content-Type is ignored; the stored value is returned.
+ doReturn(CONTENT_TYPE2).when(headers).getHeaderString("Content-Type");
+ assertEquals(CONTENT_TYPE1,
+ get(rest, BUCKET_NAME, "typed-key").getHeaderString("Content-Type"));
+
+ // No stored Content-Type: request still ignored, default returned.
+ assertEquals("binary/octet-stream",
+ get(rest, BUCKET_NAME, KEY_NAME).getHeaderString("Content-Type"));
+
+ // response-content-type still overrides.
+ MultivaluedMap<String, String> queryParameter =
+ rest.getContext().getUriInfo().getQueryParameters();
+ queryParameter.putSingle("response-content-type", CONTENT_TYPE2);
+ assertEquals(CONTENT_TYPE2,
+ get(rest, BUCKET_NAME, "typed-key").getHeaderString("Content-Type"));
+ }
+
private void setDefaultHeader() {
doReturn(CONTENT_TYPE1)
.when(headers).getHeaderString("Content-Type");
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectHead.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectHead.java
index f6795ba8f56..158d856f7d4 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectHead.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectHead.java
@@ -24,6 +24,7 @@
import static
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertSucceeds;
import static org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.put;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.PRECOND_FAILED;
+import static
org.apache.hadoop.ozone.s3.util.S3Consts.CUSTOM_METADATA_HEADER_PREFIX;
import static org.apache.hadoop.ozone.s3.util.S3Consts.IF_MATCH_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.IF_NONE_MATCH_HEADER;
import static
org.apache.hadoop.ozone.s3.util.S3Consts.IF_UNMODIFIED_SINCE_HEADER;
@@ -42,6 +43,8 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -49,6 +52,7 @@
import org.apache.hadoop.ozone.client.OzoneBucket;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.HeaderPreprocessor;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.util.RFC1123Util;
import org.apache.http.HttpStatus;
@@ -231,6 +235,37 @@ public void testHeadObjectIncludesTagCount()
assertEquals("2", response.getHeaderString(TAG_COUNT_HEADER));
}
+ @Test
+ public void testHeadSeparatesUserContentTypeMetadataFromObjectContentType()
+ throws Exception {
+ String keyName = "typed-with-user-meta";
+ String objectContentType = "image/jpeg";
+ String userContentType = "user/custom-type";
+
+ // PUT with both the object's Content-Type and a colliding user
+ // x-amz-meta-content-type.
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn(objectContentType);
+ MultivaluedMap<String, String> requestHeaders = new MultivaluedHashMap<>();
+ requestHeaders.putSingle(
+ CUSTOM_METADATA_HEADER_PREFIX + "content-type", userContentType);
+ when(headers.getRequestHeaders()).thenReturn(requestHeaders);
+ assertSucceeds(() -> put(keyEndpoint, bucketName, keyName,
"head-content"));
+
+ // The user value is remapped, so the object's Content-Type is preserved.
+ assertEquals(objectContentType,
+ bucket.getKey(keyName).getMetadata().get(HttpHeaders.CONTENT_TYPE));
+
+ // HEAD returns the object Content-Type as the standard header and the user
+ // value as x-amz-meta-content-type.
+ Response response = keyEndpoint.head(bucketName, keyName);
+ assertEquals(HttpStatus.SC_OK, response.getStatus());
+ assertEquals(objectContentType,
+ response.getHeaderString(HttpHeaders.CONTENT_TYPE));
+ assertEquals(userContentType,
+ response.getHeaderString(CUSTOM_METADATA_HEADER_PREFIX +
"content-type"));
+ }
+
private byte[] createKey(String keyPath) throws IOException {
byte[] bytes =
RandomStringUtils.secure().nextAlphanumeric(32).getBytes(UTF_8);
try (OutputStream out = bucket.createKey(keyPath, bytes.length)) {
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
index c9313207c01..2af6cbcfdcc 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
@@ -83,6 +83,7 @@
import org.apache.hadoop.ozone.client.OzoneKeyDetails;
import org.apache.hadoop.ozone.client.OzoneVolume;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.s3.HeaderPreprocessor;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
import org.apache.hadoop.ozone.s3.util.S3Consts;
@@ -381,6 +382,42 @@ void testCopyObject() throws Exception {
() -> put(objectEndpoint, "nonexistent", KEY_NAME, CONTENT));
}
+ @Test
+ void testContentTypeStoredAndCopied() throws Exception {
+ // PUT with an explicit Content-Type (preserved by HeaderPreprocessor).
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn("audio/mpeg");
+
+ assertSucceeds(() -> putObject(CONTENT));
+ OzoneKeyDetails source = bucket.getKey(KEY_NAME);
+ assertEquals("audio/mpeg",
+ source.getMetadata().get(HttpHeaders.CONTENT_TYPE));
+
+ // COPY directive: destination keeps the source Content-Type, ignoring the
+ // Content-Type on the copy request.
+ when(headers.getHeaderString(CUSTOM_METADATA_COPY_DIRECTIVE_HEADER))
+ .thenReturn("COPY");
+ when(headers.getHeaderString(COPY_SOURCE_HEADER))
+ .thenReturn(BUCKET_NAME + "/" + urlEncode(KEY_NAME));
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn("text/plain");
+
+ assertSucceeds(() -> put(objectEndpoint, DEST_BUCKET_NAME, DEST_KEY,
CONTENT));
+ assertEquals("audio/mpeg",
+
destBucket.getKey(DEST_KEY).getMetadata().get(HttpHeaders.CONTENT_TYPE));
+
+ // REPLACE directive: destination takes the Content-Type from the request.
+ when(headers.getHeaderString(CUSTOM_METADATA_COPY_DIRECTIVE_HEADER))
+ .thenReturn("REPLACE");
+ when(headers.getRequestHeaders()).thenReturn(new MultivaluedHashMap<>());
+ when(headers.getHeaderString(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE))
+ .thenReturn("application/json");
+
+ assertSucceeds(() -> put(objectEndpoint, DEST_BUCKET_NAME, DEST_KEY,
CONTENT));
+ assertEquals("application/json",
+
destBucket.getKey(DEST_KEY).getMetadata().get(HttpHeaders.CONTENT_TYPE));
+ }
+
@Test
public void testCopyObjectMessageDigestResetDuringException() throws
Exception {
assertSucceeds(() -> putObject(CONTENT));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]