outofcoffee / imposter

Scriptable, multipurpose mock server. Run standalone mock servers, or embed mocks within your tests.
https://imposter.sh
Other
364 stars 59 forks source link

AWS Lambda returns 500 instead of 404 for mocked endpoint #633

Closed mrtnhwttktc closed 21 hours ago

mrtnhwttktc commented 1 day ago

When using Imposter on AWS Lambda, creating a mock of an endpoint that should return a status code 404 results in a status code 500 instead.

Expected Behavior

The endpoint should return a 404 status code with the specified JSON content.

Actual Behavior

The endpoint returns a 500 status code with the following response:

{
  "statusCode": 500,
  "isBase64Encoded": false
}

Steps to Reproduce

  1. Deploy a Lambda function following the steps in the documentation.
  2. Use the following configuration files in the S3 bucket at the config directory path:

demo-config.yaml:

plugin: openapi
resources:
- method: GET
  path: /demo
  response:
    statusCode: 200
- method: GET
  path: /notfound
  response:
    statusCode: 404
specFile: demo.yaml

demo.yaml:

openapi: 3.0.0
info:
  title: Demo
  version: 1.0.0
paths:
  /demo:
    get:
      summary: get Demo
      parameters:
        - name: number
          in: query
          required: true
          schema:
            type: integer
          description: demo hello world
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              example:
                hello: world!
  /notfound:
    get:
      summary: testing not found response
      responses:
        "404":
          description: not found
          content:
            application/json:
              example:
                code: 4
                message: Could not find target data table_name.

Environment Details

Logs

Looking at the logs, it seems that Imposter was initially able to generate the mock with a 404 status code, but after closing/ending the response encountered an error and then sent a status code 500 instead.

DEBUG logs:

START RequestId: 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc Version: $LATEST
2024-10-01 00:33:44 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc INFO  i.g.i.a.AbstractHandler - Received request: GET /notfound
2024-10-01 00:33:44  DEBUG i.g.i.p.o.OpenApiPluginImpl - Setting content type [application/json] from specification for GET http://0.0.0.0/notfound
2024-10-01 00:33:44  WARN  i.g.i.s.ResponseServiceImpl - Response file and data are blank for [56e83707-3cad-4b13-a46d-0ee9ebb6afc9] GET http://0.0.0.0/notfound
2024-10-01 00:33:44  DEBUG i.g.i.p.o.s.ExampleServiceImpl - No exact match found for accepted content types - choosing first item found (application/json) from specification. You can switch off this behaviour by setting configuration option 'pickFirstIfNoneMatch: false'
2024-10-01 00:33:44  WARN  i.g.i.p.o.s.ResponseTransmissionServiceImpl - Unsupported example type 'class com.fasterxml.jackson.databind.node.ObjectNode' - attempting String conversion
2024-10-01 00:33:44  INFO  i.g.i.p.o.s.ResponseTransmissionServiceImpl - Serving mock example for GET http://0.0.0.0/notfound with status code 404 (response body 61 bytes)
2024-10-01 00:33:44 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc WARN  i.g.i.s.HandlerServiceImpl - File not found: GET http://0.0.0.0/notfound
2024-10-01 00:33:44 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc ERROR i.g.i.a.AbstractHandler - java.lang.IllegalStateException: Response already ended or closed
2024-10-01 00:33:44 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc INFO  i.g.i.a.AbstractHandler - Sending response: [statusCode=500,body=<null>]
END RequestId: 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc
REPORT RequestId: 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc  Duration: 25252.98 ms   Billed Duration: 25253 ms   Memory Size: 768 MB Max Memory Used: 279 MB 

I also have logs with trace enabled but couldn't include them because they are too long but if needed I can send them, let me know.

I looked around in the code a little and found this function which seems to be called and causing the issue:

    override fun buildNotFoundExceptionHandler() = { httpExchange: HttpExchange ->
        if (
            null == httpExchange.get(ResourceUtil.RC_REQUEST_ID_KEY) ||
            httpExchange.get<Boolean>(ResourceUtil.RC_SEND_NOT_FOUND_RESPONSE) == true
        ) {
            // only override response processing if the 404 did not originate from the mock engine
            logAppropriatelyForPath(httpExchange, "File not found")
            responseService.sendNotFoundResponse(httpExchange)
        }

        // print summary
        LogUtil.logCompletion(httpExchange)
    }

It seems to match the warning I am seeing in the logs right after generating the correct 404 status code mock.

2024-10-01 00:33:44  INFO  i.g.i.p.o.s.ResponseTransmissionServiceImpl - Serving mock example for GET http://0.0.0.0/notfound with status code 404 (response body 61 bytes)
2024-10-01 00:33:44 7e24ab0e-66b7-4023-b95c-dbb9bf0a19cc WARN  i.g.i.s.HandlerServiceImpl - File not found: GET http://0.0.0.0/notfound

From the comment, it looks like the status code 404 is mistakenly seen as not originating from the mock engine? Calling responseService.sendNotFoundResponse(httpExchange) then triggers the IllegalStateException in HttpResponse because the response should already be marked as finished.

Looking at the logs the RequestId seems to be set so it shouldn't be null, so I was thinking that the response.sendNotFoundResponse from ResourceUtil.RC_SEND_NOT_FOUND_RESPONSE is incorrectly set to true somewhere along the way.

I looked around in the codebase but do not know kotlin/java well so I had trouble finding exactly what is happening.

I also tested using the imposter CLI and the error does not happen while using the same config file so I think the issue is exclusive to AWS Lambda.

Let me know if there is any other details I could provide. I am new to Kotlin but I will be trying to build it with some changes to see if I can find out what is causing the issue, if I am successful and find anything relevant I will update this issue.

outofcoffee commented 1 day ago

Thanks @mrtnhwttktc - I can reproduce this. It looks like the 404 is being caught by the default 'not found' handler in the Lambda implementation, which is trying to handle a request that's already been responded to. This'll need a new release to fix, once we have one.

outofcoffee commented 1 day ago

This should be fixed in release 4.0.4 (a delightful coincidence!)

mrtnhwttktc commented 21 hours ago

I just tested v4.0.4 (nice) and confirmed the fix works as intended! Thank you for fixing this so quickly! Closing this issue.