Skip to content

Async interceptor payload + S3 Checksums #823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 17, 2018

Conversation

spfink
Copy link
Contributor

@spfink spfink commented Nov 14, 2018

Exposes the ability to modify the AsyncRequestBody and AsyncResponseTransformer.

Currently this is done by adding two new interceptors.

For AsyncRequestBody:

default AsyncRequestBody modifyAsyncRequestBody(Context.ModifyRequest context,
                                                ExecutionAttributes executionAttributes) {
    return context.requestBody();
}

For AsyncResponseTransformer:

default AsyncResponseTransformer modifyAsyncResponseTransformer(Context.ModifyRequest context,
                                                                ExecutionAttributes executionAttributes) {
    return context.responseTransformer();
}

Possible other options include making modifyRequest return back a container object instead of an SdkRequest:

default SdkRequestContext modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) {
    return context.request();
}

Where SdkRequestContext contains SdkRequest, AsyncRequestBody and AsyncResponseTransformer.

Or we could introduce an AsyncExecutionInterceptor interface that again either has two methods as in the PR or one method with a container object for async.

In general, trying to avoid potential confusion of when these interceptors are valid and when the values will be populated. If we use the approach in the PR, users of the Sync client should not use the new interceptors. If we go with the container approach for modifyRequest, then the asyncRequestBody and asyncResponseTransformer will be null.

@millems
Copy link
Contributor

millems commented Nov 14, 2018

The sync payload is currently on the HTTP request, and interceptors modify the HTTP request payload by wrapping the contentStreamProvider in modifyHttpRequest.

Why can't we do the same for async?

@shorea
Copy link
Contributor

shorea commented Nov 14, 2018

I like the idea of having separate callbacks. I would change modifyHttpRequest to just return the headers and modify the content in a separate callback.

SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes);

InputStream modifyHttpContent(Context.ModifyHttpContent context, ExecutionAttributes executionAttributes);

AsyncRequestBody modifyAsyncHttpContent(Context.ModifyHttpContent context, ExecutionAttributes executionAttributes);

It would be better if we didn't add the thing being modified into the context until after the modify method so you would have something like this. (this would also allow composite interceptors since the thing you want to modify isn't in an immutable object).

SdkHttpRequest modifyHttpRequest(SdkHttpRequest toModify, Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes);

InputStream modifyHttpContent(InputStream toModify, Context.ModifyHttpContent context, ExecutionAttributes executionAttributes);

AsyncRequestBody modifyAsyncHttpContent(AsyncRequestBody toModify, Context.ModifyHttpContent context, ExecutionAttributes executionAttributes);

and then whatever stage comes after modifying http content could have an Either<InputStream, AsyncRequestBody> for further access to the content.

@spfink
Copy link
Contributor Author

spfink commented Nov 14, 2018

@millems, we certainly could wrap the SdkHttpContentPublisher and the TransformingAsyncResponseHandler. We would still need to either use a container object on modifyHttpRequest or expose separate methods in the interceptors.

@shorea, I do like the idea of being consistent and splitting out the sync content into a separate method as well.

@zoewangg
Copy link
Contributor

@shorea +1 on your first option. Like the idea of splitting sync and async modifyHttpContents methods.

Would it make sense to further split the Context?
Context.ModifyHttpContent and Context.ModifyAsyncHttpContent

For the second option, I feel it could be a bit confusing because the it can get the inputStream from both toModify and context.httpContent(), even though context.httpContent() would be null at that time, but the person who implements this method might not know.

InputStream modifyHttpContent(InputStream toModify, Context.ModifyHttpContent context, ExecutionAttributes executionAttributes);

@shorea
Copy link
Contributor

shorea commented Nov 14, 2018

So I was proposing not to have the content in the context at all until after that modifyHttpContent stage, so it's only in one location (the parameter in modifyHttpContent and the next context object in future callbacks).

@millems
Copy link
Contributor

millems commented Nov 14, 2018

As long as sync and async are consistent, I don't mind too much.

  1. If we're going to have sync payloads on the HTTP request, we should have async payloads there as well.
  2. If we use AsyncRequestBody in the interceptor for async, we should use RequestBody for async.

@spfink
Copy link
Contributor Author

spfink commented Nov 15, 2018

Updated PR to include separate content interceptors.

@millems, not sure I follow your latest comment. Are you saying we should put the response transformer and request body on the SdkHttpFullRequest/Response objects?

@@ -204,6 +205,7 @@ protected SdkClientConfiguration finalizeChildConfiguration(SdkClientConfigurati
private SdkClientConfiguration finalizeSyncConfiguration(SdkClientConfiguration config) {
return config.toBuilder()
.option(SdkClientOption.SYNC_HTTP_CLIENT, resolveSyncHttpClient(config))
.option(SdkClientOption.CLIENT_TYPE, "Sync")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add.

@@ -86,6 +106,9 @@ private Context() {}
@ThreadSafe
@SdkPublicApi
public interface BeforeTransmission extends ModifyHttpRequest {
RequestBody requestBody();

AsyncRequestBody asyncRequestBody();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be in AfterMarshalling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not if we have them as separate arguments in interceptors.

@@ -105,15 +144,16 @@ private Context() {}
*/
@ThreadSafe
@SdkPublicApi
public interface ModifyHttpResponse extends AfterTransmission {
public interface ModifyHttpResponseContent extends ModifyAsyncHttpResponseContent {
Publisher responsePublisher();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No raw generics, please.
Shouldn't this be in AfterTransmision?
Where is the sync payload?


// Disable CS to avoid "Unused Import" error. If we use the FQCN in the Javadoc, we'll run into line length issues instead.
// CHECKSTYLE:OFF
// CHECKSTYLE:ON
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntelliJ moved these from where they should be around an import.

*/
default Publisher<ByteBuffer> modifyAsyncHttpResponseContent(Publisher<ByteBuffer> toModify,
Context.ModifyAsyncHttpResponseContent context,
ExecutionAttributes executionAttributes) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values to be modified aren't usually passed separately. Any reason we're changing that just for these?

.map(c -> invokeSafely(() -> IoUtils.toUtf8String(c)))
.orElse(null);

String policy = requestBody.contentStreamProvider() == null ? null : invokeSafely(() ->IoUtils.toUtf8String(requestBody.contentStreamProvider().newStream()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run checkstyle

Builder request(SdkHttpFullRequest request);
Builder request(SdkHttpRequest request);

Builder contentStreamProvider(Optional<ContentStreamProvider> contentStreamProvider);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't accept an optional parameter.

@@ -26,5 +26,5 @@
@SdkPublicApi
public interface InvokeableHttpRequest extends Callable<SdkHttpFullResponse>, Abortable {
@Override
SdkHttpFullResponse call() throws IOException;
SdkHttpFullResponse call() throws IOException; // ExecuteResponse - sdkResponse + inputStream
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping TODO

@millems
Copy link
Contributor

millems commented Nov 16, 2018

@zoewangg @shorea The problem with removing http request from the AfterMarshalling context and http response from the AfterTransmission context, is that it makes afterMarshalling and afterTransmission much less useful. Those methods exist to see those values, so removing them means there's not really any reason to use them (or any way to see the values before they're modified by other interceptors).

If we moved them to the first parameter of afterMarshalling (eg.) it would fix the problem, but three parameters for every method sounds like a lot...

Copy link
Contributor

@millems millems left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also update the execution interceptor protocol test to include the new methods?

* Implementation of {@link SdkChecksum} to calculate an MD5 checksum.
*/
@SdkInternalApi
public class MD5Checksum implements SdkChecksum {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Md5Checksum*

@@ -193,7 +198,18 @@ public void onError(Throwable error) {

@Override
public void onStream(Publisher<ByteBuffer> publisher) {
delegate.onStream(publisher);
Publisher newPublisher = context.interceptorChain().modifyAsyncHttpResponse(context.interceptorContext()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameterize. Can you turn on intellij warnings for this?


RequestBody requestBody();

AsyncRequestBody asyncRequestBody();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc. Should these be marked optional?

@@ -122,9 +122,9 @@ private HttpRequestBase wrapEntity(SdkHttpFullRequest request,
* preparation for the retry. Eventually, these wrappers would
* return incorrect validation result.
*/
if (request.contentStreamProvider().isPresent()) {
if (request.contentStreamProvider() != null && request.contentStreamProvider().isPresent()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any method that returns optional should never return null. Sounds like there's a bug somewhere upstream.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping - why is contentStreamProvider null?

private final SdkChecksum sdkChecksum;
private final int contentLength;

public ChecksumValidatingPublisher(Publisher publisher,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameterize.

@SdkInternalApi
public class ChecksumValidatingPublisher implements SdkPublisher<ByteBuffer> {

private final Publisher publisher;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameterize.

return this;
}

public Builder responsePublisher(Publisher responsePublisher) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameterize.

@@ -26,5 +26,5 @@
@SdkPublicApi
public interface InvokeableHttpRequest extends Callable<SdkHttpFullResponse>, Abortable {
@Override
SdkHttpFullResponse call() throws IOException;
SdkHttpFullResponse call() throws IOException; // ExecuteResponse - sdkResponse + inputStream
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping TODO

@@ -122,9 +122,9 @@ private HttpRequestBase wrapEntity(SdkHttpFullRequest request,
* preparation for the retry. Eventually, these wrappers would
* return incorrect validation result.
*/
if (request.contentStreamProvider().isPresent()) {
if (request.contentStreamProvider() != null && request.contentStreamProvider().isPresent()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping - why is contentStreamProvider null?

b.putHeader("Content-Length", contentLength);
}
}).build();
return (SdkHttpFullRequest) SdkHttpFullRequest.builder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this (and other similar casts) needed?

@@ -25,6 +25,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import org.apache.log4j.BasicConfigurator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import

@@ -136,7 +136,7 @@ private static JsonMarshallerRegistry createMarshallerRegistry() {
}

private SdkHttpFullRequest.Builder fillBasicRequestParams(OperationInfo operationInfo) {
return ProtocolUtils.createSdkHttpRequest(operationInfo, endpoint)
return (SdkHttpFullRequest.Builder) ProtocolUtils.createSdkHttpRequest(operationInfo, endpoint)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed cast?

@@ -26,5 +26,5 @@
@SdkPublicApi
public interface InvokeableHttpRequest extends Callable<SdkHttpFullResponse>, Abortable {
@Override
SdkHttpFullResponse call() throws IOException;
SdkHttpFullResponse call() throws IOException; // TODO: Change to container instad of SdkHttpFullResponse
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, can we actually do this?

@@ -416,7 +416,7 @@ private SdkHttpFullRequest validRequest() {
}

private SdkHttpFullRequest.Builder validRequestBuilder() {
return SdkHttpFullRequest.builder().protocol("http").host("localhost").method(SdkHttpMethod.GET);
return (SdkHttpFullRequest.Builder) SdkHttpFullRequest.builder().protocol("http").host("localhost").method(SdkHttpMethod.GET);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cast still needed?

b.putHeader("Content-Length", contentLength);
}
}).build();
return (SdkHttpFullRequest) SdkHttpFullRequest.builder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cast still needed?

result = result.copy(b -> b.httpRequest(interceptorResult)
.asyncRequestBody(asyncRequestBody)
.requestBody(requestBody)
.build());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build isn't needed

import software.amazon.awssdk.annotations.SdkPublicApi;

@SdkPublicApi
public class ExecuteResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably follow up with Andrew and see if we want to have a different name than ExecuteRequest and ExecuteResponse later.

@spfink spfink force-pushed the finks/async-interceptor-payload branch from e2949ea to ad39430 Compare November 16, 2018 22:19
@spfink spfink force-pushed the finks/async-interceptor-payload branch from ad39430 to 774b98b Compare November 17, 2018 01:11
@spfink spfink merged commit 236147d into master Nov 17, 2018
@shorea shorea deleted the finks/async-interceptor-payload branch November 23, 2018 03:54
aws-sdk-java-automation added a commit that referenced this pull request Apr 30, 2020
…04e9d0eb

Pull request: release <- staging/142eddbf-4255-4523-bc71-564f04e9d0eb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants