Skip to content

Commit dd75cc9

Browse files
committed
Merge branch 'appdispatch-docs'
2 parents fbd4886 + 97efffa commit dd75cc9

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

docs/patterns/appdispatch.rst

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
.. _app-dispatch:
2+
3+
Application Dispatching
4+
=======================
5+
6+
Application dispatching is the process of combining multiple Flask
7+
applications on the WSGI level. You can not only combine Flask
8+
applications into something larger but any WSGI application. This would
9+
even allow you to run a Django and a Flask application in the same
10+
interpreter side by side if you want. The usefulness of this depends on
11+
how the applications work internally.
12+
13+
The fundamental difference from the :ref:`module approach
14+
<larger-applications>` is that in this case you are running the same or
15+
different Flask applications that are entirely isolated from each other.
16+
They run different configurations and are dispatched on the WSGI level.
17+
18+
Combining Applications
19+
----------------------
20+
21+
If you have entirely separated applications and you want them to work next
22+
to each other in the same Python interpreter process you can take
23+
advantage of the :class:`werkzeug.wsgi.DispatcherMiddleware`. The idea
24+
here is that each Flask application is a valid WSGI application and they
25+
are combined by the dispatcher middleware into a larger one that
26+
dispatched based on prefix.
27+
28+
For example you could have your main application run on `/` and your
29+
backend interface on `/admin`::
30+
31+
from werkzeug.wsgi import DispatcherMiddleware
32+
from frontend_app import application as frontend
33+
from backend_app import application as backend
34+
35+
application = DispatcherMiddleware(frontend, {
36+
'/backend': backend
37+
})
38+
39+
40+
Dispatch by Subdomain
41+
---------------------
42+
43+
Sometimes you might want to use multiple instances of the same application
44+
with different configurations. Assuming the application is created inside
45+
a function and you can call that function to instanciate it, that is
46+
really easy to implement. In order to develop your application to support
47+
creating new instances in functions have a look at the
48+
:ref:`app-factories` pattern.
49+
50+
A very common example would be creating applications per subdomain. For
51+
instance you configure your webserver to dispatch all requests for all
52+
subdomains to your application and you then use the subdomain information
53+
to create user-specific instances. Once you have your server set up to
54+
listen on all subdomains you can use a very simple WSGI application to do
55+
the dynamic application creation.
56+
57+
The perfect level for abstraction in that regard is the WSGI layer. You
58+
write your own WSGI application that looks at the request that comes and
59+
and delegates it to your Flask application. If that application does not
60+
exist yet, it is dynamically created and remembered::
61+
62+
from threading import Lock
63+
64+
class SubdomainDispatcher(object):
65+
66+
def __init__(self, domain, create_app):
67+
self.domain = domain
68+
self.create_app = create_app
69+
self.lock = Lock()
70+
self.instances = {}
71+
72+
def get_application(self, host):
73+
host = host.split(':')[0]
74+
assert host.endswith(self.domain), 'Configuration error'
75+
subdomain = host[:-len(self.domain)].rstrip('.')
76+
with self.lock:
77+
app = self.instances.get(subdomain)
78+
if app is None:
79+
app = self.create_app(subdomain)
80+
self.instances[subdomain] = app
81+
return app
82+
83+
def __call__(self, environ, start_response):
84+
app = self.get_application(environ['HTTP_HOST'])
85+
return app(environ, start_response)
86+
87+
88+
This dispatcher can then be used like this::
89+
90+
from myapplication import create_app, get_user_for_subdomain
91+
from werkzeug.exceptions import NotFound
92+
93+
def make_app(subdomain):
94+
user = get_user_for_subdomain(subdomain)
95+
if user is None:
96+
# if there is no user for that subdomain we still have
97+
# to return a WSGI application that handles that request.
98+
# We can then just return the NotFound() exception as
99+
# application which will render a default 404 page.
100+
# You might also redirect the user to the main page then
101+
return NotFound()
102+
103+
# otherwise create the application for the specific user
104+
return create_app(user)
105+
106+
application = SubdomainDispatcher('example.com', make_app)
107+
108+
109+
Dispatch by Path
110+
----------------
111+
112+
Dispatching by a path on the URL is very similar. Instead of looking at
113+
the `Host` header to figure out the subdomain one simply looks at the
114+
request path up to the first slash::
115+
116+
from threading import Lock
117+
from werkzeug.wsgi import pop_path_info, peek_path_info
118+
119+
class PathDispatcher(object):
120+
121+
def __init__(self, default_app, create_app):
122+
self.default_app = default_app
123+
self.create_app = create_app
124+
self.lock = Lock()
125+
self.instances = {}
126+
127+
def get_application(self, prefix):
128+
with self.lock:
129+
app = self.instances.get(prefix)
130+
if app is None:
131+
app = self.create_app(prefix)
132+
if app is not None:
133+
self.instances[prefix] = app
134+
return app
135+
136+
def __call__(self, environ, start_response):
137+
app = self.get_application(peek_path_info(environ))
138+
if app is not None:
139+
pop_path_info(environ)
140+
else:
141+
app = self.default_app
142+
return app(environ, start_response)
143+
144+
The big difference between this and the subdomain one is that this one
145+
falls back to another application if the creator function returns `None`::
146+
147+
from myapplication import create_app, default_app, get_user_for_prefix
148+
149+
def make_app(prefix):
150+
user = get_user_for_prefix(prefix)
151+
if user is not None:
152+
return create_app(user)
153+
154+
application = PathDispatcher('example.com', default_app, make_app)

docs/patterns/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
1818

1919
packages
2020
appfactories
21+
appdispatch
2122
distribute
2223
fabric
2324
sqlite3

tests/flask_tests.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ def has_encoding(name):
4747
def catch_warnings():
4848
"""Catch warnings in a with block in a list"""
4949
import warnings
50+
51+
# make sure deprecation warnings are active in tests
52+
warnings.simplefilter('default', category=DeprecationWarning)
53+
5054
filters = warnings.filters
5155
warnings.filters = filters[:]
5256
old_showwarning = warnings.showwarning

0 commit comments

Comments
 (0)