aws / aws/aws-lambda-java-libs

Custom Serialization feature for handler class that implements RequestHandler and uses method name handler syntax does not use exact types during serialization

Aberta
#552 0 comentários 2 reações 0 responsáveis Ver no GitHub
Linguagem predominante
Java
Estrelas
548
Forks
241
Merge médio
2d 5min
PRs com merge (30d)
11

Descrição

The [custom serialization](https://docs.aws.amazon.com/lambda/latest/dg/java-custom-serialization.html) feature does not use the exact types of type parameters in the presence of inheritance when (a) the implementation class implements `RequestHandler`; and (b) the configured handler uses method name syntax (as opposed to class name syntax, so, `com.example.ExampleLambdaFunction#requestHandler`).

For example, consider the below Lambda implementation with the given custom serializer. Note that the `ExampleLambdaFunction` is the actual handler class, and it extends `LambdaFunctionBase`, which implements `RequestHandler` and uses generic type parameters for the `handleRequest` method parameters and result.

package com.example;

public abstract class LambdaFunctionBase
implements RequestHandler {
@Override
public OutputT handleRequest(InputT input, Context context) {
System.out.println("Enter handleRequest");
OutputT result = doHandleRequest(input, context);
System.out.println("Exit handleRequest");
return result;
}

protected abstract OutputT doHandleRequest(InputT input, Context context);
}

public class ExampleLambdaFunction
extends LambdaFunctionBase {
@Override
public ExampleResponse doHandleRequest(ExampleRequest request) {
ExampleResponse result;
result = businessLogic(request);
return result;
}
}

public class ExampleCustomPojoSerializer
implements CustomPojoSerializer {
@Override
public T fromJson(InputStream input, Type type) {
System.err.println("fromJson(" + type + ")");
return deserializeImplementation(input, type);
}

@Override
public T fromJson(String input, Type type) {
System.err.println("fromJson(" + type + ")");
return deserializeImplementation(input, type);
}

@Override
public void toJson(T value, OutputStream output, Type type) {
System.err.println("toJson(" + type + ")");
return serializeImplementation(value, output, type);
}
}

If the configured handler looks like `com.example.ExampleLambdaFunction`, then the result of creating, deploying, and invoking the lambda would produce the following output on stderr:

fromJson(ExampleRequest)
toJson(ExampleResponse)

So everything works as expected.

However, if the configured handler looks like `com.example.ExampleLambdaFunction::handleRequest`, then the result is different:

fromJson(InputT)
toJson(OutputT) // <-- Assuming serialization succeeded, and the lambda completed successfully

Having poked around in the code a bit, it looks like the difference is due to code in aws-lambda-java-runtime-interface-client. If we look at [`EventHandler#getHandlerFromOverload(Class,Method)`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L415), we see that it creates the `LambdaRequestHandler` from the literal return and parameter types of the given method. However, we know that in this example, those are going to be generic type parameters. It needs to use [the exact return type of the method](https://github.com/leangen/geantyref?tab=readme-ov-file#getting-the-exact-return-type-of-a-method) and [the exact parameter types of the method](https://github.com/leangen/geantyref?tab=readme-ov-file#getting-the-exact-types-of-method-parameters) (examples linked from the excellent [https://github.com/leangen/geantyref](geantyref) library, purely for illustration and reference).

The call stack looks like this when the error occurs:

EventHandlerLoader::loadEventHandler // <-- Branch in this method is why handler syntax matters
EventHandlerLoader::loadEventPojoHandler
EventHandlerLoader::getHandlerFromOverload // <-- Error is here

The feature works as expected when the handler looks like `com.example.ExampleLambdaFunction` because in [`EventHandlerLoader#loadEventHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L336), control flows through [`EventHandler.loadStreamingRequestHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L325), which calls through methods to handle generic types, i.e., [`EventHandlerLoader#wrapPojoHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L522).

The feature does not work as expected when the handler looks like `com.example.ExampleLambdaFunction::handleRequest` because in [`EventHandlerLoader#loadEventHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L336), control flows through [`EventHandler.loadEventPojoHandler`](https://github.com/aws/aws-lambda-java-libs/blob/150b1b73bfad950cce9948ff836d80d80aa3dc1f/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java#L452), which uses the literal types of the indicated method, as noted above.

It's not clear whether or not this is a "bug", per se. Based on the code, it seems like the intent is that developers use `com.example.ExampleLambdaFunction` syntax when their implementation inherits from `RequestHandler`, and `com.example.ExampleLambdaFunction::methodName` when it doesn't. On the one hand, method name syntax should always work as expected. On the other hand, if the user uses method name syntax for a class that inherits from RequestHandler, it's "belt and suspenders" at best, and potentially confusing at worst.

In any case, I lost some time debugging my custom serializer due to this issue, so I thought I'd write it up. Fortunately, there should be a pretty simple fix, if the team agrees that this is a bug. (I'd be happy to contribute a patch!) Otherwise, a line in the documentation like "If your class implements RequestHandler, then use `com.example.ExampleLambdaFunction` syntax; otherwise, use `com.example.ExampleLambdaFunction::methodName` syntax. If your RequestHandler implementation uses method name syntax, you might get unexpected behavior in some cases." might be useful to others. (Please ignore if that documentation already exists! I looked, but didn't find it.)

Please let me know if you have any questions, or if I can help in any way!

Guia de contribuição

Abrir o guia de contribuição

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.