Skip to content

Commit 8befe5f

Browse files
committed
adds an interceptor to replace params-to-body stage and applies it to every query protocol service through codegen
1 parent 46f1d8d commit 8befe5f

File tree

9 files changed

+358
-173
lines changed

9 files changed

+358
-173
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,11 @@ public boolean isXmlProtocol() {
519519
protocol == Protocol.REST_XML;
520520
}
521521

522+
public boolean isQueryProtocol() {
523+
return protocol == Protocol.EC2 ||
524+
protocol == Protocol.QUERY;
525+
}
526+
522527
/**
523528
* @return True for RESTful protocols. False for all other protocols (RPC, Query, etc).
524529
*/

codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
import com.squareup.javapoet.CodeBlock;
2525
import com.squareup.javapoet.FieldSpec;
2626
import com.squareup.javapoet.MethodSpec;
27+
import com.squareup.javapoet.ParameterizedTypeName;
28+
import com.squareup.javapoet.TypeName;
2729
import com.squareup.javapoet.TypeSpec;
2830
import com.squareup.javapoet.TypeVariableName;
31+
32+
import java.util.Collections;
2933
import java.util.List;
3034
import javax.lang.model.element.Modifier;
3135
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -46,6 +50,7 @@
4650
import software.amazon.awssdk.core.signer.Signer;
4751
import software.amazon.awssdk.http.Protocol;
4852
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
53+
import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor;
4954
import software.amazon.awssdk.utils.AttributeMap;
5055
import software.amazon.awssdk.utils.CollectionUtils;
5156
import software.amazon.awssdk.utils.StringUtils;
@@ -177,6 +182,16 @@ private MethodSpec finalizeServiceConfigurationMethod() {
177182
.addCode("interceptors = $T.mergeLists(interceptors, config.option($T.EXECUTION_INTERCEPTORS));\n",
178183
CollectionUtils.class, SdkClientOption.class);
179184

185+
if (model.getMetadata().isQueryProtocol()) {
186+
TypeName listType = ParameterizedTypeName.get(List.class, ExecutionInterceptor.class);
187+
builder.addStatement("$T protocolInterceptors = $T.singletonList(new $T())",
188+
listType,
189+
Collections.class,
190+
QueryParametersToBodyInterceptor.class);
191+
builder.addStatement("interceptors = $T.mergeLists(interceptors, protocolInterceptors)",
192+
CollectionUtils.class);
193+
}
194+
180195
if (model.getEndpointOperation().isPresent()) {
181196
builder.beginControlFlow("if (!endpointDiscoveryEnabled)")
182197
.addStatement("endpointDiscoveryEnabled = CHAIN.resolveEndpointDiscovery()")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.protocols.query.interceptor;
17+
18+
import static java.util.Collections.singletonList;
19+
import static software.amazon.awssdk.utils.StringUtils.lowerCase;
20+
21+
import java.io.ByteArrayInputStream;
22+
import java.nio.charset.StandardCharsets;
23+
24+
import software.amazon.awssdk.annotations.SdkProtectedApi;
25+
import software.amazon.awssdk.core.interceptor.Context;
26+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
27+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
28+
import software.amazon.awssdk.http.SdkHttpFullRequest;
29+
import software.amazon.awssdk.http.SdkHttpMethod;
30+
import software.amazon.awssdk.http.SdkHttpRequest;
31+
import software.amazon.awssdk.utils.CollectionUtils;
32+
import software.amazon.awssdk.utils.http.SdkHttpUtils;
33+
34+
/**
35+
* Modifies an HTTP request by moving query parameters to the body under the following conditions:
36+
* - It is a POST request
37+
* - There is no content stream provider
38+
* - There are query parameters to transfer
39+
* <p>
40+
* This interceptor is automatically inserted by codegen for services using Query Protocol
41+
*/
42+
@SdkProtectedApi
43+
public final class QueryParametersToBodyInterceptor implements ExecutionInterceptor {
44+
45+
private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=" +
46+
lowerCase(StandardCharsets.UTF_8.toString());
47+
48+
@Override
49+
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
50+
ExecutionAttributes executionAttributes) {
51+
52+
SdkHttpRequest httpRequest = context.httpRequest();
53+
54+
if (!(httpRequest instanceof SdkHttpFullRequest)) {
55+
return httpRequest;
56+
}
57+
58+
SdkHttpFullRequest httpFullRequest = (SdkHttpFullRequest) httpRequest;
59+
if (shouldPutParamsInBody(httpFullRequest)) {
60+
return changeQueryParametersToFormData(httpFullRequest);
61+
}
62+
return httpFullRequest;
63+
}
64+
65+
private boolean shouldPutParamsInBody(SdkHttpFullRequest input) {
66+
return input.method() == SdkHttpMethod.POST &&
67+
!input.contentStreamProvider().isPresent() &&
68+
!CollectionUtils.isNullOrEmpty(input.rawQueryParameters());
69+
}
70+
71+
private SdkHttpRequest changeQueryParametersToFormData(SdkHttpFullRequest input) {
72+
byte[] params = SdkHttpUtils.encodeAndFlattenFormData(input.rawQueryParameters()).orElse("")
73+
.getBytes(StandardCharsets.UTF_8);
74+
75+
return input.toBuilder().clearQueryParameters()
76+
.contentStreamProvider(() -> new ByteArrayInputStream(params))
77+
.putHeader("Content-Length", singletonList(String.valueOf(params.length)))
78+
.putHeader("Content-Type", singletonList(DEFAULT_CONTENT_TYPE))
79+
.build();
80+
}
81+
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.protocols.query.interceptor;
17+
18+
import org.junit.Before;
19+
import org.junit.Test;
20+
import software.amazon.awssdk.core.Protocol;
21+
import software.amazon.awssdk.core.SdkRequest;
22+
import software.amazon.awssdk.core.async.AsyncRequestBody;
23+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
24+
import software.amazon.awssdk.core.sync.RequestBody;
25+
import software.amazon.awssdk.http.ContentStreamProvider;
26+
import software.amazon.awssdk.http.SdkHttpFullRequest;
27+
import software.amazon.awssdk.http.SdkHttpMethod;
28+
import software.amazon.awssdk.http.SdkHttpRequest;
29+
import software.amazon.awssdk.utils.IoUtils;
30+
31+
import java.io.ByteArrayInputStream;
32+
import java.net.URI;
33+
import java.nio.charset.StandardCharsets;
34+
import java.util.Optional;
35+
import java.util.stream.Stream;
36+
37+
import static java.util.Collections.singletonList;
38+
import static org.assertj.core.api.Assertions.assertThat;
39+
40+
public class QueryParametersToBodyInterceptorTest {
41+
42+
public static final URI HTTP_LOCALHOST = URI.create("http://localhost:8080");
43+
44+
private QueryParametersToBodyInterceptor interceptor;
45+
private ExecutionAttributes executionAttributes;
46+
47+
private SdkHttpFullRequest.Builder requestBuilder;
48+
49+
@Before
50+
public void setup() {
51+
52+
interceptor = new QueryParametersToBodyInterceptor();
53+
executionAttributes = new ExecutionAttributes();
54+
55+
requestBuilder = SdkHttpFullRequest.builder()
56+
.protocol(Protocol.HTTPS.toString())
57+
.method(SdkHttpMethod.POST)
58+
.putRawQueryParameter("key", singletonList("value"))
59+
.uri(HTTP_LOCALHOST);
60+
}
61+
62+
@Test
63+
public void postRequestsWithNoBodyHaveTheirParametersMovedToTheBody() throws Exception {
64+
65+
SdkHttpFullRequest request = requestBuilder.build();
66+
67+
SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
68+
new HttpRequestOnlyContext(request, null), executionAttributes);
69+
70+
assertThat(output.rawQueryParameters()).hasSize(0);
71+
assertThat(output.headers())
72+
.containsKey("Content-Length")
73+
.containsEntry("Content-Type", singletonList("application/x-www-form-urlencoded; charset=utf-8"));
74+
assertThat(output.contentStreamProvider()).isNotEmpty();
75+
}
76+
77+
@Test
78+
public void nonPostRequestsWithNoBodyAreUnaltered() throws Exception {
79+
Stream.of(SdkHttpMethod.values())
80+
.filter(m -> !m.equals(SdkHttpMethod.POST))
81+
.forEach(this::nonPostRequestsUnaltered);
82+
}
83+
84+
@Test
85+
public void postWithContentIsUnaltered() throws Exception {
86+
byte[] contentBytes = "hello".getBytes(StandardCharsets.UTF_8);
87+
ContentStreamProvider contentProvider = () -> new ByteArrayInputStream(contentBytes);
88+
89+
SdkHttpFullRequest request = requestBuilder.contentStreamProvider(contentProvider).build();
90+
91+
SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
92+
new HttpRequestOnlyContext(request, null), executionAttributes);
93+
94+
assertThat(output.rawQueryParameters()).hasSize(1);
95+
assertThat(output.headers()).hasSize(0);
96+
assertThat(IoUtils.toByteArray(output.contentStreamProvider().get().newStream())).isEqualTo(contentBytes);
97+
}
98+
99+
@Test
100+
public void onlyAlterRequestsIfParamsArePresent() throws Exception {
101+
SdkHttpFullRequest request = requestBuilder.clearQueryParameters().build();
102+
103+
SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
104+
new HttpRequestOnlyContext(request, null), executionAttributes);
105+
106+
assertThat(output.rawQueryParameters()).hasSize(0);
107+
assertThat(output.headers()).hasSize(0);
108+
assertThat(output.contentStreamProvider()).isEmpty();
109+
}
110+
111+
private void nonPostRequestsUnaltered(SdkHttpMethod method) {
112+
113+
SdkHttpFullRequest request = requestBuilder.method(method).build();
114+
115+
SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
116+
new HttpRequestOnlyContext(request, null), executionAttributes);
117+
118+
assertThat(output.rawQueryParameters()).hasSize(1);
119+
assertThat(output.headers()).hasSize(0);
120+
assertThat(output.contentStreamProvider()).isEmpty();
121+
}
122+
123+
public final class HttpRequestOnlyContext implements software.amazon.awssdk.core.interceptor.Context.ModifyHttpRequest {
124+
125+
private final SdkHttpRequest request;
126+
private final RequestBody requestBody;
127+
128+
public HttpRequestOnlyContext(SdkHttpRequest request,
129+
RequestBody requestBody) {
130+
this.request = request;
131+
this.requestBody = requestBody;
132+
}
133+
134+
@Override
135+
public SdkRequest request() {
136+
return null;
137+
}
138+
139+
@Override
140+
public SdkHttpRequest httpRequest() {
141+
return request;
142+
}
143+
144+
@Override
145+
public Optional<RequestBody> requestBody() {
146+
return Optional.ofNullable(requestBody);
147+
}
148+
149+
@Override
150+
public Optional<AsyncRequestBody> asyncRequestBody() {
151+
return Optional.empty();
152+
}
153+
}
154+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestMutableStage;
4141
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomHeadersStage;
4242
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomQueryParamsStage;
43-
import software.amazon.awssdk.core.internal.http.pipeline.stages.MoveParametersToBodyStage;
4443
import software.amazon.awssdk.core.internal.http.pipeline.stages.SigningStage;
4544
import software.amazon.awssdk.core.internal.http.pipeline.stages.UnwrapResponseContainer;
4645
import software.amazon.awssdk.core.internal.retry.SdkDefaultRetrySetting;
@@ -178,7 +177,6 @@ public <OutputT> CompletableFuture<OutputT> execute(
178177
.then(ApplyUserAgentStage::new)
179178
.then(MergeCustomHeadersStage::new)
180179
.then(MergeCustomQueryParamsStage::new)
181-
.then(MoveParametersToBodyStage::new)
182180
.then(MakeRequestImmutableStage::new)
183181
.then(RequestPipelineBuilder
184182
.first(SigningStage::new)

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestMutableStage;
4141
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomHeadersStage;
4242
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomQueryParamsStage;
43-
import software.amazon.awssdk.core.internal.http.pipeline.stages.MoveParametersToBodyStage;
4443
import software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage;
4544
import software.amazon.awssdk.core.internal.http.pipeline.stages.SigningStage;
4645
import software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage;
@@ -178,7 +177,6 @@ public <OutputT> OutputT execute(HttpResponseHandler<Response<OutputT>> response
178177
.then(ApplyUserAgentStage::new)
179178
.then(MergeCustomHeadersStage::new)
180179
.then(MergeCustomQueryParamsStage::new)
181-
.then(MoveParametersToBodyStage::new)
182180
.then(MakeRequestImmutableStage::new)
183181
// End of mutating request
184182
.then(RequestPipelineBuilder

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MoveParametersToBodyStage.java

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)