0

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.
Marco Luzzara
  • 5,540
  • 3
  • 16
  • 42
  • Explicit Status Code: When setting up integration responses in API Gateway (especially when dealing with Lambda integrations), you may also need to ensure that your Lambda function explicitly returns the desired status code in the response format that API Gateway expects. – Piyush Patil Aug 19 '23 at 10:47
  • @PiyushPatil Thanks for the comment. Could you please elaborate it? Where did you find this requirement? [This page](https://docs.aws.amazon.com/apigateway/latest/developerguide/handle-errors-in-lambda-integration.html) does not mention anything about it – Marco Luzzara Aug 19 '23 at 13:54
  • Check this https://stackoverflow.com/questions/31329495/is-there-a-way-to-change-the-http-status-codes-returned-by-amazon-api-gateway – Piyush Patil Aug 19 '23 at 13:56
  • @PiyushPatil I have read the answers but I am not using Lambda Proxy Integration. For the custom Lambda integration, the response body for an error includes `errorMessage`, `errorType` and `stackTrace`. Then I have the selection pattern to control the status code, otherwise the `put-method-response` and `put-integration-response` commands would be useless. Am i missing something? – Marco Luzzara Aug 19 '23 at 15:47

2 Answers2

0

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.

d1sh4
  • 1,710
  • 5
  • 21
  • Thanks for the answer. I never faked the response, I am using spring cloud function (AWS adapter) to handle that part. The HTTP response is generated by that library after I throw an exception. I am basically asking if it is impossible to use this integration with spring cloud function, or Localstack is missing support on selection pattern, or is it just me that I misconfigured the integration. – Marco Luzzara Aug 27 '23 at 16:07
  • The selection pattern should be correct, I have even tried with perfect matching but it does not work. I am using this pattern because I was worried the matched string would include something else aside from the `No value present` string. – Marco Luzzara Aug 27 '23 at 16:08
0

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.

Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • thanks for the answer. I completely missed that header, but I am still quite confused on why errors are handled in this way. What is the recommended way to handle errors with Api gateway and lambda? Eventually, how can I map an exception to a status code with api gateway? I am using Spring cloud function (AWS adapter), but if you have never used it, then an example with `RequestHandler` would be accepted anyway. Thanks again – Marco Luzzara Aug 27 '23 at 15:59
  • I am perfectly aware that testing against a real Lambda is the best option, but with this demo I am checking the opposite. I am verifying whether Localstack is ready to be used for integration tests in my scenario. – Marco Luzzara Aug 27 '23 at 16:15
  • Unfortunately I am not that fluent in Java to give an advice on Spring framework. My point is there are many moving parts and I would recommend to test error handling in isolation - create a hello-world app which does nothing but raises an exception, run it in the cloud and confirm that the runtime handles the exception properly and return the error header to the API (with API Gateway test or cloudwatch). Then use the same example locally. If it still work, it must be Spring error handling, otherwise the local emulator is not quite compatible with the real thing. – Alex Blex Aug 27 '23 at 20:54
  • I tested my original code on AWS and it works. Does it mean that `X-Amz-Function-Error` is unnecessary? In the response I see `X-Amz-Function-Error=Unhandled`, but I have not set it. I would really like to know how things work under the hood. By the way, it seems Localstack is bugged for this type of integration. – Marco Luzzara Aug 28 '23 at 09:54
  • 1
    The "Unhandled" is hardcoded here https://github.com/localstack/localstack/blob/78b46628944eb2a6ace5ab5e2497da23748bd66f/localstack/services/lambda_/lambda_api.py#L1788, and the only way to pass the error header explicitly is by returning a dictionary instead of Response object. Not sure how to do it from Java / Spring tho. I'd raise it with localstack team. – Alex Blex Aug 28 '23 at 11:41
  • I already opened an issue a week ago for the selection pattern support, they are working on it. Thanks for your help, although I would have appreciated some additional insight on the lambda error handling. I am not going to accept the answer, but you deserve the reward. – Marco Luzzara Aug 28 '23 at 13:41