diff --git a/README.rst b/README.rst index 80e2fb0..6923a83 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,12 @@ To deploy it, I'm using gunicorn and supervisord:: Don't forget to set the right permission for your files ! Also, create a `settings.py` file with the appropriate values if you need to -use a different database for instance. +use a different database for instance. You can also set `APPLICATION_ROOT` if +you want to prefix your URLs to serve ihatemonney in the *folder* of a domain, +e.g: + + APPLICATION_ROOT='/budget' + How about the REST API? ======================= diff --git a/budget/default_settings.py b/budget/default_settings.py index 394ab00..c80c783 100644 --- a/budget/default_settings.py +++ b/budget/default_settings.py @@ -4,6 +4,7 @@ SQLACHEMY_ECHO = DEBUG SECRET_KEY = "tralala" MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org") +APPLICATION_ROOT = '/' try: from settings import * diff --git a/budget/run.py b/budget/run.py index 2e18599..bdb8f46 100644 --- a/budget/run.py +++ b/budget/run.py @@ -6,7 +6,7 @@ from raven.contrib.flask import Sentry from web import main, db, mail from api import api - +from utils import PrefixedWSGI app = Flask(__name__) @@ -15,6 +15,7 @@ def configure(): """ A way to (re)configure the app, specially reset the settings """ app.config.from_object("default_settings") + app.wsgi_app = PrefixedWSGI(app) # Deprecations if 'DEFAULT_MAIL_SENDER' in app.config: diff --git a/budget/tests.py b/budget/tests.py index 0f7c2a2..b151e7f 100644 --- a/budget/tests.py +++ b/budget/tests.py @@ -821,5 +821,20 @@ class APITestCase(TestCase): self.assertStatus(404, req) +class ServerTestCase(APITestCase): + def setUp(self): + run.configure() + super(ServerTestCase, self).setUp() + + def test_unprefixed(self): + req = self.app.get("/foo/") + self.assertStatus(303, req) + + def test_prefixed(self): + run.app.config['APPLICATION_ROOT'] = '/foo' + req = self.app.get("/foo/") + self.assertStatus(200, req) + + if __name__ == "__main__": unittest.main() diff --git a/budget/utils.py b/budget/utils.py index 60337fb..7717aaa 100644 --- a/budget/utils.py +++ b/budget/utils.py @@ -32,3 +32,34 @@ class Redirect303(HTTPException, RoutingException): def get_response(self, environ): return redirect(self.new_url, 303) + + +class PrefixedWSGI(object): + ''' + Wrap the application in this middleware and configure the + front-end server to add these headers, to let you quietly bind + this to a URL other than / and to an HTTP scheme that is + different than what is used locally. + + It relies on "APPLICATION_ROOT" app setting. + + Inspired from http://flask.pocoo.org/snippets/35/ + + :param app: the WSGI application + ''' + def __init__(self, app): + self.app = app + self.wsgi_app = app.wsgi_app + + def __call__(self, environ, start_response): + script_name = self.app.config['APPLICATION_ROOT'] + if script_name: + environ['SCRIPT_NAME'] = script_name + path_info = environ['PATH_INFO'] + if path_info.startswith(script_name): + environ['PATH_INFO'] = path_info[len(script_name):] + + scheme = environ.get('HTTP_X_SCHEME', '') + if scheme: + environ['wsgi.url_scheme'] = scheme + return self.wsgi_app(environ, start_response)