- 后端
- Serverless
- CLI
【免费下载链接】chalice
Python Serverless Microframework for AWS
导读
本文以 AWS Chalice 官方中间件文档为骨架,系统讲解 Chalice 中间件的定义、注册方式、错误处理策略与常见实战模式。中间件让你在不动业务代码的前提下,拦截并改造每个 Lambda 处理函数的请求与响应生命周期,可用于统一日志、鉴权校验、性能埋点、第三方工具集成等场景。读完本文,你将掌握@app.middleware()装饰器、register_middleware()方法、ConvertToMiddleware适配器三类注册手段,以及 REST API 特有的异常处理行为,并能在真实 Chalice 应用中直接落地这些模式。
Chalice 开箱即用地提供了大量特性,但实际项目中你常常需要针对自己的业务定制框架行为——中间件正是为此设计的:它是一个注册到应用上的函数,每当你的 Lambda 函数被调用时,Chalice 会自动执行它。下文所有源码引用均出自当前仓库 chalice/app.py,测试佐证来自 tests/unit/test_app.py。
中间件基础:一段最小示例
from chalice import Chalice app = Chalice(app_name='demo-middleware') @app.middleware('all') def my_middleware(event, get_response): app.log.info("Before calling my main Lambda function.") response = get_response(event) app.log.info("After calling my main Lambda function.") return response @app.route('/') def index(): return {'hello': 'world'} @app.on_sns_message('mytopic') def sns_handler(event): pass这个示例中的中间件在 Lambda 函数被调用前后各输出一条日志。由于注册时指定的事件类型是all,无论触发的是 REST API 的index()还是 SNS 消息处理函数sns_handler(),my_middleware都会被执行。
从源码结构看,中间件的核心载体是 chalice/app.py 中的MiddlewareHandler类与BaseLambdaHandler._build_middleware_handlers():调用链通过把每个中间件包装成MiddlewareHandler(handler=handler, next_handler=current)形成"洋葱模型",最后层的original_handler才是真正的 Lambda 处理函数。Chalice.__call__中通过_get_middleware_handlers('http')等调用把匹配的中间件注入到对应事件处理器中(见 chalice/app.py 与 chalice/app.py)。
编写中间件的四项要求
中间件必须满足以下约束:
- 必须是可调用对象,接受两个参数:
event和get_response。event的具体类型取决于中间件注册时指定的事件类型(详见下文"注册中间件"一节)。 - 必须返回一个响应,这个响应会最终返回给调用方。
- 必须调用
get_response(event)才能触发链上的下一个中间件并最终执行真正的 Lambda 处理函数。 - 可以短路(short-circuit)请求:中间件可以不调用
get_response(event)而直接返回自己的响应;此时响应类型应与底层 Lambda 处理函数的响应类型保持一致。
下面是最简单的"什么都不做"的中间件:
@app.middleware('all') def noop_middleware(event, get_response): # `event` 的类型取决于被调用的 Lambda 处理函数类型。 return get_response(event)测试用例 tests/unit/test_app.py 验证了多层中间件的执行顺序:注册在前的中间件先执行,随后逐层向内,最后执行真正的 handler;tests/unit/test_app.py 则验证了短路行为——第一个中间件直接返回响应后,后续中间件与 handler 都不会再被调用。
错误处理策略
除 REST API 外,所有事件类型(SNS、S3、SQS、CloudWatch、Websocket、纯 Lambda 等)的中间件遵循相同的错误处理策略:Lambda handler 抛出的任何异常都会原样传播回每一层中间件。你可以在中间件中捕获并处理这些异常。例如:
@app.middleware('all') def handle_errors(event, get_response): try: return get_response(event) except MyCustomError as e: # 不希望 MyCustomError 继续向外传播, # 而是把它转换成一个错误响应字典。 return {"Error": e.__class__.__name__, "Message": str(e)} @app.lambda_function() def noop_middleware(event, context): raise MyCustomError("Raising an error.")如果 Lambda handler 抛出异常且没有任何中间件捕获它,该异常会原样返回给调用 Lambda 的客户端。
REST API 的特殊错误处理
出于向后兼容的考虑,REST API 有专门的处理流程:如果@app.route装饰的 view 函数抛出了异常,Chalice 会自动捕获它并转换为设置了适当状态码的Response对象(即 views.rst 中view-error-handling所描述的视图错误处理机制)。因此,注册在 REST API 上的中间件看不到异常传播——它们调用get_response(event)得到的是Response对象,而不是异常。
如果你想允许异常从 view 函数中向外传播,可以抛出chalice.ChaliceUnhandledError。例如:
from chalice import ChaliceUnhandledError @app.middleware('all') def handle_errors(event, get_response): try: return get_response(event) except ChaliceUnhandledError as e: return Response(status_code=500, body=str(e), headers={'Content-Type': 'text/plain'}) @app.route('/') def index(): # handle_errors 中间件永远看不到这个异常, # 它会被自动转换为状态码为 500 的 Response 对象。 raise MyCustomError("Raising an error.") @app.route('/error') def unhandled_error(): # 因为异常类型是 ChaliceUnhandledError, # handle_errors 中间件会看到这个异常。 raise ChaliceUnhandledError("Raising an error.")这一设计非常适合"对所有事件类型统一做错误处理"的中间件。如果ChaliceUnhandledError抛出后没有任何中间件捕获处理,则走标准错误处理流程:向用户返回 500 响应;若开启了 debug 模式,则把 traceback 作为响应体返回。
源码佐证:在 chalice/app.py 的_get_view_function_response()中,view 函数的异常处理顺序是:ChaliceUnhandledError被显式raise重抛(让中间件有机会处理),ChaliceViewError子类(如BadRequestError、UnauthorizedError,定义见 chalice/app.py)被转换为对应状态码的响应,其他未知异常则通过_unhandled_exception_to_response()转换为 500 响应(chalice/app.py)。REST API 处理器的中间件链还额外在首位插入了_global_error_handler作为最外层兜底(chalice/app.py)。
注册中间件
注册中间件有两种方式。
方式一:@app.middleware()装饰器
@app.middleware()接受一个参数,用于指定该中间件要注册到哪种类型的 Lambda 函数上。这样你可以只把中间件应用于特定事件类型的 handler(例如仅 REST API、仅 WebSocket、仅 S3 事件)。要注册到所有 Lambda 函数,传入all即可。
支持的事件类型及对应提供给中间件的 event 类型如下表:
| 事件类型 | 中间件收到的 event 类型 |
|---|---|
all | Any |
s3 | S3Event |
sns | SNSEvent |
sqs | SQSEvent |
cloudwatch | CloudWatchEvent |
scheduled | CloudWatchEvent |
websocket | WebsocketEvent |
http | Request |
pure_lambda | LambdaFunctionEvent |
注意:
chalice.LambdaFunctionEvent是唯一一个中间件 event 类型与对应 Lambda handler event 类型不一致的情况。出于向后兼容,@app.lambda_function()装饰器的签名被保留为(event, context),而中间件需要一个统一的单参数签名,因此引入了LambdaFunctionEvent。相关定义见 chalice/app.py,装饰器名到中间件事件类型的映射见 chalice/app.py(_MIDDLEWARE_MAPPING,额外支持kinesis与dynamodb映射)。
各事件类(S3Event、SNSEvent、SQSEvent、CloudWatchEvent、WebsocketEvent等)的属性提取逻辑都可以在 chalice/app.py 中找到,例如S3Event提供bucket与key属性,SNSEvent提供message、subject、message_attributes属性。
方式二:Chalice.register_middleware()方法
register_middleware()与middleware()行为一致,区别在于把中间件函数作为参数传入而非用装饰器,这在导入第三方函数并当作中间件使用时非常方便:
import thirdparty app.register_middleware(thirdparty.func, 'all')DecoratorAPI.middleware()的装饰器实现本质上就是在内部调用register_middleware(func, event_type)(见 chalice/app.py),而Chalice.register_middleware的实现会把(func, event_type)追加到middleware_handlers列表中(chalice/app.py)。
用ConvertToMiddleware复用现有 Lambda 包装器
ConvertToMiddleware类可以把已有的"Lambda 包装器"(wrapper)转换为中间件。例如你有一个日志装饰器:
def log_invocation(func): def wrapper(event, context): logger.debug("Before lambda function.") response = func(event, context) logger.debug("After lambda function.") return wrapper @app.lambda_function() @log_invocation def myfunction(event, context): logger.debug("In myfunction().")与其在每个 Lambda 函数上都手动套@log_invocation,不如用ConvertToMiddleware一次性把该包装器应用到应用中所有Lambda 函数:
from chalice import ConvertToMiddleware app.register_middleware(ConvertToMiddleware(log_invoation))从实现看,ConvertToMiddleware.__call__会先从传入的event中还原出(original_event, context)参数对(REST API 请求走Request.to_original_event(),其余走event.to_dict()),再用get_response(event)桥接回中间件链,最终以(event, context)的原始签名调用第三方包装器(见 chalice/app.py)。该能力同样适合集成那些以 Lambda 包装器形式存在的第三方库,完整的集成示例见下文"集成 AWS Lambda Powertools"。
实战模式示例
模式一:短路请求(Short Circuiting)
假设我们要求所有 HTTP 请求都必须携带某个自定义请求头,否则直接返回 400。因为这是 HTTP 特有的逻辑,只需注册到http事件类型:
from chalice import Response @app.middleware('http') def require_header(event, get_response): # 由上文表格可知,http 事件类型下 event 是 chalice.Request。 if 'X-Custom-Header' not in event.headers: return Response( status_code=400, body={"Error": "Missing required 'X-Custom-Header'"}) # 请求头存在,则走正常请求流程。 return get_response(event)Request.headers是大小写不敏感的映射,因此'x-custom-header'与'X-Custom-Header'的写法都能正确命中。短路场景的测试验证见 tests/unit/test_app.py。
模式二:修改响应(Modifying a Response)
假设我们想测量处理耗时并注入到 Lambda 响应中:
import time @app.middleware('pure_lambda') def inject_time(event, get_response): start = time.time() response = get_response(event) total = time.time() - start response.setdefault('metadata', {})['duration'] = total return response这里注册为pure_lambda,因此get_response(event)返回的是纯 Lambda 处理函数(@app.lambda_function())的返回字典,可以安全地对它做setdefault修改。响应可被中间件逐层修改的行为在 tests/unit/test_app.py 中有对应验证(多个中间件依次向响应字典注入自己的键)。
模式三:集成 AWS Lambda Powertools
AWS Lambda Powertools 是一套面向 Lambda 的工具集,让 X-Ray 追踪、结构化日志、自定义指标变得更容易。你可以用 Chalice 中间件轻松地把 Powertools 集成进应用。下面的例子把 Powertools 的Logger与Tracer转成 Chalice 中间件,自动应用到应用中所有 Lambda 函数:
from chalice import Chalice from chalice.app import ConvertToMiddleware # 首先,不再使用 Chalice 内置 logger,改用 powertools 的结构化 logger。 # 除了自动注入 lambda context,假设我们还希望注入当前调用的路由。 from aws_lambda_powertools import Logger from aws_lambda_powertools import Tracer app = Chalice(app_name='chalice-powertools') logger = Logger(service=app.app_name) tracer = Tracer(service=app.app_name) # 自动把 lambda 函数上的装饰器转换为中间件, # 连接到应用中每一个 lambda 函数。 # 这样既避免了在每个 lambda 函数上手动加装饰器, # 也适用于我们无法控制源码的场景(例如注册 blueprint)。 app.register_middleware(ConvertToMiddleware(logger.inject_lambda_context)) app.register_middleware( ConvertToMiddleware( tracer.capture_lambda_handler(capture_response=False)) ) # 这里编写 Chalice 风格的中间件: # 对任何 HTTP API,把请求路径注入到结构化日志消息中。 # 这展示了如何把 Chalice 中间件与已有工具组合使用。 @app.middleware('http') def inject_route_info(event, get_response): logger.structure_logs(append=True, request_path=event.path) return get_response(event) @app.route('/') def index(): logger.info("In index() function, this will have a 'path' key.") return {'hello': 'world'} @app.route('/foo/bar') def foobar(): logger.info("In foobar() function") return {'foo': 'bar'} @app.lambda_function() def myfunction(event, context): logger.info("In myfunction().") tracer.put_annotation(key="Status", value="SUCCESS") return {}这个例子展示了两层用法:ConvertToMiddleware把 Powertools 的装饰器统一注入到所有 Lambda 函数(包括@app.lambda_function()声明的纯 Lambda),而@app.middleware('http')编写的自定义中间件则专门为 HTTP 请求补充路由信息,两者可以无缝共存于同一条中间件链中。该模式对使用 Blueprint 组织的应用尤其有价值——因为 Blueprint 中的 handler 由外部模块定义,借助 chalice/app.py 中Blueprint.register_middleware的延迟注册机制,应用级中间件也能覆盖到它们。
中间件执行原理速览
把上述内容汇总为执行链路:
- 应用启动时,
@app.middleware(type)或app.register_middleware(func, type)把(func, event_type)存入Chalice.middleware_handlers列表。 - 每次 Lambda 被调用时,
Chalice._get_middleware_handlers(event_type)以生成器形式筛选出event_type匹配(等于指定类型或all)的中间件(chalice/app.py)。之所以用生成器,是为了尽量延迟收集,从而让 handler 定义之后再注册的中间件也能生效。 - 事件处理器(如
EventSourceHandler)首次调用时,通过_build_middleware_handlers()把中间件列表逆序包装成嵌套的MiddlewareHandler链,最内层是原始 handler(chalice/app.py)。REST API 处理器则额外在最外层套上_global_error_handler。 - 请求进入时逐层穿过中间件:每层执行"前置逻辑 →
get_response(event)调用内层 → 后置逻辑",最终返回的响应再逐层向外返回给调用方。
基于这一机制,你可以把鉴权、限流、日志、追踪、错误兜底等横切关注点统一下沉到中间件层,让业务 handler 保持纯粹。
相关文档导航
- 视图函数错误处理(
ChaliceViewError与状态码映射):views.rst - 中间件核心实现(
MiddlewareHandler、ConvertToMiddleware、事件类与映射表):chalice/app.py、chalice/app.py、chalice/app.py - 中间件行为测试(顺序、短路、响应修改、异常传播、REST API 过滤注册):tests/unit/test_app.py
- 蓝图中注册中间件的延迟机制:chalice/app.py
- 后端
- Serverless
- CLI
【免费下载链接】chalice
Python Serverless Microframework for AWS
相关推荐
midwayServerless中间件:请求生命周期管理
midwayServerless中间件:请求生命周期管理 你是否在开发Serverless应用时遇到过这些问题:如何在请求前后统一处理日志?如何优雅地实现接口鉴
后端微服务云原生Rocket 框架 Fairings 完全指南:请求生命周期的结构化中间件
Rocket 框架 Fairings 完全指南:请求生命周期的结构化中间件 Fairings 是 Rust Web 框架 Rocket 实现结构化中间件(str
后端开发工具Traefik Headers 中间件完全指南:自定义请求/响应头、CORS 与安全响应头实战
Traefik Headers 中间件完全指南:自定义请求/响应头、CORS 与安全响应头实战 本文基于当前仓库 docs/content/reference/
后端API网关负载均衡微服务网络云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考