Skip to content

Instantly share code, notes, and snippets.

@eipiminus1
Created September 26, 2017 21:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eipiminus1/3741b3c51c226d043e55e3c7555e1cbd to your computer and use it in GitHub Desktop.
Save eipiminus1/3741b3c51c226d043e55e3c7555e1cbd to your computer and use it in GitHub Desktop.
Internal redirects in Flask

Internal redirects in Flask

Goal: Instead of redirecting a client to a new path, the request should be redirected internally. The response should be returned without changing the clients (browser) URL.

Simple internal redirect

The simplest way I found to achieve internal redirecting is by calling the app.full_dispatch_request with a modified context.

def internal_redirect(path):
    env = request.environ  # take the original environment object
    env['PATH_INFO'] = path  # replace PATH_INFO with the new path
    with landing.request_context(env):  # push new request_context to stack
        response = landing.full_dispatch_request()  # handle original request with new path
    return response

You could probably create more complex scenarios by manually pushing and popping the context.

env = request.environ  # take the original environment object
env['PATH_INFO'] = path  # replace PATH_INFO with the new path
ctx = landing.request_context(env)  # create new request_context
ctx.push()  # make new context active by pushing
response = some_view_function(foo='bar')  # render view within original request but with new path
ctx.pop()  # destroy new context
return response

Background: Flask request handling

This is my understanding of how Flask handles requests. Most of it comes from:

  1. The Flask app creates an app.request_context(environ) based on an environment variable that is passed from the WSGI server.
  2. The ctx = request_context() is pushed to the request_context_stack via ctx.push(). Now ctx is the currently active context.
  3. The request is processed by the Flask app calling the app.full_dispatch_request() function (plus some error handling).
  4. While app.dispatch_request would only call the view_function, full_dispatch_request also does the following:
    1. Before each request, before_request() functions are executed. If one of these functions return a response, the other functions are no longer called. In any case however the return value is treated as a replacement for the view’s return value.
    2. If the before_request() functions did not return a response, the regular request handling (app.dispatch_request) kicks in and the view function that was matched has the chance to return a response.
    3. The return value of the view is then converted into an actual response object and handed over to the after_request()functions which have the chance to replace it or modify it in place.
    4. At the end of the request the teardown_request()functions are executed. This always happens, even in case of an unhandled exception down the road or if a before-request handler was not executed yet or at all (for example in test environments sometimes you might want to not execute before-request callbacks).
def internal_redirect(path):
env = request.environ # take the original environment object
env['PATH_INFO'] = path # replace PATH_INFO with the new path
with landing.request_context(env): # push new request_context to stack
response = landing.full_dispatch_request() # handle original request with new path
return response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment