【问题标题】:API Gateway + Lambda + Python: Handling ExceptionsAPI Gateway + Lambda + Python:处理异常
【发布时间】:2018-02-07 16:23:53
【问题描述】:

我正在以非代理模式从 API Gateway 调用基于 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

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

【问题讨论】:

    标签: python exception-handling aws-lambda aws-api-gateway


    【解决方案1】:

    tl;博士

    1. 如果您想要非 200 响应,您的 Lambda 处理程序必须引发异常。
    2. 在处理程序方法中捕获所有异常。将捕获的异常消息格式化为 JSON 并作为自定义异常类型抛出。
    3. 使用集成响应正则表达式您在 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"
    }
    

    API 网关集成响应

    概述

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

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

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

    捕捉异常

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

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

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

    有意义的例外

    要在其消息中创建具有足够详细信息的异常,error-handling-patterns-in-amazon-api-gateway-and-aws-lambda 博客建议您在处理程序中创建一个异常处理程序,以将异常的详细信息填充到一个 JSON 字符串中以在异常消息中使用。

    我首选的方法是创建一个新的顶级方法作为处理 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
    

    如果出现异常,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}"
    }
    

    映射异常

    现在您已经在 errorMessage 中获得了所有详细信息,您可以开始映射状态代码并创建格式正确的错误负载。 API Gateway 对errorMessage 字段进行解析和转义,因此使用的正则表达式不需要处理转义。

    例子

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

    1. 创建新的错误模型:

      {
        "type": "object",
        "title": "MyErrorModel",
        "properties": {
          "isError": {
              "type": "boolean"
          },
          "message": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "token",
          "isError",
          "type"
        ]
      }
      
    2. 编辑“方法响应”并将新模型映射到400
    3. 添加新的集成响应
    4. 将代码设置为400
    5. 设置正则表达式以匹配“ClientException”类型并允许空格:.*"type"\s*:\s*"ClientException".*
    6. application/json 添加一个主体映射模板,以将errorMessage 的内容映射到您的模型:

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

    【讨论】:

    • 我可能错过了一步。但是,一旦您将集成响应设为 200 作为默认值,是否不会将任何与您的特定错误不匹配的异常返回为 200?
    • 刚来这里是为了说json.dumps 是严格要求的——因为没有它,python 会引发一个带有 dict 的异常,然后将其转换为 JSON-with-single-quotes 这对于 API Gateway 的 @ 无效987654336@,静默失败(是的,我知道..)并返回一个空的 json 对象。
    • @unclemeat 确实,但这是大多数用户在使用默认设置时遇到的问题。无论原始异常是什么,上述方法都应该始终返回 LambdaException,除非在最外层的异常处理中发生了某些事情。 -- 我想当您想要返回 200 状态时,可以将错误设置为默认响应并匹配已知良好的字符串。您仍然需要提供一个正文映射模板以确保您的响应隐藏您的堆栈跟踪。
    猜你喜欢
    • 2018-12-06
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多