aws / aws/aws-sdk-java-v2

Copy-free signing of Netty FullHttpRequest for lambda

Open
#1,573 1 comment 0 reactions 0 assignees View on GitHub
feature-request p3
Dominant language
Java
Stars
2.6k
Forks
1k
Avg merge
2d 9h
Merged PRs (30d)
51

Description

I work on a gateway-type application built in Netty which proxies http requests to aws lambda. The gateway app signs the inbound http request using the awssdk 2.x before sending to lambda with a Netty http client.

Here is the code snippet:
```
//
// 1. inbound FullHttpRequest. content is a Netty ByteBuf
//
ByteBuf content = request.content();

//
// 2. Convert Netty ByteBuf to a type InvokeRequest accepts
//

byte[] arr;
if (buffer.hasArray()) {
arr = buffer.array();
} else {
arr = new byte[buffer.readableBytes()];
buffer.getBytes(buffer.readerIndex(), arr);
}
SdkBytes sdkBytes = SdkBytes.fromByteArray(arr); // always copy :(
buffer.release(); // fromByteArray always copy => release ByteBuf

//
// 3. sign request with AWS SDK v2
//

InvokeRequest invokeRequest =
InvokeRequest.builder()
.functionName(functionName)
.invocationType(InvocationType.REQUEST_RESPONSE)
.qualifier(qualifier)
.payload(sdkBytes)
.build();

AwsJsonProtocolFactory protocolFactory =
AwsJsonProtocolFactory.builder()
.clientConfiguration(
SdkClientConfiguration.builder()
.option(SdkClientOption.ENDPOINT, endpointUri)
.build())
.build();

InvokeRequestMarshaller invokeRequestMarshaller = new InvokeRequestMarshaller(protocolFactory);
SdkHttpFullRequest marshalledRequest = invokeRequestMarshaller.marshall(invokeRequest);

// looks thread-safe. TODO: get Amazon to officially confirm & document
Aws4Signer aws4Signer = Aws4Signer.create();
SdkHttpFullRequest signedRequest =
aws4Signer.sign(
marshalledRequest,
Aws4SignerParams.builder()
.awsCredentials(CREDENTIALS)
.signingName("lambda")
.signingRegion(AWS_REGION)
.timeOffset(0)
.build());

//
// 3. convert AWS signed request back into a Netty FullHttpRequest
// Another piece of work here that requires copying the signed request InputStream into a Netty ByteBuf
//

FullHttpRequest req = toNettyRequest(signedRequest);
/*
* private FullHttpRequest toNettyRequest(SdkHttpFullRequest awsReq) throws IOException {
* ByteBuf content;
* Optional contentStreamProvider = awsReq.contentStreamProvider();
* // FIXME: copy InputStream directly into pooled ByteBuf
* if (contentStreamProvider.isPresent()) {
* byte[] contentBytes = IoUtils.toByteArray(contentStreamProvider.get().newStream());
* content = Unpooled.wrappedBuffer(contentBytes);
* } else {
* content = Unpooled.buffer(0);
* }
* ... etc.
*/

// 4. send signed FullHttpRequest to lambda with Netty client
myNettyClient.send(req);
```

With the current AWS SDK API, this implementation requires doing several copies of the request content ByteBuf/payload:
1. First to turn the Netty ByteBuf into a byte array
2. `SdkBytes` copies the byte array again
3. `SdkBytes` parent class (`BytesWrapper`) exposes methods that suggest more copies may happen depending on implementation of AWS SDK classes... (perhaps `ContentStreamProvider` is a copy of `SdkBytes`... I haven't checked...)
4. `ContentStreamProvider`'s `InputStream` is copied in chunks to a `byte[]` in `IoUtils.toByteArray`.
5. The `ByteArrayOutputStream` (in `IoUtils.toByteArray`) gets resized (which causes copies) as more space is needed. `InputStream.available()` method is not used due to lack of clarity from the Java+AWS API (will it always return the full size of the underlying stream in this case?)

**Request**: I'd like this entire flow to be copy free and be able to reuse my allocated pooled Netty ByteBufs.

If `InvokeRequest` could accept an InputStream instead of a `SdkBytes` for its payload method, this would already be a great help as I would be able to wrap the Netty ByteBuf in a `ByteBufInputStream`. Underlying implementation of the request marshaller and signer would also need to be copy-free.

Alternatively, if I could sign the Netty request content without needing to create a `SdkHttpFullRequest` and just get the additional headers I need to add to the Netty request, this would make this entire flow simpler and more efficient.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.