Gateway + Lambda + Python API: exception handling

I am invoking the Python-based AWS Lambda method from the non-proxy API gateway. How to handle exceptions correctly, so that the corresponding HTTP status code is set along with the JSON body using the exception parts.

As an example, I have the following handler:

def my_handler(event, context): try: s3conn.head_object(Bucket='my_bucket', Key='my_filename') except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "404": raise ClientException("Key '{}' not found".format(filename)) # or: return "Key '{}' not found".format(filename) ? class ClientException(Exception): pass 

Should I make an exception or return a string? Then how to configure the integration answer? Obviously I have RTFM, but FM is FU.

+5
source share
1 answer

TL; DR

  • Your Lambda handler should throw an exception if you want to respond with non-200.
  • Catch all exceptions in the handler method. Format the caught exception message in JSON and enter Exception as a custom type.
  • Use the Integration Response response to regex your custom Exception found in the errorMessage field of the Lambda response.

API Gateway API + AWS Lambda Exception Handling

There are many things you need to know about Lambda, the Gateway API, and how they work together.

Lambda exceptions

When an exception is thrown from the handler / function / method, the exception is serialized into a JSON message. From your sample code, on 404 with S3, your code will throw:

 { "stackTrace": [ [ "/var/task/mycode.py", 118, "my_handler", "raise ClientException(\"Key '{}' not found \".format(filename))" ] ], "errorType": "ClientException", "errorMessage": "Key 'my_filename' not found" } 

API Gateway Integration Response

Overview

Integration Responses maps Lambda responses to HTTP codes. They also allow you to change the body of the message as they pass.

By default, the โ€œ200โ€ Integration Response response is configured for you, which passes all the responses from Lambda back to the client as it is, including serialized JSON exceptions, as an HTTP 200 (OK) response.

For good messages, you can use the โ€œ200โ€ Integration Response response to map the JSON payload to one of your specific models.

Exception exceptions

For exceptions, you want to set the appropriate HTTP status code and possibly remove the stack to hide the insides of your code.

For each HTTP status code that you want to return, you need to add an "Integration Response" entry. The integration response is configured to match the regular expression (using java.util.regex.Matcher.matches() not .find() ), which corresponds to the errorMessage field. After the match is completed, you can customize the body mapping template to selectively format the appropriate exception body.

Since the regular expression matches only the body of errorMessage from the exception, you need to make sure that your exception contains enough information to allow the various integration answers to match and set the corresponding error. (You cannot use .* To match all exceptions, as this seems to match all answers, including non-exceptions!)

Value exceptions

To create exceptions with sufficient detail in your post, the error-handling-patterns-in-amazon-api-gateway-and-aws-lambda blog recommends creating an exception handler in your handler to populate the exception information in the JSON string that will be used in the exception message.

My preferred approach is to create a new top method as a handler that handles the response to the Gateway API. This method returns the required payload or throws an exception with the original exception encoded as a JSON string as an exception message.

 def my_handler_core(event, context): try: s3conn.head_object(Bucket='my_bucket', Key='my_filename') ... return something except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "404": raise ClientException("Key '{}' not found".format(filename)) def my_handler(event=None, context=None): try: token = my_handler_core(event, context) response = { "response": token } # This is the happy path return response except Exception as e: exception_type = e.__class__.__name__ exception_message = str(e) api_exception_obj = { "isError": True, "type": exception_type, "message": exception_message } # Create a JSON string api_exception_json = json.dumps(api_exception_obj) raise LambdaException(api_exception_json) # Simple exception wrappers class ClientException(Exception): pass class LambdaException(Exception): pass 

In case of an exception, Lambda now returns:

 { "stackTrace": [ [ "/var/task/mycode.py", 42, "my_handler", "raise LambdaException(api_exception_json)" ] ], "errorType": "LambdaException", "errorMessage": "{\"message\": \"Key 'my_filename' not found\", \"type\": \"ClientException\", \"isError\": true}" } 

Display errors

Now that you have all the details in errorMessage , you can start matching status codes and creating well-formed error messages. The Gateway API parses and cancels the errorMessage field, so the regular expression used does not need to be fixed.

example

To catch this ClientException as a 400 error and map the payload to a clean error model, you can do the following:

  • Create a new error model:

     { "type": "object", "title": "MyErrorModel", "properties": { "isError": { "type": "boolean" }, "message": { "type": "string" }, "type": { "type": "string" } }, "required": [ "token", "isError", "type" ] } 
  • Edit the โ€œMethod Responseโ€ and map the new model to 400
  • Add New Integration Response
  • Set code 400
  • Set a regular expression to match ClientException types with a space tolerance:. .*"type"\s*:\s*"ClientException".*
  • Add a body mapping template for application/json to map the contents of errorMessage to your model:

     #set($inputRoot = $input.path('$')) #set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage'))) { "isError" : true, "message" : "$errorMessageObj.message", "type": "$errorMessageObj.type" } 
+14
source

Source: https://habr.com/ru/post/1259278/


All Articles