Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Serving Flask under a subpath

Your Flask app object implements the __call__ method, which means it can be called like a regular function. When your WSGI container receives a HTTP request it calls your app with the environ dict and the start_response callable. WSGI is specified in PEP 0333. The two relevant environ variables are:

SCRIPT_NAME
The initial portion of the request URL's "path" that corresponds to the application object, so that the application knows its virtual "location". This may be an empty string, if the application corresponds to the "root" of the server.

PATH_INFO
The remainder of the request URL's "path", designating the virtual "location" of the request's target within the application. This may be an empty string, if the request URL targets the application root and does not have a trailing slash.

Flask's routing and url_for are provided by Werkzeug. Werkzeug's routing operates on PATH_INFO and its url_for function prepends SCRIPT_NAME.

Conclusion

This means that if your application isn't located at the root of your server but under a path you have to tell your WSGI container the env SCRIPT_NAME. It will then split incoming request paths into SCRIPT_NAME and PATH_INFO.

In production
How you do this depends on your deployment option. Eg. with Gunicorn you can pass environment variables with -e.

gunicorn -e SCRIPT_NAME=/my-app my_app:app

With Flask's dev server
Flask's builtin development server app.run() uses werkzeug.serving.run_simple(). In order to use the convenient debugging and reloading features while still serving the site under a SCRIPT_NAME you would have to use a WSGI middleware that sets the SCRIPT_NAME and trims the PATH_INFO.

from werkzeug.serving import run_simple
from my_app import app

class FixScriptName(object):
	def __init__(self, app):
		self.app = app

	def __call__(self, environ, start_response):
		SCRIPT_NAME = '/my-app'
		
		if environ['PATH_INFO'].startswith(SCRIPT_NAME):
			environ['PATH_INFO'] = environ['PATH_INFO'][len(SCRIPT_NAME):]
			environ['SCRIPT_NAME'] = SCRIPT_NAME
			return self.app(environ, start_response)
		else:
			start_response('404', [('Content-Type', 'text/plain')])
			return ["This doesn't get served by your FixScriptName middleware.".encode()]

app = FixScriptName(app)

run_simple('0.0.0.0', 5000, app, use_reloader=True)
@roemhildtg

This comment has been minimized.

Copy link

@roemhildtg roemhildtg commented Nov 22, 2016

Thanks for posting this, this has solved 90% of my issues. In a few cases, I have some code like this though, where the redirect is not working:

from flask import request, redirect, url_for
@route('/route')
def route():
    return redirect(url_for('login', next=request.full_path)

It turns out, request.full_path does not have the prefix in the value. It looks like this: /route when it should be /prefix/route.

What's the best way to handle this scenario?

Edit:

I guess this is solvable by using request.url. Not sure if its the best, but it works :)

@kylerlaird

This comment has been minimized.

Copy link

@kylerlaird kylerlaird commented Sep 1, 2020

Thank you!!! This was a huge help for me. (I'm trying to get a Flask app running.)

@kevenv

This comment has been minimized.

Copy link

@kevenv kevenv commented Sep 30, 2020

Thanks, it was not obvious to me that you have to run this using python my_app.py instead of the usual flask run.

@m3m0ry

This comment has been minimized.

Copy link

@m3m0ry m3m0ry commented Dec 22, 2020

I have a simpler solution. Make your app a blueprint. Use app.register_blueprint(your_blueprint, url_prefix=YOUR_PREFIX)

Or see here: https://stackoverflow.com/a/18969161/2329365

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment