Skip to content

Instantly share code, notes, and snippets.

@Larivact
Last active August 18, 2024 16:33
Show Gist options
  • Save Larivact/1ee3bad0e53b2e2c4e40 to your computer and use it in GitHub Desktop.
Save Larivact/1ee3bad0e53b2e2c4e40 to your computer and use it in GitHub Desktop.

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)
@kevenv
Copy link

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
Copy link

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

@kopp
Copy link

kopp commented Mar 28, 2022

Hi @Larivact , could you please specify a license (ideally a permissive one like MIT) under which this code is available?

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