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> 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:
- First to turn the Netty ByteBuf into a byte array
SdkBytes
copies the byte array againSdkBytes
parent class (BytesWrapper
) exposes methods that suggest more copies may happen depending on implementation of AWS SDK classes... (perhapsContentStreamProvider
is a copy ofSdkBytes
... I haven't checked...)ContentStreamProvider
'sInputStream
is copied in chunks to abyte[]
inIoUtils.toByteArray
.- The
ByteArrayOutputStream
(inIoUtils.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.