Search code examples
amazon-web-servicesaws-lambdacorsaws-api-gatewayspring-cloud-function

Spring Cloud Function AWS Lambda with proxy integration on APIGW : CORS issue


We have created our AWS Lambda function using Spring Cloud function. This function returns APIGatewayProxyResponseEvent response. Sample below

{
    "statusCode": 200,
    "headers": {
        "Access-Control-Expose-Headers": "Access-Control-Allow-Methods,Access-Control-Allow-Origin",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET",
        "Access-Control-Max-Age": "200",
        "Access-Control-Allow-Headers": "Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers",
        "Content-Type": "application/json"
    },
    "multiValueHeaders": null,
    "body": "response Data json Body",
    "isBase64Encoded": false
}

APIGW uses Lambda proxy integration , and hence there is no option for response mappings. We have enabled CORS by using Actions on the console. This automatically adds the OPTIONS method where we have configured the 200 response with below headers

Access-Control-Max-Age          :   '200'   
Access-Control-Allow-Headers    :   'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'  
Access-Control-Expose-Headers   :   'Access-Control-Allow-Methods,Access-Control-Allow-Origin'  
Access-Control-Allow-Origin     :   '*' 
Access-Control-Allow-Methods    :   'GET,OPTIONS'

The above steps are in sync with the AWS documentation AWS - How to CORS Lambda proxy

We deployed the API in a stage and are able to access it via Postman. On accessing from our web-application, which is currently on localhost we get CORS error.

In Network tab its visible that the preflight request (OPTIONS) returns 200 OK and the required CORS headers. However the actual GET call still fails , saying "CORS Error".

The issue is that APIGW is not copying the headers returned in the APIGatewayProxyResponseEvent object to final APIGW Response headers

Is this a known issue or am I missing something

Edit

Screen shot of APIGW lambda proxy

enter image description here

Screen shot from APIGW response (Testing from console)

enter image description here

Network tab in browser developer options showing preflight request successful

enter image description here

Edit2

Adding Console output enter image description here


Solution

  • Edit 1

    On checking the spring cloud milestone release, This issue has been addressed starting 3.2.0-M1 . (Currently available release is 3.1.5). Once this is released my previous approach of sending APIGatewayProxyResponseEvent as output will work just fine.

    @Oleg Zhurakousky can confirm

    Original Answer below : (almost a work around)

    Got help from AWS support and understood that the response being returned from the Spring Cloud function was being modified. This in turn resulted in all the headers being encapsulated as part of Lambda response body.

    My previous implementation of the function was

     @Bean
        public Function<APIGatewayProxyRequestEvent,APIGatewayProxyResponseEvent> testFunc2(){
            return event -> {
                System.out.println(event);
                APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
                Map<String,String> hdr = new HashMap<>();
                hdr.put("Access-Control-Allow-Origin","*");
                response.setHeaders(hdr);
                response.setStatusCode(HttpStatus.OK.value());
                response.setBody("Hello World!");
                return response;
            };
        }
    

    I had to change it to below to make sure the headers are treated as http headers and not a part of the lambda response body

    @Bean
        public Function<APIGatewayProxyRequestEvent, Message<APIGatewayProxyResponseEvent>> testFunc3(){
            return event -> {
                System.out.println(event);
                APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
                Map<String,Object> hdr = new HashMap<>();
                hdr.put("Access-Control-Allow-Origin","*");
                response.setStatusCode(HttpStatus.OK.value());
                response.setBody("Hello World!");
                Message<APIGatewayProxyResponseEvent> finalResponse = new GenericMessage<APIGatewayProxyResponseEvent>(response,hdr);
                System.out.println("Response prepared " +response);
                System.out.println("Final Response being returned " +finalResponse);
                return finalResponse;
            };
        }
    

    The actual entry point into Spring cloud function is org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest

    Here while preparing response spring takes the returned value from the function as Message (org.springframework.messaging) Payload.

    Hence In order to set http headers we need to return a Message<APIGatewayProxyResponseEvent> instead of APIGatewayProxyResponseEvent. Here we explicitly set our http headers in Message headers.