Skip to content

Commit 59b59a7

Browse files
committed
Optimize HTTP binding and XML overhead
1 parent 0befb09 commit 59b59a7

32 files changed

Lines changed: 2288 additions & 969 deletions

benchmarks/serde-benchmarks/src/jmh/java/software/amazon/smithy/java/benchmarks/serde/AwsQueryDeserializeBenchmark.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ public class AwsQueryDeserializeBenchmark {
3131
private static final String VERSION = "1999-12-31";
3232
private static final String CONTENT_TYPE = "text/xml";
3333

34+
/**
35+
* Empty body fixture for response tests that have no body. AWS Query responses are XML, so an empty
36+
* {@code byte[]} cleanly hits the {@code XmlCodec} empty-buffer fast path. None of the current
37+
* {@code GetMetricDataResponse_*} test cases have empty bodies, but keeping the fixture consistent with
38+
* {@link RestXmlDeserializeBenchmark} makes the auto-derive workaround in {@link DeserializeState} unnecessary.
39+
*/
40+
private static final byte[] EMPTY_XML_BODY = new byte[0];
41+
3442
@Param({
3543
"awsQuery_GetMetricDataResponse_S",
3644
"awsQuery_GetMetricDataResponse_M",
@@ -45,7 +53,7 @@ public class AwsQueryDeserializeBenchmark {
4553
@Setup
4654
public void setup() {
4755
protocol = new AwsQueryClientProtocol(SERVICE_ID, VERSION);
48-
state = DeserializeState.forTestCase(testCaseId, GENERATED_PACKAGE, null, CONTENT_TYPE, false);
56+
state = DeserializeState.forTestCase(testCaseId, GENERATED_PACKAGE, EMPTY_XML_BODY, CONTENT_TYPE, false);
4957
}
5058

5159
@Benchmark

benchmarks/serde-benchmarks/src/jmh/java/software/amazon/smithy/java/benchmarks/serde/RestXmlDeserializeBenchmark.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ public class RestXmlDeserializeBenchmark {
2828
"software.amazon.smithy.java.benchmarks.serde.generated.restxml.model";
2929
private static final ShapeId SERVICE_ID =
3030
ShapeId.from("com.amazonaws.sdk.benchmark#AwsRestXmlDataPlane");
31-
/** Pass null to DeserializeState to use the auto-derived <ShapeName/> default. */
32-
private static final byte[] EMPTY_XML_BODY = null;
31+
/**
32+
* Empty body fixture for response tests that have no body. Feeding zero bytes here accurately models the wire
33+
* reality of an empty 200 response and exercises that fast path.
34+
*/
35+
private static final byte[] EMPTY_XML_BODY = new byte[0];
3336
private static final String CONTENT_TYPE = "application/xml";
3437

3538
@Param({
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.xml;
7+
8+
import java.math.BigDecimal;
9+
import java.math.BigInteger;
10+
import java.nio.ByteBuffer;
11+
import java.time.Instant;
12+
import software.amazon.smithy.java.core.schema.Schema;
13+
import software.amazon.smithy.java.core.serde.SerializationException;
14+
import software.amazon.smithy.java.core.serde.ShapeDeserializer;
15+
import software.amazon.smithy.java.core.serde.document.Document;
16+
17+
/**
18+
* A no-op {@link ShapeDeserializer} for empty XML payloads.
19+
*/
20+
final class EmptyXmlDeserializer implements ShapeDeserializer {
21+
22+
static final EmptyXmlDeserializer INSTANCE = new EmptyXmlDeserializer();
23+
24+
private EmptyXmlDeserializer() {}
25+
26+
@Override
27+
public boolean isNull() {
28+
return true;
29+
}
30+
31+
@Override
32+
public <T> void readStruct(Schema schema, T state, StructMemberConsumer<T> consumer) {}
33+
34+
@Override
35+
public <T> void readList(Schema schema, T state, ListMemberConsumer<T> consumer) {}
36+
37+
@Override
38+
public <T> void readStringMap(Schema schema, T state, MapMemberConsumer<String, T> consumer) {}
39+
40+
// Scalar reads are not valid at the top level of an empty XML body.
41+
@Override
42+
public boolean readBoolean(Schema schema) {
43+
throw empty(schema);
44+
}
45+
46+
@Override
47+
public ByteBuffer readBlob(Schema schema) {
48+
throw empty(schema);
49+
}
50+
51+
@Override
52+
public byte readByte(Schema schema) {
53+
throw empty(schema);
54+
}
55+
56+
@Override
57+
public short readShort(Schema schema) {
58+
throw empty(schema);
59+
}
60+
61+
@Override
62+
public int readInteger(Schema schema) {
63+
throw empty(schema);
64+
}
65+
66+
@Override
67+
public long readLong(Schema schema) {
68+
throw empty(schema);
69+
}
70+
71+
@Override
72+
public float readFloat(Schema schema) {
73+
throw empty(schema);
74+
}
75+
76+
@Override
77+
public double readDouble(Schema schema) {
78+
throw empty(schema);
79+
}
80+
81+
@Override
82+
public BigInteger readBigInteger(Schema schema) {
83+
throw empty(schema);
84+
}
85+
86+
@Override
87+
public BigDecimal readBigDecimal(Schema schema) {
88+
throw empty(schema);
89+
}
90+
91+
@Override
92+
public String readString(Schema schema) {
93+
throw empty(schema);
94+
}
95+
96+
@Override
97+
public Document readDocument() {
98+
throw new SerializationException("Cannot read a document value from an empty XML payload");
99+
}
100+
101+
@Override
102+
public Instant readTimestamp(Schema schema) {
103+
throw empty(schema);
104+
}
105+
106+
private static SerializationException empty(Schema schema) {
107+
return new SerializationException("Cannot read " + schema.id() + " value from an empty XML payload");
108+
}
109+
}

codecs/xml-codec/src/main/java/software/amazon/smithy/java/xml/XmlCodec.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public ShapeSerializer createSerializer(OutputStream sink) {
6363

6464
@Override
6565
public ShapeDeserializer createDeserializer(ByteBuffer source) {
66+
if (source == null || !source.hasRemaining()) {
67+
return EmptyXmlDeserializer.INSTANCE;
68+
}
69+
6670
try {
6771
var reader = xmlInputFactory.createXMLStreamReader(ByteBufferUtils.byteBufferInputStream(source));
6872
return XmlDeserializer.topLevel(

codecs/xml-codec/src/test/java/software/amazon/smithy/java/xml/XmlCodecTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ public void deserializesXml() {
4848
}
4949
}
5050

51+
@Test
52+
public void deserializesEmptyBody() {
53+
try (var codec = XmlCodec.builder().build()) {
54+
// Empty ByteBuffer: top-level readStruct should be a no-op
55+
var pojo = codec.deserializeShape(ByteBuffer.allocate(0), new TestPojo.Builder());
56+
assertThat(pojo.name, equalTo(null));
57+
assertThat(pojo.date, equalTo(null));
58+
assertThat(pojo.numbers, equalTo(List.of()));
59+
}
60+
}
61+
5162
@Test
5263
public void serializesXml() {
5364
try (var codec = XmlCodec.builder().build()) {

core/src/main/java/software/amazon/smithy/java/core/schema/DeferredRootSchema.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ public Schema member(String memberName) {
9595
return resolvedMembers.members.get(memberName);
9696
}
9797

98+
@Override
99+
public Schema member(int memberIndex) {
100+
resolveInternal();
101+
return resolvedMembers.memberList.get(memberIndex);
102+
}
103+
98104
@Override
99105
public Set<Integer> intEnumValues() {
100106
return intEnumValues;

core/src/main/java/software/amazon/smithy/java/core/schema/ResolvedRootSchema.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public Schema member(String memberName) {
4646
return members.get(memberName);
4747
}
4848

49+
@Override
50+
public Schema member(int memberIndex) {
51+
return memberList.get(memberIndex);
52+
}
53+
4954
@Override
5055
public Set<Integer> intEnumValues() {
5156
return intEnumValues;

core/src/main/java/software/amazon/smithy/java/core/schema/RootSchema.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ public Schema member(String memberName) {
108108
return members.get(memberName);
109109
}
110110

111+
@Override
112+
public Schema member(int memberIndex) {
113+
return memberList.get(memberIndex);
114+
}
115+
111116
@Override
112117
public Set<Integer> intEnumValues() {
113118
return intEnumValues;

core/src/main/java/software/amazon/smithy/java/core/schema/Schema.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,22 @@ public Schema member(String memberName) {
547547
return null;
548548
}
549549

550+
/**
551+
* Get a member by its index.
552+
*
553+
* <p>Index is the position of the member in declaration order, matching
554+
* what {@link Schema#memberIndex()} returns for that member. Direct array
555+
* indexing — significantly faster than {@link #member(String)} when the
556+
* caller already has a member's index in hand (for example, when walking
557+
* a precomputed plan).
558+
*
559+
* @param memberIndex Index of the member, 0-based in declaration order.
560+
* @return the found member, or null if this schema has no such index.
561+
*/
562+
public Schema member(int memberIndex) {
563+
return null;
564+
}
565+
550566
/**
551567
* Get the member of a list.
552568
*

0 commit comments

Comments
 (0)