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
The recommended approach as per AWS Docs is to raise the exception within the application, which then propagates to the API gateway:
The following general code is also provided:
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 afterNo value present
– try to just match the text.https://docs.aws.amazon.com/cli/latest/reference/apigateway/put-integration-response.html#output reads:
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-howIt 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.