Skip to content

Commit 15d820a

Browse files
Merge branch 'develop' into fix/7979-authorizer-proxy-plus-regex
2 parents 5ad6057 + 74fbcc5 commit 15d820a

File tree

4 files changed

+189
-77
lines changed

4 files changed

+189
-77
lines changed

aws_lambda_powertools/event_handler/http_resolver.py

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
import base64
55
import inspect
6+
import threading
67
import warnings
78
from typing import TYPE_CHECKING, Any, Callable
89
from urllib.parse import parse_qs
@@ -324,36 +325,65 @@ async def final_handler(app):
324325
return await next_handler(self)
325326

326327
def _wrap_middleware_async(self, middleware: Callable, next_handler: Callable) -> Callable:
327-
"""Wrap a middleware to work in async context."""
328+
"""Wrap a middleware to work in async context.
329+
330+
For sync middlewares, we split execution into pre/post phases around the
331+
call to next(). The sync middleware runs its pre-processing (e.g. request
332+
validation), then we intercept the next() call, await the async handler,
333+
and resume the middleware with the real response so post-processing
334+
(e.g. response validation) sees the actual data.
335+
"""
328336

329337
async def wrapped(app):
330-
# Create a next_middleware that the sync middleware can call
331-
def sync_next(app):
332-
# This will be called by sync middleware
333-
# We need to run the async next_handler
334-
loop = asyncio.get_event_loop()
335-
if loop.is_running():
336-
# We're in an async context, create a task
337-
future = asyncio.ensure_future(next_handler(app))
338-
# Store for later await
339-
app.context["_async_next_result"] = future
340-
return Response(status_code=200, body="") # Placeholder
341-
else: # pragma: no cover
342-
return loop.run_until_complete(next_handler(app))
343-
344-
# Check if middleware is async
345338
if inspect.iscoroutinefunction(middleware):
346-
result = await middleware(app, next_handler)
347-
else:
348-
# Sync middleware - need special handling
349-
result = middleware(app, sync_next)
339+
return await middleware(app, next_handler)
350340

351-
# Check if we stored an async result
352-
if "_async_next_result" in app.context:
353-
future = app.context.pop("_async_next_result")
354-
result = await future
341+
# We use an Event to coordinate: the sync middleware runs in a thread,
342+
# calls sync_next which signals us to resolve the async handler,
343+
# then waits for the real response.
344+
middleware_called_next = asyncio.Event()
345+
next_app_holder: list = []
346+
real_response_holder: list = []
347+
middleware_result_holder: list = []
348+
middleware_error_holder: list = []
355349

356-
return result
350+
def sync_next(app):
351+
next_app_holder.append(app)
352+
middleware_called_next.set()
353+
# Block this thread until the real response is available
354+
event = threading.Event()
355+
next_app_holder.append(event)
356+
event.wait()
357+
return real_response_holder[0]
358+
359+
def run_middleware():
360+
try:
361+
result = middleware(app, sync_next)
362+
middleware_result_holder.append(result)
363+
except Exception as e:
364+
middleware_error_holder.append(e)
365+
366+
thread = threading.Thread(target=run_middleware, daemon=True)
367+
thread.start()
368+
369+
# Wait for the middleware to call next()
370+
await middleware_called_next.wait()
371+
372+
# Now resolve the async next_handler
373+
real_response = await next_handler(next_app_holder[0])
374+
real_response_holder.append(real_response)
375+
376+
# Signal the thread that the response is ready
377+
threading_event = next_app_holder[1]
378+
threading_event.set()
379+
380+
# Wait for the middleware thread to finish
381+
thread.join()
382+
383+
if middleware_error_holder:
384+
raise middleware_error_holder[0]
385+
386+
return middleware_result_holder[0]
357387

358388
return wrapped
359389

poetry.lock

Lines changed: 47 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ kafka-consumer-protobuf = ["protobuf"]
8383
coverage = { extras = ["toml"], version = "^7.6" }
8484
pytest = ">=8.3.4,<10.0.0"
8585
boto3 = "^1.26.164"
86-
isort = ">=5.13.2,<8.0.0"
86+
isort = ">=5.13.2,<9.0.0"
8787
pytest-cov = ">=5,<8"
8888
pytest-mock = "^3.14.0"
8989
pytest-asyncio = ">=0.24,<1.4"
@@ -116,7 +116,7 @@ types-python-dateutil = "^2.8.19.6"
116116
aws-cdk-aws-appsync-alpha = "^2.59.0a0"
117117
httpx = ">=0.23.3,<0.29.0"
118118
sentry-sdk = ">=1.22.2,<3.0.0"
119-
ruff = ">=0.5.1,<0.15.9"
119+
ruff = ">=0.5.1,<0.15.10"
120120
retry2 = "^0.9.5"
121121
pytest-socket = ">=0.6,<0.8"
122122
types-redis = "^4.6.0.7"

0 commit comments

Comments
 (0)