API网关+ Lambda + Python:处理异常

Ala*_*ack 15 python exception-handling aws-lambda aws-api-gateway

我在非代理模式下从API网关调用基于Python的AWS Lambda方法.我应该如何正确处理异常,以便使用部分异常设置适当的HTTP状态代码和JSON主体.

作为一个例子,我有以下处理程序:

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
Run Code Online (Sandbox Code Playgroud)

我应该抛出异常还是返回一个字符串?那我该如何配置集成响应?显然我有RTFM,但FM是FU.

Ala*_*ack 30

TL;博士

  1. 如果您想要非200响应,则Lambda处理程序必须抛出异常.
  2. 捕获处理程序方法中的所有异常.将捕获的异常消息格式化为JSON并作为自定义异常类型抛出.
  3. 使用Integration Response来正则表达在Lambda响应的errorMessage字段中找到的自定义异常.

API网关+ AWS Lambda异常处理

关于Lambda,API Gateway以及它们如何协同工作,您需要了解许多内容.

Lambda例外

从处理程序/函数/方法抛出异常时,异常将序列化为JSON消息.从您的示例代码,在S3的404上,您的代码将抛出:

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

 API网关集成响应

概观

"集成响应"将响应从Lambda映射到HTTP代码.它们还允许消息体在通过时进行更改.

默认情况下,为您配置"200"集成响应,它将Lambda的所有响应按原样传递回客户端,包括序列化的JSON异常,作为HTTP 200(OK)响应.

对于好消息,您可能希望使用"200"集成响应将JSON有效负载映射到您定义的模型之一.

捕捉异常

对于例外,您需要设置适当的HTTP状态代码,并可能删除堆栈跟踪以隐藏代码的内部.

对于您希望返回的每个HTTP状态代码,您需要添加"集成响应"条目.集成响应配置了与errorMessage字段匹配的正则表达式匹配(使用java.util.regex.Matcher.matches()not .find()).完成匹配后,您可以配置正文映射模板,以选择性地格式化合适的异常体.

由于正则表达式仅匹配异常中的errorMessage字段,因此您需要确保您的异常包含足够的信息以允许不同的Integration响应匹配并相应地设置错误.(您不能.*用来匹配所有异常,因为它似乎匹配所有响应,包括非例外!)

有意义的例外

要在消息中创建具有足够详细信息的异常,错误处理模式在amazon-api-gateway-and-aws-lambda博客中建议您在处理程序中创建异常处理程序,以将异常的详细信息填充到JSON中要在异常消息中使用的字符串.

我首选的方法是创建一个新的top方法作为处理程序,处理响应API网关.此方法返回所需的有效负载,或者将编译为JSON字符串的原始异常作为异常消息抛出异常.

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
Run Code Online (Sandbox Code Playgroud)

在例外情况下,Lambda现在将返回:

{
    "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}"
}
Run Code Online (Sandbox Code Playgroud)

映射异常

现在您已经拥有了errorMessage中的所有详细信息,您可以开始映射状态代码并创建格式良好的错误有效负载.API网关解析和取消了errorMessage字段,因此使用的正则表达式不需要处理转义.

要将此ClientException捕获为400错误并将有效内容映射到干净的错误模型,您可以执行以下操作:

  1. 创建新的错误模型:

    {
      "type": "object",
      "title": "MyErrorModel",
      "properties": {
        "isError": {
            "type": "boolean"
        },
        "message": {
          "type": "string"
        },
        "type": {
          "type": "string"
        }
      },
      "required": [
        "token",
        "isError",
        "type"
      ]
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 编辑"方法响应"并将新模型映射到400
  3. 添加新的集成响应
  4. 将代码设置为 400
  5. 设置正则表达式以匹配具有空格容差的"ClientException"类型: .*"type"\s*:\s*"ClientException".*
  6. 添加一个Body Mapping模板,application/json用于将内容映射errorMessage到您的模型:

    #set($inputRoot = $input.path('$'))
    #set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))
    {
        "isError" : true,
        "message" : "$errorMessageObj.message",
        "type": "$errorMessageObj.type"
    }
    
    Run Code Online (Sandbox Code Playgroud)