Skip to content

Commit

Permalink
Merge pull request #1566 from aws/salande/unsupported-payloads-interc…
Browse files Browse the repository at this point in the history
…eptor

adds an interceptor to replace params-to-body stage and applies it to…
  • Loading branch information
cenedhryn authored Jan 9, 2020
2 parents e8472b0 + a5bca3d commit 308c95a
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,11 @@ public boolean isXmlProtocol() {
protocol == Protocol.REST_XML;
}

public boolean isQueryProtocol() {
return protocol == Protocol.EC2 ||
protocol == Protocol.QUERY;
}

/**
* @return True for RESTful protocols. False for all other protocols (RPC, Query, etc).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;

import java.util.Collections;
import java.util.List;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkInternalApi;
Expand All @@ -46,6 +50,7 @@
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.protocols.query.interceptor.QueryParametersToBodyInterceptor;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.CollectionUtils;
import software.amazon.awssdk.utils.StringUtils;
Expand Down Expand Up @@ -177,6 +182,16 @@ private MethodSpec finalizeServiceConfigurationMethod() {
.addCode("interceptors = $T.mergeLists(interceptors, config.option($T.EXECUTION_INTERCEPTORS));\n",
CollectionUtils.class, SdkClientOption.class);

if (model.getMetadata().isQueryProtocol()) {
TypeName listType = ParameterizedTypeName.get(List.class, ExecutionInterceptor.class);
builder.addStatement("$T protocolInterceptors = $T.singletonList(new $T())",
listType,
Collections.class,
QueryParametersToBodyInterceptor.class);
builder.addStatement("interceptors = $T.mergeLists(interceptors, protocolInterceptors)",
CollectionUtils.class);
}

if (model.getEndpointOperation().isPresent()) {
builder.beginControlFlow("if (!endpointDiscoveryEnabled)")
.addStatement("endpointDiscoveryEnabled = CHAIN.resolveEndpointDiscovery()")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2020 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.protocols.query.interceptor;

import static java.util.Collections.singletonList;
import static software.amazon.awssdk.utils.StringUtils.lowerCase;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.core.interceptor.Context;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.utils.CollectionUtils;
import software.amazon.awssdk.utils.http.SdkHttpUtils;

/**
* Modifies an HTTP request by moving query parameters to the body under the following conditions:
* - It is a POST request
* - There is no content stream provider
* - There are query parameters to transfer
* <p>
* This interceptor is automatically inserted by codegen for services using Query Protocol
*/
@SdkProtectedApi
public final class QueryParametersToBodyInterceptor implements ExecutionInterceptor {

private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=" +
lowerCase(StandardCharsets.UTF_8.toString());

@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
ExecutionAttributes executionAttributes) {

SdkHttpRequest httpRequest = context.httpRequest();

if (!(httpRequest instanceof SdkHttpFullRequest)) {
return httpRequest;
}

SdkHttpFullRequest httpFullRequest = (SdkHttpFullRequest) httpRequest;
if (shouldPutParamsInBody(httpFullRequest)) {
return changeQueryParametersToFormData(httpFullRequest);
}
return httpFullRequest;
}

private boolean shouldPutParamsInBody(SdkHttpFullRequest input) {
return input.method() == SdkHttpMethod.POST &&
!input.contentStreamProvider().isPresent() &&
!CollectionUtils.isNullOrEmpty(input.rawQueryParameters());
}

private SdkHttpRequest changeQueryParametersToFormData(SdkHttpFullRequest input) {
byte[] params = SdkHttpUtils.encodeAndFlattenFormData(input.rawQueryParameters()).orElse("")
.getBytes(StandardCharsets.UTF_8);

return input.toBuilder().clearQueryParameters()
.contentStreamProvider(() -> new ByteArrayInputStream(params))
.putHeader("Content-Length", singletonList(String.valueOf(params.length)))
.putHeader("Content-Type", singletonList(DEFAULT_CONTENT_TYPE))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2010-2020 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.protocols.query.interceptor;

import org.junit.Before;
import org.junit.Test;
import software.amazon.awssdk.core.Protocol;
import software.amazon.awssdk.core.SdkRequest;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.http.ContentStreamProvider;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.utils.IoUtils;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.stream.Stream;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;

public class QueryParametersToBodyInterceptorTest {

public static final URI HTTP_LOCALHOST = URI.create("http://localhost:8080");

private QueryParametersToBodyInterceptor interceptor;
private ExecutionAttributes executionAttributes;

private SdkHttpFullRequest.Builder requestBuilder;

@Before
public void setup() {

interceptor = new QueryParametersToBodyInterceptor();
executionAttributes = new ExecutionAttributes();

requestBuilder = SdkHttpFullRequest.builder()
.protocol(Protocol.HTTPS.toString())
.method(SdkHttpMethod.POST)
.putRawQueryParameter("key", singletonList("value"))
.uri(HTTP_LOCALHOST);
}

@Test
public void postRequestsWithNoBodyHaveTheirParametersMovedToTheBody() throws Exception {

SdkHttpFullRequest request = requestBuilder.build();

SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
new HttpRequestOnlyContext(request, null), executionAttributes);

assertThat(output.rawQueryParameters()).hasSize(0);
assertThat(output.headers())
.containsKey("Content-Length")
.containsEntry("Content-Type", singletonList("application/x-www-form-urlencoded; charset=utf-8"));
assertThat(output.contentStreamProvider()).isNotEmpty();
}

@Test
public void nonPostRequestsWithNoBodyAreUnaltered() throws Exception {
Stream.of(SdkHttpMethod.values())
.filter(m -> !m.equals(SdkHttpMethod.POST))
.forEach(this::nonPostRequestsUnaltered);
}

@Test
public void postWithContentIsUnaltered() throws Exception {
byte[] contentBytes = "hello".getBytes(StandardCharsets.UTF_8);
ContentStreamProvider contentProvider = () -> new ByteArrayInputStream(contentBytes);

SdkHttpFullRequest request = requestBuilder.contentStreamProvider(contentProvider).build();

SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
new HttpRequestOnlyContext(request, null), executionAttributes);

assertThat(output.rawQueryParameters()).hasSize(1);
assertThat(output.headers()).hasSize(0);
assertThat(IoUtils.toByteArray(output.contentStreamProvider().get().newStream())).isEqualTo(contentBytes);
}

@Test
public void onlyAlterRequestsIfParamsArePresent() throws Exception {
SdkHttpFullRequest request = requestBuilder.clearQueryParameters().build();

SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
new HttpRequestOnlyContext(request, null), executionAttributes);

assertThat(output.rawQueryParameters()).hasSize(0);
assertThat(output.headers()).hasSize(0);
assertThat(output.contentStreamProvider()).isEmpty();
}

private void nonPostRequestsUnaltered(SdkHttpMethod method) {

SdkHttpFullRequest request = requestBuilder.method(method).build();

SdkHttpFullRequest output = (SdkHttpFullRequest) interceptor.modifyHttpRequest(
new HttpRequestOnlyContext(request, null), executionAttributes);

assertThat(output.rawQueryParameters()).hasSize(1);
assertThat(output.headers()).hasSize(0);
assertThat(output.contentStreamProvider()).isEmpty();
}

public final class HttpRequestOnlyContext implements software.amazon.awssdk.core.interceptor.Context.ModifyHttpRequest {

private final SdkHttpRequest request;
private final RequestBody requestBody;

public HttpRequestOnlyContext(SdkHttpRequest request,
RequestBody requestBody) {
this.request = request;
this.requestBody = requestBody;
}

@Override
public SdkRequest request() {
return null;
}

@Override
public SdkHttpRequest httpRequest() {
return request;
}

@Override
public Optional<RequestBody> requestBody() {
return Optional.ofNullable(requestBody);
}

@Override
public Optional<AsyncRequestBody> asyncRequestBody() {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestMutableStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomHeadersStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomQueryParamsStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.MoveParametersToBodyStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.SigningStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.UnwrapResponseContainer;
import software.amazon.awssdk.core.internal.retry.SdkDefaultRetrySetting;
Expand Down Expand Up @@ -178,7 +177,6 @@ public <OutputT> CompletableFuture<OutputT> execute(
.then(ApplyUserAgentStage::new)
.then(MergeCustomHeadersStage::new)
.then(MergeCustomQueryParamsStage::new)
.then(MoveParametersToBodyStage::new)
.then(MakeRequestImmutableStage::new)
.then(RequestPipelineBuilder
.first(SigningStage::new)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestMutableStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomHeadersStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomQueryParamsStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.MoveParametersToBodyStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.SigningStage;
import software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage;
Expand Down Expand Up @@ -178,7 +177,6 @@ public <OutputT> OutputT execute(HttpResponseHandler<Response<OutputT>> response
.then(ApplyUserAgentStage::new)
.then(MergeCustomHeadersStage::new)
.then(MergeCustomQueryParamsStage::new)
.then(MoveParametersToBodyStage::new)
.then(MakeRequestImmutableStage::new)
// End of mutating request
.then(RequestPipelineBuilder
Expand Down

This file was deleted.

Loading

0 comments on commit 308c95a

Please sign in to comment.