From 978054d3cf66e1633acf442172991828bf023076 Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Thu, 28 Aug 2025 10:51:27 -0700 Subject: [PATCH 1/4] Sigv4 Signer optimizations --- .../internal/signer/V4CanonicalRequest.java | 34 ++++---- .../signer/Sigv4SignerBenchmark.java | 81 +++++++++++++++++++ 2 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 test/sdk-benchmarks/src/main/java/software/amazon/awssdk/benchmark/signer/Sigv4SignerBenchmark.java diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java index ea14f356889e..ec5d1b5e60ac 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java @@ -94,6 +94,8 @@ public String getCanonicalRequestString() { return canonicalRequestString; } + + private SortedMap> canonicalQueryParams() { if (canonicalParams == null) { canonicalParams = getCanonicalQueryParams(request); @@ -202,21 +204,17 @@ public static String getCanonicalHeadersString(List>> * Get the string representing which headers are part of the signing process. Header names are separated by a semicolon. */ public static String getSignedHeadersString(List>> canonicalHeaders) { - String signedHeadersString; StringBuilder headersString = new StringBuilder(512); for (Pair> header : canonicalHeaders) { - headersString.append(header.left()).append(";"); + headersString.append(header.left()).append(';'); } - // get rid of trailing semicolon - signedHeadersString = headersString.toString(); - - boolean trimTrailingSemicolon = signedHeadersString.length() > 1 && - signedHeadersString.endsWith(";"); - if (trimTrailingSemicolon) { - signedHeadersString = signedHeadersString.substring(0, signedHeadersString.length() - 1); + int len = headersString.length(); + if (len > 0) { + headersString.setLength(len - 1); // remove last semicolon } - return signedHeadersString; + + return headersString.toString(); } /** @@ -296,13 +294,17 @@ private static void addAndTrim(StringBuilder result, String value) { * If the path is empty, a single-forward slash ('/') is returned. */ private static String getCanonicalUri(SdkHttpRequest request, Options options) { - String path = options.normalizePath ? request.getUri().normalize().getRawPath() - : request.encodedPath(); - - if (StringUtils.isEmpty(path)) { + String path = request.encodedPath(); + if (StringUtils.isEmpty(path) || "/".equals(path)) { return "/"; } + if (options.normalizePath) { + path = request.getUri().normalize().getRawPath(); + } + + + if (options.doubleUrlEncode) { path = SdkHttpUtils.urlEncodeIgnoreSlashes(path); } @@ -329,6 +331,10 @@ private static String getCanonicalUri(SdkHttpRequest request, Options options) { * Get the sorted map of query parameters that are to be signed. */ private static SortedMap> getCanonicalQueryParams(SdkHttpRequest request) { + if (request.numRawQueryParameters() == 0) { + return Collections.emptySortedMap(); + } + SortedMap> sorted = new TreeMap<>(); // Signing protocol expects the param values also to be sorted after url diff --git a/test/sdk-benchmarks/src/main/java/software/amazon/awssdk/benchmark/signer/Sigv4SignerBenchmark.java b/test/sdk-benchmarks/src/main/java/software/amazon/awssdk/benchmark/signer/Sigv4SignerBenchmark.java new file mode 100644 index 000000000000..3f949bc45d48 --- /dev/null +++ b/test/sdk-benchmarks/src/main/java/software/amazon/awssdk/benchmark/signer/Sigv4SignerBenchmark.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.benchmark.signer; + +import java.net.URI; +import java.time.Clock; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.internal.signer.CredentialScope; +import software.amazon.awssdk.http.auth.aws.internal.signer.V4Properties; +import software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigner; +import software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigningResult; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; + + +@State(Scope.Thread) +@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(2) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +public class Sigv4SignerBenchmark { + SdkHttpRequest.Builder request; + V4RequestSigner siger; + + // Setup runs once per trial or iteration (not measured) + @Setup(Level.Iteration) + public void setup() { + URI target = URI.create("https://test.com/"); + request = SdkHttpRequest.builder() + .method(SdkHttpMethod.GET) + .uri(target) + .encodedPath(target.getPath()) + .putHeader("x-amz-content-sha256", "checksum") + .putHeader("x-amz-archive-description", "test test"); + AwsCredentialsIdentity creds = + AwsCredentialsIdentity.create("access", "secret"); + Clock clock = Clock.systemUTC(); + V4Properties properties = V4Properties.builder() + .credentials(creds) + .credentialScope(new CredentialScope("us-east-1", "demo", clock.instant())) + .signingClock(clock) + .doubleUrlEncode(true) + .normalizePath(true) + .build(); + + siger = V4RequestSigner.create(properties, "abc123"); + } + + @Benchmark + public void benchmarkSign(Blackhole blackhole) { + V4RequestSigningResult signingResult = siger.sign(request); + blackhole.consume(signingResult); + } +} \ No newline at end of file From 2909cd2a3314ee69f9d1644bbb405403a9d61b8a Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Thu, 28 Aug 2025 10:53:43 -0700 Subject: [PATCH 2/4] Add changelog --- .changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json b/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json new file mode 100644 index 000000000000..f661210b2215 --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Improve Sigv4 signing performance, particurally for rpc protocols" +} From 3e18f8c8b8d91292e70f70dce94096b2447eabbb Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Thu, 28 Aug 2025 11:15:26 -0700 Subject: [PATCH 3/4] Minor cleanup --- .../http/auth/aws/internal/signer/V4CanonicalRequest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java index ec5d1b5e60ac..cdafec6489be 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java @@ -209,9 +209,8 @@ public static String getSignedHeadersString(List>> can headersString.append(header.left()).append(';'); } - int len = headersString.length(); - if (len > 0) { - headersString.setLength(len - 1); // remove last semicolon + if (headersString.length() > 0) { + headersString.setLength(headersString.length() - 1); // remove last semicolon } return headersString.toString(); From 703c57a0eca787c1469e8542db95f151d4b7e22f Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Fri, 29 Aug 2025 07:34:59 -0700 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: David Ho <70000000+davidh44@users.noreply.github.com> --- .changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json | 2 +- .../http/auth/aws/internal/signer/V4CanonicalRequest.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json b/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json index f661210b2215..b3bd0c0bb365 100644 --- a/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-5e4425a.json @@ -2,5 +2,5 @@ "type": "bugfix", "category": "AWS SDK for Java v2", "contributor": "", - "description": "Improve Sigv4 signing performance, particurally for rpc protocols" + "description": "Improve Sigv4 signing performance, particularly for RPC protocols" } diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java index cdafec6489be..ea9794da5470 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/V4CanonicalRequest.java @@ -94,8 +94,6 @@ public String getCanonicalRequestString() { return canonicalRequestString; } - - private SortedMap> canonicalQueryParams() { if (canonicalParams == null) { canonicalParams = getCanonicalQueryParams(request);