Skip to content

Commit ddb505e

Browse files
committed
Simplify header and message creation, add closeable
Centralize header and message creation around building up modifiable headers/messages and then copying them to make them unmodifiable. This centralizes all formatting and grouping and trimming logic for headers, and we have some "fast-paths" for making copies since we know headers were already formatted and grouped. This also updates HttpMessage to be closeable so it cleans up any contained DataStream.
1 parent f679c63 commit ddb505e

20 files changed

Lines changed: 452 additions & 197 deletions

File tree

aws/aws-sigv4/src/main/java/software/amazon/smithy/java/aws/client/auth/scheme/sigv4/SigV4Signer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ final class SigV4Signer implements Signer<HttpRequest, AwsCredentialsIdentity> {
4141
private static final InternalLogger LOGGER = InternalLogger.getLogger(SigV4Signer.class);
4242
private static final List<String> HEADERS_TO_IGNORE_IN_LOWER_CASE = List.of(
4343
"connection",
44+
"content-length",
4445
"x-amzn-trace-id",
4546
"user-agent",
4647
"expect");

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,11 @@ private java.net.http.HttpRequest createJavaRequest(Context context, HttpRequest
134134

135135
// Any explicitly set headers overwrite existing headers, they do not merge.
136136
for (var entry : request.headers().map().entrySet()) {
137-
for (var value : entry.getValue()) {
138-
httpRequestBuilder.setHeader(entry.getKey(), value);
137+
// Skip restricted headers
138+
if (!entry.getKey().equals("content-length")) {
139+
for (var value : entry.getValue()) {
140+
httpRequestBuilder.setHeader(entry.getKey(), value);
141+
}
139142
}
140143
}
141144

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpHeaders.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,18 @@ default boolean isEmpty() {
110110
*
111111
* @return the created modifiable headers.
112112
*/
113-
ModifiableHttpHeaders toModifiable();
113+
default ModifiableHttpHeaders toModifiable() {
114+
return SimpleModifiableHttpHeaders.of(this);
115+
}
116+
117+
/**
118+
* Get an unmodifiable version of the headers.
119+
*
120+
* @return the unmodifiable headers.
121+
*/
122+
default HttpHeaders toUnmodifiable() {
123+
return SimpleUnmodifiableHttpHeaders.of(this);
124+
}
114125

115126
/**
116127
* Normalizes an HTTP header name by trimming whitespace and converting ASCII uppercase to lowercase.

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpMessage.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
/**
1515
* HTTP message.
16+
*
17+
* <p>When closed, the body of the message is also closed.
1618
*/
17-
public interface HttpMessage {
19+
public interface HttpMessage extends AutoCloseable {
1820
/**
1921
* Get the HTTP version.
2022
*
@@ -70,12 +72,20 @@ default long contentLength(long defaultValue) {
7072
HttpHeaders headers();
7173

7274
/**
73-
* Get the body of the message, or null.
75+
* Get the body of the message.
7476
*
75-
* @return the message body or null.
77+
* @return the message body (never null, may be zero length).
7678
*/
7779
DataStream body();
7880

81+
@Override
82+
default void close() {
83+
var body = body();
84+
if (body != null) {
85+
body.close();
86+
}
87+
}
88+
7989
/**
8090
* Builder for HTTP messages.
8191
*

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpRequest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ public interface HttpRequest extends HttpMessage {
3232
*/
3333
ModifiableHttpRequest toModifiable();
3434

35+
/**
36+
* Creates an unmodifiable copy of the request, or returns it as is if it is already unmodifiable.
37+
*
38+
* @return the unmodifiable version of this request.
39+
*/
40+
HttpRequest toUnmodifiable();
41+
3542
/**
3643
* Create a builder configured with the values of the request.
3744
*

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpRequestImpl.java

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ record HttpRequestImpl(
2020
HttpHeaders headers,
2121
DataStream body) implements HttpRequest {
2222

23+
HttpRequestImpl(ModifiableHttpRequestImpl from) {
24+
this(from.httpVersion(), from.method(), from.uri(), from.headers().toUnmodifiable(), from.body());
25+
}
26+
27+
@Override
28+
public HttpRequest toUnmodifiable() {
29+
return this;
30+
}
31+
2332
@Override
2433
public ModifiableHttpRequest toModifiable() {
2534
var mod = new ModifiableHttpRequestImpl();
@@ -32,28 +41,22 @@ public ModifiableHttpRequest toModifiable() {
3241
}
3342

3443
static final class Builder implements HttpRequest.Builder {
35-
36-
String method;
37-
URI uri;
38-
DataStream body;
39-
HttpHeaders headers = SimpleUnmodifiableHttpHeaders.EMPTY;
40-
HttpVersion httpVersion = HttpVersion.HTTP_1_1;
41-
private Map<String, List<String>> mutatedHeaders;
44+
private final ModifiableHttpRequest modifiableHttpRequest = new ModifiableHttpRequestImpl();
4245

4346
Builder() {}
4447

4548
public Builder httpVersion(HttpVersion httpVersion) {
46-
this.httpVersion = httpVersion;
49+
modifiableHttpRequest.setHttpVersion(httpVersion);
4750
return this;
4851
}
4952

5053
public Builder method(String method) {
51-
this.method = method;
54+
modifiableHttpRequest.setMethod(method);
5255
return this;
5356
}
5457

5558
public Builder uri(URI uri) {
56-
this.uri = uri;
59+
modifiableHttpRequest.setUri(uri);
5760
return this;
5861
}
5962

@@ -62,61 +65,49 @@ public Builder body(Flow.Publisher<ByteBuffer> publisher) {
6265
}
6366

6467
public Builder body(DataStream body) {
65-
this.body = body;
68+
modifiableHttpRequest.setBody(body);
6669
return this;
6770
}
6871

69-
public Builder headers(HttpHeaders headers) {
70-
this.headers = Objects.requireNonNull(headers);
71-
mutatedHeaders = null;
72+
@Override
73+
public HttpRequest.Builder headers(HttpHeaders headers) {
74+
modifiableHttpRequest.setHeaders(headers.toModifiable());
7275
return this;
7376
}
7477

7578
@Override
7679
public Builder withAddedHeader(String name, String value) {
77-
mutatedHeaders = SimpleUnmodifiableHttpHeaders.addHeader(this.headers, mutatedHeaders, name, value);
80+
modifiableHttpRequest.headers().addHeader(name, value);
7881
return this;
7982
}
8083

8184
@Override
8285
public Builder withAddedHeaders(Map<String, List<String>> headers) {
83-
mutatedHeaders = SimpleUnmodifiableHttpHeaders.addHeaders(this.headers, mutatedHeaders, headers);
86+
modifiableHttpRequest.headers().addHeaders(headers);
8487
return this;
8588
}
8689

8790
@Override
8891
public Builder withReplacedHeaders(Map<String, List<String>> headers) {
89-
mutatedHeaders = SimpleUnmodifiableHttpHeaders.replaceHeaders(this.headers, mutatedHeaders, headers);
92+
modifiableHttpRequest.headers().setHeaders(headers);
9093
return this;
9194
}
9295

9396
private void beforeBuild() {
94-
if (mutatedHeaders != null) {
95-
headers = new SimpleUnmodifiableHttpHeaders(mutatedHeaders, false);
96-
}
97-
Objects.requireNonNull(httpVersion, "HttpVersion cannot be null");
98-
Objects.requireNonNull(method, "Method cannot be null");
99-
Objects.requireNonNull(uri, "URI cannot be null");
100-
body = Objects.requireNonNullElse(body, DataStream.ofEmpty());
101-
mutatedHeaders = null; // decouple from built request
97+
Objects.requireNonNull(modifiableHttpRequest.method(), "method not set");
98+
Objects.requireNonNull(modifiableHttpRequest.uri(), "uri not set");
10299
}
103100

104101
@Override
105102
public HttpRequest build() {
106103
beforeBuild();
107-
return new HttpRequestImpl(httpVersion, method, uri, headers, body);
104+
return modifiableHttpRequest.toUnmodifiable();
108105
}
109106

110107
@Override
111108
public ModifiableHttpRequest buildModifiable() {
112109
beforeBuild();
113-
var mod = new ModifiableHttpRequestImpl();
114-
mod.setHttpVersion(httpVersion);
115-
mod.setMethod(method);
116-
mod.setUri(uri);
117-
mod.setHeaders(headers.toModifiable());
118-
mod.setBody(body);
119-
return mod;
110+
return modifiableHttpRequest.copy();
120111
}
121112
}
122113
}

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpResponse.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ public interface HttpResponse extends HttpMessage {
2323
*/
2424
ModifiableHttpResponse toModifiable();
2525

26+
/**
27+
* Creates an unmodifiable copy of the request, or returns it as is if it is already unmodifiable.
28+
*
29+
* @return the unmodifiable version of this request.
30+
*/
31+
HttpResponse toUnmodifiable();
32+
2633
/**
2734
* Create a builder configured with the values of the response.
2835
*

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpResponseImpl.java

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import java.util.List;
99
import java.util.Map;
10-
import java.util.Objects;
1110
import software.amazon.smithy.java.io.datastream.DataStream;
1211

1312
record HttpResponseImpl(
@@ -16,6 +15,15 @@ record HttpResponseImpl(
1615
HttpHeaders headers,
1716
DataStream body) implements HttpResponse {
1817

18+
HttpResponseImpl(ModifiableHttpResponse from) {
19+
this(from.httpVersion(), from.statusCode(), from.headers().toUnmodifiable(), from.body());
20+
}
21+
22+
@Override
23+
public HttpResponse toUnmodifiable() {
24+
return this;
25+
}
26+
1927
@Override
2028
public ModifiableHttpResponse toModifiable() {
2129
var mod = new ModifiableHttpResponseImpl();
@@ -27,84 +35,60 @@ public ModifiableHttpResponse toModifiable() {
2735
}
2836

2937
static final class Builder implements HttpResponse.Builder {
30-
int statusCode;
31-
DataStream body;
32-
HttpHeaders headers = SimpleUnmodifiableHttpHeaders.EMPTY;
33-
HttpVersion httpVersion = HttpVersion.HTTP_1_1;
34-
private Map<String, List<String>> mutatedHeaders;
38+
ModifiableHttpResponseImpl modifiableResponse = new ModifiableHttpResponseImpl();
3539

3640
Builder() {}
3741

3842
@Override
3943
public Builder httpVersion(HttpVersion httpVersion) {
40-
this.httpVersion = httpVersion;
44+
modifiableResponse.setHttpVersion(httpVersion);
4145
return this;
4246
}
4347

4448
@Override
4549
public Builder statusCode(int statusCode) {
46-
this.statusCode = statusCode;
50+
modifiableResponse.setStatusCode(statusCode);
4751
return this;
4852
}
4953

5054
@Override
5155
public Builder body(DataStream body) {
52-
this.body = body;
56+
modifiableResponse.setBody(body);
5357
return this;
5458
}
5559

5660
@Override
5761
public Builder headers(HttpHeaders headers) {
58-
this.headers = Objects.requireNonNull(headers);
59-
mutatedHeaders = null;
62+
modifiableResponse.setHeaders(headers.toModifiable());
6063
return this;
6164
}
6265

6366
@Override
6467
public Builder withAddedHeader(String name, String value) {
65-
mutatedHeaders = SimpleUnmodifiableHttpHeaders.addHeader(this.headers, mutatedHeaders, name, value);
68+
modifiableResponse.headers().addHeader(name, value);
6669
return this;
6770
}
6871

6972
@Override
7073
public Builder withAddedHeaders(Map<String, List<String>> headers) {
71-
mutatedHeaders = SimpleUnmodifiableHttpHeaders.addHeaders(this.headers, mutatedHeaders, headers);
74+
modifiableResponse.headers().addHeaders(headers);
7275
return this;
7376
}
7477

7578
@Override
7679
public Builder withReplacedHeaders(Map<String, List<String>> headers) {
77-
mutatedHeaders = SimpleUnmodifiableHttpHeaders.replaceHeaders(this.headers, mutatedHeaders, headers);
80+
modifiableResponse.headers().setHeaders(headers);
7881
return this;
7982
}
8083

81-
private void beforeBuild() {
82-
if (statusCode == 0) {
83-
throw new IllegalStateException("No status code was set on response");
84-
}
85-
if (mutatedHeaders != null) {
86-
headers = new SimpleUnmodifiableHttpHeaders(mutatedHeaders, false);
87-
}
88-
Objects.requireNonNull(httpVersion);
89-
body = Objects.requireNonNullElse(body, DataStream.ofEmpty());
90-
mutatedHeaders = null; // decouple from built response
91-
}
92-
9384
@Override
9485
public HttpResponse build() {
95-
beforeBuild();
96-
return new HttpResponseImpl(httpVersion, statusCode, headers, body);
86+
return modifiableResponse.toUnmodifiable();
9787
}
9888

9989
@Override
10090
public ModifiableHttpResponse buildModifiable() {
101-
beforeBuild();
102-
var mod = new ModifiableHttpResponseImpl();
103-
mod.setHttpVersion(httpVersion);
104-
mod.setStatusCode(statusCode);
105-
mod.setHeaders(headers.toModifiable());
106-
mod.setBody(body);
107-
return mod;
91+
return modifiableResponse.copy();
10892
}
10993
}
11094
}

http/http-api/src/main/java/software/amazon/smithy/java/http/api/HttpVersion.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Enumeration of the HTTP protocol versions.
1010
*/
1111
public enum HttpVersion {
12+
HTTP_1_0,
1213
HTTP_1_1,
1314
HTTP_2;
1415

@@ -20,15 +21,17 @@ public enum HttpVersion {
2021
*/
2122
public static HttpVersion from(String version) {
2223
return switch (version) {
23-
case "HTTP/1.1" -> HTTP_1_1;
24-
case "HTTP/2.0" -> HTTP_2;
24+
case "HTTP/1.0", "http/1.0" -> HTTP_1_0;
25+
case "HTTP/1.1", "http/1.1" -> HTTP_1_1;
26+
case "HTTP/2.0", "h2" -> HTTP_2;
2527
default -> throw new UnsupportedOperationException("Unsupported HTTP version: " + version);
2628
};
2729
}
2830

2931
@Override
3032
public String toString() {
3133
return switch (this) {
34+
case HTTP_1_0 -> "HTTP/1.0";
3235
case HTTP_1_1 -> "HTTP/1.1";
3336
case HTTP_2 -> "HTTP/2.0";
3437
};

0 commit comments

Comments
 (0)