skip to Main Content

I am using Localstack with API Gateway and Lambda Integration to test my APIs. You can find the repository here.

The scripts I am using to build the emulated AWS environment are in this folder.


I am trying to create multiple integration responses for a single API, which is GET /users/{userId}. I have integrated the Lambda function with the API Gateway, the integration type is AWS. The issue is that the default response is always called (200), whereas I would like to return 404 when the user does not exist. It should return 404 when the lambda throws an exception with the message No value present (which is the message of a NoSuchElementException). Basically, I need two integration responses:

  • The default one (200)

    awslocal apigateway put-integration-response 
              --rest-api-id "$_REST_API_ID" 
              --resource-id "$_RESOURCE_ID" 
              --http-method "$_HTTP_METHOD" 
              --status-code 200 
              --selection-pattern ""
    

    I have also tried to remove --selection-pattern but it is the only one called anyway.

  • The one for the "no user with that id" case (404):

    awslocal apigateway put-integration-response 
              --rest-api-id "$_REST_API_ID" 
              --resource-id "$_RESOURCE_ID" 
              --http-method "$_HTTP_METHOD" 
              --status-code "404" 
              --selection-pattern ".*No value present.*"
    

    I have read that the selection pattern tries to match the errorMessage of the lambda response, but it does not seem the case.

This is the HTTP response I have intercepted with Wireshark:

GET /restapis/s3zoijp35e/test/_user_request_/users/1 HTTP/1.1
Connection: Upgrade, HTTP2-Settings
Content-Length: 0
Host: 127.0.0.1:49161
HTTP2-Settings: AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA
Upgrade: h2c
User-Agent: Java-http-client/17.0.7
Content-Type: application/json

HTTP/1.1 200 
Content-Type: text/plain; charset=utf-8
Content-Length: 952
Connection: close
date: Sat, 19 Aug 2023 08:40:32 GMT
server: hypercorn-h11

{"errorMessage":"No value present","errorType":"java.util.NoSuchElementException","stackTrace":["java.base/java.util.Optional.orElseThrow(Unknown Source)","it.unimi.cloudproject.ui.lambda.UserLambda.lambda$getUser$2(UserLambda.java:37)","org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeFunctionAndEnrichResultIfNecessary(SimpleFunctionRegistry.java:943)","org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeFunction(SimpleFunctionRegistry.java:889)","org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:734)","org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.apply(SimpleFunctionRegistry.java:577)","org.springframework.cloud.function.adapter.aws.FunctionInvoker.handleRequest(FunctionInvoker.java:91)"]}

This test breaks for that reason, showing also the lambda logs.


Even if I have linked the repository code, I don’t find necessary to explain the rest of the code because the error could come from:

  • Localstack, which does not correctly support the --selection-pattern option. I am pretty sure that it is at least partially supported because if I use the pattern .* with the 404 response, the gateway always chooses this one.
  • Me, because I am making some incorrect assumptions about the selection pattern matching.

2

Answers


  1. The recommended approach as per AWS Docs is to raise the exception within the application, which then propagates to the API gateway:

    The Lambda function must exit with an error in order for the response
    pattern to be evaluated – it is not possible to “fake” an error
    response by simply returning an “errorMessage” field in a successful
    Lambda response.

    The following general code is also provided:

    public class LambdaFunctionHandler implements RequestHandler<String, String> {
      @Override
        public String handleRequest(String input, Context context) {
    
            Map<String, Object> errorPayload = new HashMap();
            errorPayload.put("errorType", "BadRequest");
            errorPayload.put("httpStatus", 400);
            errorPayload.put("requestId", context.getAwsRequestId());
            errorPayload.put("message", "An unknown error has occurred. Please try again.");
            String message = new ObjectMapper().writeValueAsString(errorPayload);
            
            throw new RuntimeException(message);
        }
    }
    

    Separately, if you still want to achieve your goal by regex, I believe your selection-pattern may be incorrect – you are trying to match characters before and after No value present – try to just match the text.

    Login or Signup to reply.
  2. https://docs.aws.amazon.com/cli/latest/reference/apigateway/put-integration-response.html#output reads:

    selectionPattern -> (string)

    Specifies the regular expression (regex) pattern used to choose an integration response based on the response from the back end. For example, if the success response returns nothing and the error response returns some string, you could use the .+ regex to match error response. However, make sure that the error response does not contain any newline (n ) character in such cases. If the back end is an Lambda function, the Lambda function error header is matched. For all other HTTP and Amazon Web Services back ends, the HTTP status code is matched.

    Essentially the "No value present" message should be returned from the lambda function to API gateway in the X-Amz-Function-Error header, not the response body. Details are here: https://docs.aws.amazon.com/lambda/latest/dg/java-exceptions.html#java-exceptions-how

    It should be a standard Lambda runtime exception handling, but please keep in mind that localstack is just a shadow of the cloud. It may miss some implementational details, especially for edge cases. There were similar issues for local nodejs emulator https://github.com/aws/aws-lambda-runtime-interface-emulator/issues/20.

    I would strongly recommend to test it against real Lambda. It would cost you a fraction of a penny and can save you hundreds of man-hours. Otherwise you may end up with unnecessary overcomplicated application architecture to workaround problems with dev environment – something that does not affect production system at the first place.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search