Skip to content

Support for Lambda Function URLs #460

Open

Description

@deki

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

Activity

  1. msailes commented on Apr 8, 2022

    @msailes

    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.

  2. deki commented on Apr 9, 2022

    @deki
    ContributorAuthor

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

  3. self-assigned this
    on Apr 9, 2022
  4. msailes commented on Apr 9, 2022

    @msailes

    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.

  5. VPatrny commented on Apr 27, 2022

    @VPatrny

    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);
    
  6. added a commit that references this issue on Apr 27, 2022
  7. astsiatsko commented on Jan 25, 2024

    @astsiatsko

    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?

  8. deki commented on Jan 25, 2024

    @deki
    ContributorAuthor

    @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...

  9. astsiatsko commented on Jan 25, 2024

    @astsiatsko

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

  10. astsiatsko commented on Jan 25, 2024

    @astsiatsko

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

  11. deki commented on Jan 25, 2024

    @deki
    ContributorAuthor

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

  12. astsiatsko commented on Jan 25, 2024

    @astsiatsko

    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?

  13. astsiatsko commented on Jan 25, 2024

    @astsiatsko

    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
        ) 
    
    
  14. astsiatsko commented on Jan 26, 2024

    @astsiatsko

    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

  15. astsiatsko commented on Jan 26, 2024

    @astsiatsko

    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"}

  16. deki commented on Jan 30, 2024

    @deki
    ContributorAuthor

    @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.

  17. astsiatsko commented on Jan 30, 2024

    @astsiatsko

    @deki Thank you
    I submitted #754

  18. olegz commented on Jan 30, 2024

    @olegz
    Collaborator

    @deki you can assign it to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions