feat(event_handler): add API Gateway WebSocket resolver#8338
Conversation
…bSocket event Groundwork for the API Gateway WebSocket event handler (aws-powertools#1165), kept as a separate commit so the data-class changes are reviewable on their own. The resolver's auth and send-to-client patterns need both properties: the repo already supports authoring WebSocket Lambda authorizers (APIGatewayAuthorizerResponseWebSocket); this adds the consuming side — the authorizer context API Gateway injects into every invocation for a connection — plus the Management API callback URL for replying to clients.
Standalone resolver for API Gateway WebSocket APIs (aws-powertools#1165), built on APIGatewayWebSocketEvent and mirroring the AppSync Events package structure: route()/on_connect/on_disconnect/on_default registration with exact route-key matching, response normalization to the statusCode/body shape API Gateway expects, and exception handling via the shared ExceptionHandlerManager with a catch-all that returns a bare 500 so exception details never reach connected clients. Includes functional tests (full coverage), docs page with getting started examples and SAM template, and mkdocs nav.
…resolver Reuses the shared middleware framework (MiddlewareFrame, BaseMiddlewareHandler) with a WebSocket terminal adapter: global middlewares registered via use() run first, then route-level middlewares=[...], then the handler. Return values — including short-circuits that skip next_middleware — go through the same response normalization as handler returns, and middleware exceptions follow the existing exception-handling path. Global middlewares also run around the 400 for unmatched route keys, matching the REST resolver's not-found behaviour. Includes middleware tests (ordering, short-circuit, isolation across routes, exception paths, class-based middleware) and docs: Middleware plus Authentication patterns (Lambda authorizer first, then authenticate/inject_user middleware with a bring-your-own connection store).
…lver include_router merges a Router's routes, global middlewares, exception handlers, and context into the resolver, so route keys can be split across files. Router handlers access the request through the router instance (router.current_event), mirroring the AppSync Events and REST resolver conventions. On route key collisions the router-registered handler wins, matching AppSync Events. Includes tests for dispatch, context pointer sharing, middleware and exception-handler merging and ordering, route-key precedence, plus docs with a two-file example.
Adds the overview and workflow mermaid diagrams, accessing the event and Lambda context, and the sending-messages recipe: a connection store capturing connection_id and callback_url at $connect, an in-resolver broadcast, and a separate function pushing a result to the requesting client later — with GoneException handling, ManageConnections IAM, and a custom domain caveat.
aad0bae to
073e08e
Compare
| RouteResponseKey: $default | ||
|
|
||
| WebSocketStage: | ||
| Type: AWS::ApiGatewayV2::Stage |
There was a problem hiding this comment.
Addressed in 72c6b67. AccessLogSettings added to the stage.
Adds botocore timeouts to Management API clients, moves client construction out of the handler, and enables function and access logging in the getting-started SAM template. Example code, it turns out, is held to production standards — fair enough.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #8338 +/- ##
===========================================
+ Coverage 96.63% 96.68% +0.04%
===========================================
Files 296 302 +6
Lines 14765 14950 +185
Branches 1245 1255 +10
===========================================
+ Hits 14268 14454 +186
+ Misses 362 361 -1
Partials 135 135 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Local test exerciser — while building this I used a small API Gateway WebSocket emulator to drive the resolver over a real socket: genuine upgrade handshake, route selection, route responses, and the Management API (with a real pip install aiohttp websockets boto3
python ws_local_harness.py # emulator serving the demo app
python ws_harness_client.py # 17 scripted checksA real session ( @app.route("orderUpdate")
def order_update():
order = app.current_event.json_body
if "orderId" not in order:
raise ValueError("missing orderId") # handled by @app.exception_handler(ValueError)
return OrderReceipt(orderId=order["orderId"], status="received", price=Decimal("19.99"), ...)
@app.route("boom")
def boom():
raise RuntimeError("secret detail that must never reach the client")Full checklist — 17/17 |



Issue number: closes #1165
Summary
Changes
Implements the design agreed in RFC discussion #5905. Adds a standalone
APIGatewayWebSocketResolverfor Amazon API Gateway WebSocket APIs, built on the existingAPIGatewayWebSocketEventdata class: route decorators (on_connect/on_disconnect/on_default, plusroute("customKey")with exact route-key matching), response normalization to thestatusCode/bodyshape API Gateway expects, middleware support,Router/include_routerfor splitting routes across files, and exception handling with a catch-all that returns a bare 500 so exception details never reach connected clients over the socket.This PR stands on the shoulders of giants: the package structure and Router mechanics mirror the AppSync Events resolver (#6558), middleware reuses the shared
BaseMiddlewareHandler/NextMiddlewareframework, and exception handling completes the sharedExceptionHandlerManagerpattern that AppSync Events left groundwork for. Most of the hard problems were already standardised elsewhere in Event Handler — the new work is mapping WebSocket semantics onto those rails.The first commit also rounds out the WebSocket data class with two small properties the resolver builds on:
authorizer(the context API Gateway injects on every invocation when a Lambda authorizer is configured) andcallback_url(the Management API endpoint for pushing messages to clients).Docs page with getting-started examples, SAM template, authentication patterns, and a send-to-client recipe included, following the AppSync Events page as the template.
User experience
Before:
After:
Open questions for review
on_connect/on_disconnect/on_defaultchosen for consistency with AppSync Events'on_publish/on_subscribe; trivial to rename.PowertoolsUserWarning+{"statusCode": 400}(a miss means the API has a route configured with no handler registered; surfacing beats silence). Alternatives: raise, or return 200 silently.NextMiddleware'sEventHandlerInstanceTypeVar is bound toApiGatewayResolver, so users annotating WebSocket middlewares with it fail mypy (runtime is fine; examples use unannotated params). A one-line bound widening would fix it. (b) This resolver importsMiddlewareFramefromapi_gateway.py— worth lifting intoevent_handler/middlewares/as shared infrastructure? Happy to do either here or as follow-ups.Possible follow-ups
Offering these as separate PRs if there's appetite — none block this one:
WebSocketConnectionManagerhelper for the send-to-client path:post(connection_id, data),GoneExceptionhandled (typed error or apost_if_connectedreturningFalse), lazy boto3 import, built from the event or an endpoint. The docs recipe in this PR is the seed. Tension acknowledged: Powertools avoids re-wrapping the SDK; the counter-precedent is parameters/idempotency, which wrap boto3 where boilerplate reduction is real —GoneExceptionhandling is exactly that.tests/e2e/event_handler_websocket/suite (real WebSocket API +wss://client) could assert what functional tests physically cannot: route responses delivering bodies, rejected$connectrefusing the upgrade at the protocol level, a real Lambda authorizer's context on message routes, and a genuineGoneException. The REST event handler's e2e suite is the precedent; offering since e2e is maintainer-run infra.docs/utilities/data_classes.mdhas noAPIGatewayWebSocketEvententry (pre-existing gap); happy to add a row.Checklist
If your change doesn't seem to apply, please leave them unchecked.
Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.