简体   繁体   中英

Handling AWS Lambda errors with API Gateway

I have class BadRequest(Exception): pass in my Lambda function.

I'd like to raise BadRequest("Invalid request params") and have the API return a response with status code 400 and body { "message": "Invalid request params" } (or equivalent).

Simply doing that however returns a response with status code 200 (oh no!) and body

{
    "errorMessage": "Invalid request params",
    "errorType": "BadRequest",
    "stackTrace": <my application code that the user shouldnt see>
}

After searching around online, it seems I have 3 options:

1) chalice

2) Use integration response and method response to parse that error into a better response. I would regex like [BadRequest].* and insert a prefix when I throw the exception (not very elegant IMO).

3) Use Step Functions to create a stateful representation of the API. This seems a little tedious because I would need to learn ASL and I don't know any deaf people. -.-

-.- amazon states language


Which rabbit hole should I go down and why?

I am returning to this question 3 years later to describe how I solve this problem today. I use the Serverless Framework to deploy my lambda functions and an API gateway.

I use a decorator that catches exceptions and returns a payload. For example, here is a successful request, an expected exception, and an unexpected exception.

def my_successful_request(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({"success": True})
    }


def handle_exceptions(f):
    def deco(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except BadRequest as e:
            print(e)
            return {"statusCode": 400, "body": json.dumps({"message": str(e)})}
        except Exception as e:
            print(e)
            return {"statusCode": 500, "body": json.dumps({"message": "unexpected error"})}
    return deco

@handle_exceptions
def my_expected_error_request(event, context):
    raise BadRequest("This function raises a BadRequest with a 400 status code that should be sent to the user. The end user can read this text.")

@handle_exceptions
def my_unexpected_error_request(event, context):
    raise Exception("Uh oh. I didn't expect this. A 500 error with an obfuscated message is raised. The end user cannot read this text.")

This pattern makes it very easy for an API to return the appropriate error message and status code . I have very rudimentary logging in this handle_exceptions implementation but you can get really detailed messages with f.__name__ to know what Lambda function erred and the traceback module to understand the source of the exception. All this error management is completely hidden from the API user.

Chalice makes implementing REST APIs with Lambda and API Gateway pretty simple, including transforming raised exceptions into responses. For your particular case, you'd raise an exception like this:

import chalice
app = chalice.Chalice(app_name='your-app')
app.debug = True  # Includes stack trace in response. Set to False for production.

@app.route('/api', methods=['GET'])
def your_api():
    raise chalice.BadRequestError("Your error message")

There's a complete working example of a REST API that uses Chalice with Lambda and API Gateway on GitHub: aws-doc-sdk-examples .

You should catch the Exception in Lambda and throw custom Exception as below.

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", "Invalid request params " + stackstace);
        String message = new ObjectMapper().writeValueAsString(errorPayload);

        throw new RuntimeException(message);
    }
}

And then use Option 2  to map the error code .

Integration response:
Selection pattern: “.*"BadRequest".*”

Method response: 500

Mapping template:

#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))
{
  "type" : "$errorMessageObj.errorType",
  "message" : "$errorMessageObj.message",
  "request-id" : "$errorMessageObj.requestId"
}

This is a perfect use case for AWS Step Functions. You would need to set up the API Gateway to directly call a state machine that you will create.

Here's the ASL for the aforementioned state machine:

{
  "Comment": "A state machine that executes my lambda function and catches the bad error.",
  "StartAt": "MyLambda",
  "States": {
    "MyLambda": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Catch": [
        {
          "ErrorEquals": ["BadError"],
          "Next": "BadErrorFallback"
        }
      ],
      "End": true
    },
    "BadErrorFallback": {
      "Type": "Pass",
      "Result": "Put here whatever is the result that you want to return.",
      "End": true
    }
  }
}

What this will do is run your provided lambda functions. If the lambda function throws BadError, then it will output the result of the BadErrorFallback state. Otherwise it will output whatever the lambda functions spits out.

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM