aws / serverless-java-container

A Java wrapper to run Spring, Spring Boot, Jersey, and other apps inside AWS Lambda.
https://aws.amazon.com/serverless/
Apache License 2.0
1.5k stars 560 forks source link

Support for Lambda Function URLs #460

Open deki opened 2 years ago

deki commented 2 years ago

Recently Lambda Function URLs were released: https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/

Those requests fail with: com.amazonaws.serverless.exceptions.InvalidRequestEventException: The incoming event is not a valid request from Amazon API Gateway or an Application Load Balancer

msailes commented 2 years ago

In AwsProxyHttpServletRequestReader

https://github.com/awslabs/aws-serverless-java-container/blob/6d88f503c02988cc947743bad78d965d8aa0b5a2/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java#L47-L49

The condition fails because when a request from a function url is mapped to a AwsProxyRequest type getHttpMethod() returns null.

deki commented 2 years ago

HttpApiV2ProxyHandler along with HttpApiV2ProxyRequest needs to be used, than it works fine. Will change docs and samples.

msailes commented 2 years ago

If I use the example request from the developer guide

{
// removed for brevity
  "requestContext": {
    "accountId": "123456789012",
    "apiId": "<urlid>",
    "authentication": null,
    "authorizer": {
        "iam": {
                "accessKey": "AKIA...",
                "accountId": "111122223333",
                "callerId": "AIDA...",
                "cognitoIdentity": null,
                "principalOrgId": null,
                "userArn": "arn:aws:iam::111122223333:user/example-user",
                "userId": "AIDA..."
        }
    },
    "domainName": "<url-id>.lambda-url.us-west-2.on.aws",
    "domainPrefix": "<url-id>",
    "http": {
      "method": "POST",
      "path": "/my/path",
      "protocol": "HTTP/1.1",
      "sourceIp": "123.123.123.123",
      "userAgent": "agent"
    },
// removed for brevity
}

Then I get the following error and a 502 http response.

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "authentication" (class com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequestContext), not marked as ignorable (11 known properties: "apiId", "domainPrefix", "accountId", "authorizer", "requestId", "timeEpoch", "time", "stage", "domainName", "routeKey", "http"])
 at [Source: (ByteArrayInputStream); line: 21, column: 27] (through reference chain: com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest["requestContext"]->com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequestContext["authentication"])

This is because HttpApiV2ProxyRequestContext is not ignoring fields that it doesn't recognise, in this case authentication.

To fix this we could add @JsonIgnoreProperties(ignoreUnknown = true) to the class so that Jackson ignores fields it doesn't recognise.

VPatrny commented 2 years ago

The fix mentioned above works for me. Thx!

public class StreamLambdaHandler implements RequestStreamHandler {
//    private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
    private static SpringBootLambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> handler;

    static {
        try {
//            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
            handler = SpringBootLambdaContainerHandler.getHttpApiV2ProxyHandler(Application.class);
astsiatsko commented 9 months ago

Hey guys, thanks for this change. But is it possible to have ONE handler for all types of events? ALB, API_GW, functional url and so on? So far I see the ways how handlers are initialized are way different and it seems that I have to keep 2X resources in lambda to keep 2 types of handlers in memory - one for ALB, API_GW and another for FURL. Is there a way to share a single handler for all types of events?

deki commented 9 months ago

@astsiatsko which framework are you using?

For Spring Boot 3 you have the possibility to use the new SpringDelegatingLambdaContainerHandler as described in https://github.com/aws/serverless-java-container/tree/main/samples/springboot3/alt-pet-store. This one supports both payload versions. More docs on this coming soon...

astsiatsko commented 9 months ago

@deki thanks! I am using spring boot 3.1.x. Thank you, I will give it a try and let you know asap.

astsiatsko commented 9 months ago

@deki One question - may I do sth like that? SpringDelegatingLambdaContainerHandler.getContainerConfig().setInitializationTimeout(timeout);

deki commented 9 months ago

This is currently not implemented for this handler type. Please open a new issue describing your requirement and someone will look into it.

astsiatsko commented 9 months ago

Hey guys. Sorry for asking but are you sure your approach works for complex urls with query and path parameters? I use SpringDelegatingLambdaContainerHandler in complex enterprise lambda function and on

mvc.service(httpServletRequest, httpServletResponse);

I constantly get error 400 and /error context path. I copied SpringDelegatingLambdaContainerHandler to the same package in my app and added logs. In my query I have two query params and path param. In aws logs I see it starts spring boot but I always get 400 and /error in logs instead of my url. I use ALB event, but it doesn't work for others either. Can you help please?

astsiatsko commented 9 months ago

My endpoint signature:

@RequestMapping(path = "/w6/test/{uuid}/refresh", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<TMaxmindResponse> refresh(
            @Parameter(description = "UUID of the order") @PathVariable("uuid") String uuid,
            @Parameter(description = "ID of the order(primary key)") @RequestParam("order_id") Long orderId,
            @Parameter(description = "tab") @RequestParam("tab") String tab
    ) 
astsiatsko commented 9 months ago

Hey guys. I think you have a problem with query parameters procession - I changed all my parameters to path params and it worked just fine. I debugged it in AWS Lambda for 2 days and gave it up, decided to move to path variables. But please, fix that or let me know what I am doing wrong. Thanks

astsiatsko commented 9 months ago

Here is the stack trace I have. May be it helps

jakarta.servlet.ServletException: Request processing failed: java.lang.NullPointerException: Cannot read the array length because \"\u003cparameter1\u003e\" is null\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1019)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc$ProxyFilterChain$ServletFilterProxy.doFilter(ProxyMvc.java:280)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc$ProxyFilterChain.doFilter(ProxyMvc.java:233)\n\tat org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:289)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc$ProxyFilterChain.doFilter(ProxyMvc.java:233)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc$ProxyFilterChain.doFilter(ProxyMvc.java:233)\n\tat org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc$ProxyFilterChain.doFilter(ProxyMvc.java:233)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc.service(ProxyMvc.java:144)\n\tat org.springframework.cloud.function.serverless.web.ProxyMvc.service(ProxyMvc.java:138)\n\tat com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler.handleRequest(SpringDelegatingLambdaContainerHandler.java:74)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.base/java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.amazonaws.services.lambda.runtime.api.client.EventHandlerLoader$StreamMethodRequestHandler.handleRequest(EventHandlerLoader.java:378)\n\tat com.amazonaws.services.lambda.runtime.api.client.EventHandlerLoader$2.call(EventHandlerLoader.java:905)\n\tat com.amazonaws.services.lambda.runtime.api.client.AWSLambda.startRuntime(AWSLambda.java:245)\n\tat com.amazonaws.services.lambda.runtime.api.client.AWSLambda.startRuntime(AWSLambda.java:197)\n\tat com.amazonaws.services.lambda.runtime.api.client.AWSLambda.main(AWSLambda.java:187)\nCaused by: java.lang.NullPointerException: Cannot read the array length because \"\u003cparameter1\u003e\" is null\n\tat java.base/java.io.ByteArrayInputStream.\u003cinit\u003e(Unknown Source)\n\tat org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest.getInputStream(ProxyHttpServletRequest.java:280)\n\tat org.springframework.web.util.ContentCachingRequestWrapper.getInputStream(ContentCachingRequestWrapper.java:98)\n\tat org.springframework.http.server.ServletServerHttpRequest.getBody(ServletServerHttpRequest.java:206)\n\tat org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver$EmptyBodyCheckingHttpInputMessage.\u003cinit\u003e(AbstractMessageConverterMethodArgumentResolver.java:323)\n\tat org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:172)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:163)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:136)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:181)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:148)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)\n\t... 27 more\n","timestamp":"2024-01-26 18:07:19.251"}

deki commented 9 months ago

@astsiatsko good catch, would you mind opening a new issue for it? This is unrelated to Lambda Function URLs. We'll include a fix in the next release.

astsiatsko commented 9 months ago

@deki Thank you I submitted https://github.com/aws/serverless-java-container/issues/754

olegz commented 9 months ago

@deki you can assign it to me