Search code examples
pythonflaskwsgi

Flask Middleware with both Request and Response


I want to create a middleware function in Flask that logs details from the request and the response. The middleware should run after the Response is created, but before it is sent back. I want to log:

  1. The request's HTTP method (GET, POST, or PUT)
  2. The request endpoint
  3. The response HTTP status code, including 500 responses. So, if an exception is raised in the view function, I want to record the resulting 500 Response before the Flask internals send it off.

Some options I've found (that don't quite work for me):

  1. The before_request and after_request decorators. If I could access the request data in after_request, my problems still won't be solved, because according to the documentation

If a function raises an exception, any remaining after_request functions will not be called.

  1. Deferred Request Callbacks - there is an after_this_request decorator described on this page, which decorates an arbitrary function (defined inside the current view function) and registers it to run after the current request. Since the arbitrary function can have info from both the request and response in it, it partially solves my problem. The catch is that I would have to add such a decorated function to every view function; a situation I would very much like to avoid.
@app.route('/')
def index():
    @after_this_request
    def add_header(response):
        response.headers['X-Foo'] = 'Parachute'
        return response
    return 'Hello World!'

Any suggestions?


Solution

  • My first answer is very hacky. There's actually a much better way to achieve the same result by making use of the g object in Flask. It is useful for storing information globally during a single request. From the documentation:

    The g name stands for “global”, but that is referring to the data being global within a context. The data on g is lost after the context ends, and it is not an appropriate place to store data between requests. Use the session or a database to store data across requests.

    This is how you would use it:

    @app.before_request
    def gather_request_data():
        g.method = request.method
        g.url = request.url
    
    @app.after_request
    def log_details(response: Response):
        g.status = response.status
    
        logger.info(f'method: {g.method}\n url: {g.url}\n status: {g.status}')
    
        return response
    
    1. Gather whatever request information you want in the function decorated with @app.before_request and store it in the g object.
    2. Access whatever you want from the response in the function decorated with @app.after_request. You can still refer to the information you stored in the g object from step 1. Note that you'll have to return the response at the end of this function.