API 网关 + Lambda + Python:处理异常
API Gateway + Lambda + Python: Handling Exceptions
我在非代理模式下从 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
我应该抛出异常还是 return 字符串?那我应该如何配置集成响应?显然我有 RTFM 但 FM 是 FU。
tl;博士
- 如果您想要非 200 响应,您的 Lambda 处理程序必须抛出异常。
- 在处理程序方法中捕获所有异常。将捕获的异常消息格式化为 JSON 并作为自定义异常类型抛出。
- 使用集成响应对在 Lambda 响应的 errorMessage 字段中找到的自定义异常进行正则表达式。
API 网关 + AWS Lambda 异常处理
关于 Lambda、API 网关以及它们如何协同工作,您需要了解很多事情。
Lambda 异常
当您的 handler/function/method 抛出异常时,该异常被序列化为 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 网关集成响应
概述
"Integration Responses" 将 Lambda 的响应映射到 HTTP 代码。它们还允许在传递时更改消息正文。
默认情况下,为您配置了“200”集成响应,它将所有来自 Lambda 的响应按原样传递回客户端,包括序列化的 JSON 异常,作为 HTTP 200 (OK) 响应。
对于良好的消息,您可能希望使用“200”集成响应将 JSON 有效负载映射到您定义的模型之一。
捕获异常
对于例外情况,您需要设置适当的 HTTP 状态代码并可能删除堆栈跟踪以隐藏代码的内部结构。
对于您希望 return 的每个 HTTP 状态代码,您需要添加一个 "Integration Response" 条目。集成响应配置了与 errorMessage 字段匹配的正则表达式匹配(使用 java.util.regex.Matcher.matches()
而不是 .find()
)。一旦匹配完成,您就可以配置主体映射模板,以有选择地格式化合适的异常主体。
由于正则表达式仅匹配异常中的 errorMessage 字段,您需要确保异常包含足够的信息以允许不同的集成响应匹配并设置错误因此。
(您不能使用 .*
匹配所有异常,因为这似乎匹配所有响应,包括非异常!)
有意义的异常
要在消息中创建包含足够详细信息的异常,error-handling-patterns-in-amazon-api-gateway-and-aws-lambda 博客建议您在处理程序中创建一个异常处理程序,以将异常的详细信息填充到 JSON 字符串中,以便在异常消息。
我的首选方法是创建一个新的顶级方法作为处理响应 API 网关的处理程序。此方法要么 returns 所需的有效负载,要么抛出异常,并将原始异常编码为 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 现在将 return:
{
"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 字段进行解析和反转义,所以使用的regex 不需要处理转义。
例子
要将此 ClientException 捕获为 400 错误并将负载映射到干净的错误模型,您可以执行以下操作:
创建新的错误模型:
{
"type": "object",
"title": "MyErrorModel",
"properties": {
"isError": {
"type": "boolean"
},
"message": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"token",
"isError",
"type"
]
}
- 编辑 "Method Response" 并将新模型映射到
400
- 添加新的集成响应
- 将代码设置为
400
- 设置正则表达式以匹配 "ClientException" 具有空格容忍度的类型:
.*"type"\s*:\s*"ClientException".*
为 application/json
添加人体映射模板,将 errorMessage
的内容映射到您的模型:
#set($inputRoot = $input.path('$'))
#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))
{
"isError" : true,
"message" : "$errorMessageObj.message",
"type": "$errorMessageObj.type"
}
我在非代理模式下从 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
我应该抛出异常还是 return 字符串?那我应该如何配置集成响应?显然我有 RTFM 但 FM 是 FU。
tl;博士
- 如果您想要非 200 响应,您的 Lambda 处理程序必须抛出异常。
- 在处理程序方法中捕获所有异常。将捕获的异常消息格式化为 JSON 并作为自定义异常类型抛出。
- 使用集成响应对在 Lambda 响应的 errorMessage 字段中找到的自定义异常进行正则表达式。
API 网关 + AWS Lambda 异常处理
关于 Lambda、API 网关以及它们如何协同工作,您需要了解很多事情。
Lambda 异常
当您的 handler/function/method 抛出异常时,该异常被序列化为 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 网关集成响应
概述
"Integration Responses" 将 Lambda 的响应映射到 HTTP 代码。它们还允许在传递时更改消息正文。
默认情况下,为您配置了“200”集成响应,它将所有来自 Lambda 的响应按原样传递回客户端,包括序列化的 JSON 异常,作为 HTTP 200 (OK) 响应。
对于良好的消息,您可能希望使用“200”集成响应将 JSON 有效负载映射到您定义的模型之一。
捕获异常
对于例外情况,您需要设置适当的 HTTP 状态代码并可能删除堆栈跟踪以隐藏代码的内部结构。
对于您希望 return 的每个 HTTP 状态代码,您需要添加一个 "Integration Response" 条目。集成响应配置了与 errorMessage 字段匹配的正则表达式匹配(使用 java.util.regex.Matcher.matches()
而不是 .find()
)。一旦匹配完成,您就可以配置主体映射模板,以有选择地格式化合适的异常主体。
由于正则表达式仅匹配异常中的 errorMessage 字段,您需要确保异常包含足够的信息以允许不同的集成响应匹配并设置错误因此。
(您不能使用 .*
匹配所有异常,因为这似乎匹配所有响应,包括非异常!)
有意义的异常
要在消息中创建包含足够详细信息的异常,error-handling-patterns-in-amazon-api-gateway-and-aws-lambda 博客建议您在处理程序中创建一个异常处理程序,以将异常的详细信息填充到 JSON 字符串中,以便在异常消息。
我的首选方法是创建一个新的顶级方法作为处理响应 API 网关的处理程序。此方法要么 returns 所需的有效负载,要么抛出异常,并将原始异常编码为 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 现在将 return:
{
"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 字段进行解析和反转义,所以使用的regex 不需要处理转义。
例子要将此 ClientException 捕获为 400 错误并将负载映射到干净的错误模型,您可以执行以下操作:
创建新的错误模型:
{ "type": "object", "title": "MyErrorModel", "properties": { "isError": { "type": "boolean" }, "message": { "type": "string" }, "type": { "type": "string" } }, "required": [ "token", "isError", "type" ] }
- 编辑 "Method Response" 并将新模型映射到
400
- 添加新的集成响应
- 将代码设置为
400
- 设置正则表达式以匹配 "ClientException" 具有空格容忍度的类型:
.*"type"\s*:\s*"ClientException".*
为
application/json
添加人体映射模板,将errorMessage
的内容映射到您的模型:#set($inputRoot = $input.path('$')) #set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage'))) { "isError" : true, "message" : "$errorMessageObj.message", "type": "$errorMessageObj.type" }