Skip to content

Commit 62ce87f

Browse files
authored
Add checksum support in request (#911)
* Add checksum support in request * Address comments
1 parent 42b0ca7 commit 62ce87f

File tree

5 files changed

+126
-2
lines changed

5 files changed

+126
-2
lines changed

aws/client/aws-client-restjson/src/it/java/software/amazon/smithy/java/client/aws/restjson/RestJson1ProtocolTests.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ public class RestJson1ProtocolTests {
3535
@HttpClientRequestTests
3636
@ProtocolTestFilter(
3737
skipTests = {
38-
// TODO: support checksums in requests
39-
"RestJsonHttpChecksumRequired",
4038
// TODO: These tests require a payload even when the httpPayload member is null. Should it?
4139
"RestJsonHttpWithHeadersButNoPayload",
4240
"RestJsonHttpWithEmptyStructurePayload",

client/client-core/src/test/java/software/amazon/smithy/java/client/core/ClientTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import software.amazon.smithy.java.client.http.mock.MockPlugin;
3333
import software.amazon.smithy.java.client.http.mock.MockQueue;
3434
import software.amazon.smithy.java.client.http.plugins.ApplyHttpRetryInfoPlugin;
35+
import software.amazon.smithy.java.client.http.plugins.HttpChecksumPlugin;
3536
import software.amazon.smithy.java.client.http.plugins.UserAgentPlugin;
3637
import software.amazon.smithy.java.core.serde.document.Document;
3738
import software.amazon.smithy.java.dynamicclient.DynamicClient;
@@ -95,6 +96,7 @@ public void tracksPlugins() throws URISyntaxException {
9596
// And HttpMessageExchange applies the UserAgentPlugin and ApplyHttpRetryInfoPlugin.
9697
UserAgentPlugin.class,
9798
ApplyHttpRetryInfoPlugin.class,
99+
HttpChecksumPlugin.class,
98100
// User plugins are applied last.
99101
FooPlugin.class));
100102
}

client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpMessageExchange.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import software.amazon.smithy.java.client.core.ClientConfig;
99
import software.amazon.smithy.java.client.core.MessageExchange;
1010
import software.amazon.smithy.java.client.http.plugins.ApplyHttpRetryInfoPlugin;
11+
import software.amazon.smithy.java.client.http.plugins.HttpChecksumPlugin;
1112
import software.amazon.smithy.java.client.http.plugins.UserAgentPlugin;
1213
import software.amazon.smithy.java.http.api.HttpRequest;
1314
import software.amazon.smithy.java.http.api.HttpResponse;
@@ -19,6 +20,7 @@
1920
* <ul>
2021
* <li>{@link UserAgentPlugin}</li>
2122
* <li>{@link ApplyHttpRetryInfoPlugin}</li>
23+
* <li>{@link HttpChecksumPlugin}</li>
2224
* </ul>
2325
*/
2426
public final class HttpMessageExchange implements MessageExchange<HttpRequest, HttpResponse> {
@@ -33,5 +35,6 @@ private HttpMessageExchange() {}
3335
public void configureClient(ClientConfig.Builder config) {
3436
config.applyPlugin(new UserAgentPlugin());
3537
config.applyPlugin(new ApplyHttpRetryInfoPlugin());
38+
config.applyPlugin(new HttpChecksumPlugin());
3639
}
3740
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.http.plugins;
7+
8+
import java.security.MessageDigest;
9+
import java.security.NoSuchAlgorithmException;
10+
import java.util.Base64;
11+
import software.amazon.smithy.java.client.core.ClientConfig;
12+
import software.amazon.smithy.java.client.core.ClientPlugin;
13+
import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor;
14+
import software.amazon.smithy.java.client.core.interceptors.RequestHook;
15+
import software.amazon.smithy.java.core.schema.TraitKey;
16+
import software.amazon.smithy.java.http.api.HttpRequest;
17+
import software.amazon.smithy.java.io.ByteBufferUtils;
18+
import software.amazon.smithy.model.traits.HttpChecksumRequiredTrait;
19+
import software.amazon.smithy.utils.ListUtils;
20+
import software.amazon.smithy.utils.SmithyInternalApi;
21+
22+
/**
23+
* Plugin that adds Content-MD5 header for operations with @httpChecksumRequired trait.
24+
*/
25+
@SmithyInternalApi
26+
public final class HttpChecksumPlugin implements ClientPlugin {
27+
28+
@Override
29+
public void configureClient(ClientConfig.Builder config) {
30+
config.addInterceptor(HttpChecksumInterceptor.INSTANCE);
31+
}
32+
33+
static final class HttpChecksumInterceptor implements ClientInterceptor {
34+
private static final ClientInterceptor INSTANCE = new HttpChecksumInterceptor();
35+
private static final TraitKey<HttpChecksumRequiredTrait> CHECKSUM_REQUIRED_TRAIT_KEY =
36+
TraitKey.get(HttpChecksumRequiredTrait.class);
37+
38+
@Override
39+
public <RequestT> RequestT modifyBeforeTransmit(RequestHook<?, ?, RequestT> hook) {
40+
return hook.mapRequest(HttpRequest.class, HttpChecksumInterceptor::processRequest);
41+
}
42+
43+
private static HttpRequest processRequest(RequestHook<?, ?, HttpRequest> hook) {
44+
if (hook.operation().schema().hasTrait(CHECKSUM_REQUIRED_TRAIT_KEY)) {
45+
return addContentMd5Header(hook.request());
46+
}
47+
return hook.request();
48+
}
49+
50+
static HttpRequest addContentMd5Header(HttpRequest request) {
51+
var body = request.body();
52+
if (body != null) {
53+
var buffer = body.waitForByteBuffer();
54+
var bytes = ByteBufferUtils.getBytes(buffer);
55+
try {
56+
byte[] hash = MessageDigest.getInstance("MD5").digest(bytes);
57+
String base64Hash = Base64.getEncoder().encodeToString(hash);
58+
return request.toBuilder()
59+
.withReplacedHeader("Content-MD5", ListUtils.of(base64Hash))
60+
.build();
61+
} catch (NoSuchAlgorithmException e) {
62+
throw new IllegalStateException("Unable to fetch message digest instance for MD5", e);
63+
}
64+
}
65+
return request;
66+
}
67+
}
68+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.http.plugins;
7+
8+
import static org.hamcrest.MatcherAssert.assertThat;
9+
import static org.hamcrest.Matchers.equalTo;
10+
import static org.hamcrest.Matchers.hasSize;
11+
12+
import java.net.URI;
13+
import java.nio.charset.StandardCharsets;
14+
import org.junit.jupiter.api.Test;
15+
import software.amazon.smithy.java.http.api.HttpRequest;
16+
import software.amazon.smithy.java.io.datastream.DataStream;
17+
18+
public class HttpChecksumPluginTest {
19+
20+
@Test
21+
public void interceptorAddsContentMd5HeaderForKnownBody() throws Exception {
22+
var interceptor = new HttpChecksumPlugin.HttpChecksumInterceptor();
23+
var req = HttpRequest.builder()
24+
.uri(new URI("/"))
25+
.method("POST")
26+
.body(DataStream.ofBytes("test body".getBytes(StandardCharsets.UTF_8)))
27+
.build();
28+
29+
var result = interceptor.addContentMd5Header(req);
30+
31+
var headers = result.headers().allValues("Content-MD5");
32+
assertThat(headers, hasSize(1));
33+
assertThat(headers.get(0), equalTo("u/mv50Mcr1+Jpgi8MejYIg=="));
34+
}
35+
36+
@Test
37+
public void interceptorReplacesExistingContentMd5Header() throws Exception {
38+
var interceptor = new HttpChecksumPlugin.HttpChecksumInterceptor();
39+
var req = HttpRequest.builder()
40+
.uri(new URI("/"))
41+
.method("POST")
42+
.body(DataStream.ofBytes("test body".getBytes(StandardCharsets.UTF_8)))
43+
.withAddedHeader("Content-MD5", "wrong-hash")
44+
.build();
45+
46+
var result = interceptor.addContentMd5Header(req);
47+
48+
var headers = result.headers().allValues("Content-MD5");
49+
assertThat(headers, hasSize(1));
50+
assertThat(headers.get(0), equalTo("u/mv50Mcr1+Jpgi8MejYIg=="));
51+
}
52+
53+
}

0 commit comments

Comments
 (0)