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

Abierto
#552 0 comentarios 2 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Java
Estrellas
548
Forks
241
Merge medio
2 d 5 min
PR fusionados (30 d)
11

Descripción

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!

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.