Skip to content

Commit 65eec08

Browse files
committed
Improve what we pre-compute for literal query
We were precomputing arrays of un-encoded query string key value pairs for literal query string parts of a URI. This instead pre-computes the already encoded query literals.
1 parent b486ecf commit 65eec08

3 files changed

Lines changed: 50 additions & 21 deletions

File tree

http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/HttpBindingSchemaExtensions.java

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import software.amazon.smithy.java.core.serde.ShapeSerializer;
2222
import software.amazon.smithy.java.core.serde.TimestampFormatter;
2323
import software.amazon.smithy.java.http.api.HeaderName;
24+
import software.amazon.smithy.java.io.uri.URLEncoding;
2425
import software.amazon.smithy.model.shapes.ShapeType;
2526
import software.amazon.smithy.model.traits.HttpTrait;
2627
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -40,6 +41,10 @@ public final class HttpBindingSchemaExtensions
4041
*/
4142
public static final SchemaExtensionKey<HttpBindingExt> KEY = new SchemaExtensionKey<>();
4243

44+
private static final Schema[] NO_SCHEMAS = new Schema[0];
45+
private static final HeaderName[] NO_HEADER_NAMES = new HeaderName[0];
46+
private static final String[] NO_STRINGS = new String[0];
47+
4348
/**
4449
* Look up the {@link MemberBinding} for a member schema. Throws if no binding or not a member.
4550
*/
@@ -87,9 +92,6 @@ static StructBindings structBindingsOf(Schema schema) {
8792
return sb;
8893
}
8994

90-
private static final Schema[] NO_SCHEMAS = new Schema[0];
91-
private static final String[] NO_QUERY_LITERALS = new String[0];
92-
9395
/**
9496
* Binding kind for a single member, derived from the traits applied to it.
9597
*
@@ -356,12 +358,14 @@ ByteBuffer emptyBody(Codec codec, Schema schema) {
356358
* Pre-computed HTTP-binding data for an operation schema.
357359
*
358360
* @param httpTrait the cached {@code @http} trait — saves an {@code expectTrait} call per request.
359-
* @param queryLiterals flat array of (name, value) pairs from the URI's static query literals, or empty.
361+
* @param queryLiteralKeys raw {@code @http} URI query literal keys (e.g. {@code ["x-id"]}).
362+
* @param queryLiteralEntries pre-encoded {@code "key=value"} strings parallel to {@link #queryLiteralKeys}.
360363
* @param defaultResponseStatus default response status declared by the {@code @http} trait.
361364
*/
362365
record OperationBinding(
363366
HttpTrait httpTrait,
364-
String[] queryLiterals,
367+
String[] queryLiteralKeys,
368+
String[] queryLiteralEntries,
365369
int defaultResponseStatus) implements HttpBindingExt {}
366370

367371
@Override
@@ -732,9 +736,6 @@ private static Schema[] toArray(List<Schema> list) {
732736
return list.isEmpty() ? NO_SCHEMAS : list.toArray(new Schema[0]);
733737
}
734738

735-
private static final HeaderName[] NO_HEADER_NAMES = new HeaderName[0];
736-
private static final String[] NO_STRINGS = new String[0];
737-
738739
/**
739740
* Pre-resolve canonical {@link HeaderName}s parallel to a {@code Schema[]} of
740741
* list-header members. Reading the name later in the deserializer becomes a
@@ -772,21 +773,31 @@ private static OperationBinding forOperation(Schema schema) {
772773
var httpTrait = schema.expectTrait(TraitKey.HTTP_TRAIT);
773774
var uriPattern = httpTrait.getUri();
774775

775-
// Flatten query literals into a (name, value) pair array. The trait's own map iteration is stable for a
776-
// given trait instance, but this avoids the per-call iterator + Map.Entry allocations.
776+
// Pre-encode static @http URI query literals into "key=value" strings so the request hot path can append
777+
// them verbatim instead of re-encoding per call.
777778
var queryLiteralMap = uriPattern.getQueryLiterals();
778-
String[] queryLiterals;
779+
String[] queryLiteralKeys;
780+
String[] queryLiteralEntries;
779781
if (queryLiteralMap.isEmpty()) {
780-
queryLiterals = NO_QUERY_LITERALS;
782+
queryLiteralKeys = NO_STRINGS;
783+
queryLiteralEntries = NO_STRINGS;
781784
} else {
782-
queryLiterals = new String[2 * queryLiteralMap.size()];
785+
int n = queryLiteralMap.size();
786+
queryLiteralKeys = new String[n];
787+
queryLiteralEntries = new String[n];
783788
int i = 0;
789+
StringBuilder pair = new StringBuilder();
784790
for (var entry : queryLiteralMap.entrySet()) {
785-
queryLiterals[i++] = entry.getKey();
786-
queryLiterals[i++] = entry.getValue();
791+
pair.setLength(0);
792+
URLEncoding.encodeUnreserved(entry.getKey(), pair, false);
793+
pair.append('=');
794+
URLEncoding.encodeUnreserved(entry.getValue(), pair, false);
795+
queryLiteralKeys[i] = entry.getKey();
796+
queryLiteralEntries[i] = pair.toString();
797+
i++;
787798
}
788799
}
789800

790-
return new OperationBinding(httpTrait, queryLiterals, httpTrait.getCode());
801+
return new OperationBinding(httpTrait, queryLiteralKeys, queryLiteralEntries, httpTrait.getCode());
791802
}
792803
}

http/http-binding/src/main/java/software/amazon/smithy/java/http/binding/HttpBindingSerializer.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,13 @@ public void writeStruct(Schema schema, SerializableStruct struct) {
144144

145145
headers = HttpHeaders.ofModifiable(headerCount);
146146

147-
// Add fixed query string parameters from @http trait's uri field.
148-
String[] qLits = operationBinding.queryLiterals();
149-
if (qLits.length > 0) {
147+
// Append the static @http URI query literals
148+
String[] qKeys = operationBinding.queryLiteralKeys();
149+
if (qKeys.length > 0) {
150150
QueryStringBuilder qsb = queryStringParams();
151-
for (int i = 0; i < qLits.length; i += 2) {
152-
qsb.add(qLits[i], qLits[i + 1]);
151+
String[] qEntries = operationBinding.queryLiteralEntries();
152+
for (int i = 0; i < qKeys.length; i++) {
153+
qsb.addPreEncoded(qKeys[i], qEntries[i]);
153154
}
154155
}
155156

io/src/main/java/software/amazon/smithy/java/io/uri/QueryStringBuilder.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ public void addForQueryParams(String key, String value) {
7979
}
8080
}
8181

82+
/**
83+
* Append a pre-encoded {@code "key=value"} entry to the query string and register its raw key with the
84+
* {@code @httpQuery} dedupe set so a subsequent {@link #addForQueryParams} with the same key will be skipped.
85+
*
86+
* @param rawKey raw (unencoded) key, used only for the dedupe set.
87+
* @param encodedKeyEqValue {@code "encodedKey=encodedValue"} as a single ready-to-append string.
88+
*/
89+
public void addPreEncoded(String rawKey, String encodedKeyEqValue) {
90+
if (!empty) {
91+
builder.append('&');
92+
} else {
93+
empty = false;
94+
}
95+
builder.append(encodedKeyEqValue);
96+
httpQueryKeys.add(rawKey);
97+
}
98+
8299
private void append(String key, String value) {
83100
if (!empty) {
84101
builder.append('&');

0 commit comments

Comments
 (0)