Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

How to get the api path in middleware? #486

Closed
devtud opened this issue Aug 30, 2019 · 8 comments
Closed

How to get the api path in middleware? #486

devtud opened this issue Aug 30, 2019 · 8 comments
Labels
question Question or problem question-migrate

Comments

@devtud
Copy link

devtud commented Aug 30, 2019

Description
I want to store some analytics data for each hit in my API.
Among the data I need to extract for each request, I have:

  • response status code (eg 200)
  • the request's matched path (eg /users/{user_id}/books)
  • etc.

I imagined that the best place to do that is in a middleware, after the response is computed:

@app.middleware("http")
async def get_analytics_data(request: Request, call_next):
    response = await call_next(request)
    analytics_data = {
        'response_code': response.status_code,
        'path': ?  # here I need the route path (eg `/users/{user_id}/books`)
    }
    save(analytics_data)
    return response

Inside the middleware I can access the request.url but it's not what I want. The path as I define it in the routes is what I need.

Is there a way to obtain the path of the request in a single place? It doesn't have to happen at the middleware level as long as it is in a single place.

EDIT:

Example:

Let's say I have defined:

@router.get('/users/{user_id}/books')
async def get_user_books(user_id: str):
    return [{book1}, {book2}]
  1. The user hits /users/10/books, so the path /users/{user_id}/books is matched.
  2. In my middleware I need to identify what path had been matched (which, in this case, it's /users/{user_id}/books).
@devtud devtud added the question Question or problem label Aug 30, 2019
@euri10
Copy link
Contributor

euri10 commented Aug 30, 2019 via email

@devtud
Copy link
Author

devtud commented Aug 30, 2019

@euri10 , get_analytics_data is the name of the middleware, so I don't see how could I obtain the route's path from it.

I've slightly updated the description hoping it's a bit more clear.

@devtud
Copy link
Author

devtud commented Sep 1, 2019

A temporary (or not) solution for my need implies using router and endpoint from request.scope. I'm not sure this is an elegant way to do it, but it does the job. Other suggestions are welcomed.

So, to get the matched path of a request, I use this snippet in my middleware.

...
response = await call_next(request)

path = [route for route in request.scope['router'].routes if route.endpoint == request.scope['endpoint']][0].path

print(f'Path is: {path}')
# Path is: /users/{user_id}/books

return response

The downside is that I have to iterate through all the routes for each request, but this can be improved quickly with a cache global variable.

@tiangolo
Copy link
Owner

I'm glad you solved your problem @devtud !

You might also want to check how to add custom classes for APIRoute: https://fastapi.tiangolo.com/advanced/custom-request-and-route/

@github-actions
Copy link
Contributor

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Jun 25, 2020

Is there a way to get the path before the response = await call_next(request) ?

...
response = await call_next(request)

path = [route for route in request.scope['router'].routes if route.endpoint == request.scope['endpoint']][0].path

print(f'Path is: {path}')
# Path is: /users/{user_id}/books

return response

@gwaramadze
Copy link

gwaramadze commented Jan 28, 2021

Hi, and how to figure out the path template if several routes are connected to a single endpoint? dummy example:

    @router.get("/x/{param1}/y/{param2}")
    @router.get("/z/{param3}")
    def endpoint(request: Request):
        # here, how to figure out if it's "/x/{param1}/y/{param2}" or "/z/{param3}"?

is regex my only option?

@james77777778
Copy link

@Kludex @gwaramadze
Hi, I just figure out the solution:

@app.middleware("http")
async def do_something(request: Request, call_next):
    path = request.scope['path']  # get the request route

    if "xxx"  in path:
        do_something_before_path_operation()

    response = await call_next(request)

    if "xxx"  in path:
        do_something_after_path_operation()
    return response

Hope this helps.

@tiangolo tiangolo changed the title [QUESTION] How to get the api path in middleware? How to get the api path in middleware? Feb 24, 2023
@tiangolo tiangolo reopened this Feb 28, 2023
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8044 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests

6 participants