Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Event handler middleware not triggered for not found routes #3916

Closed
tomsimpkins opened this issue Mar 8, 2024 · 12 comments · Fixed by #4492
Closed

Bug: Event handler middleware not triggered for not found routes #3916

tomsimpkins opened this issue Mar 8, 2024 · 12 comments · Fixed by #4492
Assignees
Labels
bug Something isn't working event_handlers

Comments

@tomsimpkins
Copy link

Expected Behaviour

I think I would expect (global) middleware to be triggered when a not-found route is used. I have middleware that implements desirable behaviour even when the route is not found.

Current Behaviour

Currently global middleware seems to be bypassed when a not found route is hit.

Code snippet

from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response
from aws_lambda_powertools.event_handler.middlewares import NextMiddleware


def test_resolve():
    def log_middleware(
        app: APIGatewayRestResolver, next_middleware: NextMiddleware
    ) -> Response:
        print("middleware before")
        return next_middleware(app)

    app = APIGatewayRestResolver()
    app.use(middlewares=[log_middleware])

    @app.get("/listItems")
    def list_items():
        return [{"id": 1}, {"id": 2}]

    @app.not_found
    def not_found(exc):
        return Response(status_code=404)

    # calling this triggers middleware
    app.resolve(
        {
            "path": "/listItems",
            "httpMethod": "GET",
            "requestContext": {},
        },
        {},
    )

    # calling this does not trigger middleware
    app.resolve(
        {
            "path": "/junk",
            "httpMethod": "GET",
            "requestContext": {},
        },
        {},
    )

Possible Solution

The solution I've used currently is to use catch all route handlers to sweep up any unexpected routes, which looks to app like a "valid" route. This means middleware is triggered.

Another solution would be to trigger global middleware before route resolution.

Steps to Reproduce

I think I've supplied enough context. The above code snippet should run under pytest -rP Please let me know if more is needed.

Powertools for AWS Lambda (Python) version

latest

AWS Lambda function runtime

3.11

Packaging format used

PyPi

Debugging logs

No response

@tomsimpkins tomsimpkins added bug Something isn't working triage Pending triage from maintainers labels Mar 8, 2024
Copy link

boring-cyborg bot commented Mar 8, 2024

Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

@leandrodamascena
Copy link
Contributor

Hi @tomsimpkins! Thanks for raising this issue and identifying this bug. We should process the global middlewares even when invoking not_found method.

Do you want to submit a PR to fix this? We would love to have your contribution here. Please let us know.

Thanks.

@leandrodamascena leandrodamascena self-assigned this Mar 12, 2024
@leandrodamascena leandrodamascena removed the triage Pending triage from maintainers label Mar 12, 2024
@tomsimpkins
Copy link
Author

Hi @leandrodamascena thanks for getting back. I can't dive into this immediately but I've done some reading and think I know how this might be solved. Please let me know if you agree/disagree.

I'd propose modelling the not found handler itself as middleware. So in the resolver we would have:

def _not_found(self, method: str):
   def not_found_middleware(app, next_middleware):
       # call handler for not found here and return Response
    
    middleware_stack = not_found_middleware
    for handler in reversed(self._router_middlewares + [_registered_api_adapter]):
        middleware_stack = MiddlewareFrame(current_middleware=handler, next_middleware=middleware_stack)
  
    return ResponseBuilder(middleware_stack(self))

The same issue seems to apply with the exception handlers. I think we could provide the exception handler as a middleware to the Route as the last element of the _route_middlewares:

response=self._to_response(
    route(router_middlewares=self._router_middlewares + [exception_handler_as_middleware], app=self, route_arguments=route_arguments),
),

There's definitely benefit to reusing the logic from Route, so I prefer the answer for the exception handlers to the not found route because of the reuse. cors for example has to be duplicated in the not found case. I considered making a NotFoundRoute which implements the overlapping functionality that we'd want so there's symmetry, but I don't know the codebase well enough to make that change.

@heitorlessa
Copy link
Contributor

resurfacing to @leandrodamascena for later this week. I'm gonna tackle as many other issues as possible today.

@leandrodamascena leandrodamascena moved this from Triage to Working on it in Powertools for AWS Lambda (Python) May 29, 2024
@heitorlessa heitorlessa moved this from Working on it to Next iteration in Powertools for AWS Lambda (Python) Jun 11, 2024
@leandrodamascena
Copy link
Contributor

Assigning to @heitorlessa!

@heitorlessa
Copy link
Contributor

hey @tomsimpkins tks a lot for the patience! I'm gonna start poking at it today - we may not have it ready for this Thursday's release (13th) but we might be able to the next one (27th); we do it bi-weekly now.

@heitorlessa heitorlessa moved this from Next iteration to Working on it in Powertools for AWS Lambda (Python) Jun 11, 2024
@heitorlessa
Copy link
Contributor

heitorlessa commented Jun 11, 2024

read up the context now. My concern with turning them into a middleware is the potential side effects of short-circuits, where either middleware (404 / exception handler) could be skipped and crash. We wrote about "rules" when authoring middleware but it's hard to contain.

Thank you for providing tests, it's not every day that we have good submissions like yours. I'll keep you posted on how the investigation goes.

Edit: hmm I forgot how coupled the 404+Cors logic is due to not-found routes requiring CORS too to avoid frontend trouble. It might be a great idea to move CORS to a middleware as part of this.

@heitorlessa
Copy link
Contributor

Found an intermediary solution. I'll work late today to be sure it covers edge cases while allowing middleware chain and exception handler to work.

@heitorlessa
Copy link
Contributor

Got it all working along with edge cases, and all 311+ functional tests pass. I'll create additional tests to be extra sure it doesn't break exotic resolvers like Bedrock Agent - if it passes, we can include it in tomorrow's release 🎉🎉

Copy link
Contributor

⚠️COMMENT VISIBILITY WARNING⚠️

This issue is now closed. Please be mindful that future comments are hard for our team to see.

If you need more assistance, please either tag a team member or open a new issue that references this one.

If you wish to keep having a conversation with other community members under this issue feel free to do so.

@github-actions github-actions bot added the pending-release Fix or implementation already in dev waiting to be released label Jun 12, 2024
@heitorlessa
Copy link
Contributor

hey @tomsimpkins, thank you for your patience - we're releasing this today <3

Copy link
Contributor

This is now released under 2.39.0 version!

@github-actions github-actions bot removed the pending-release Fix or implementation already in dev waiting to be released label Jun 13, 2024
@heitorlessa heitorlessa moved this from Coming soon to Shipped in Powertools for AWS Lambda (Python) Jun 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working event_handlers
Projects
Status: Shipped
3 participants