1""" 2Application Dispatcher 3====================== 4 5This middleware creates a single WSGI application that dispatches to 6multiple other WSGI applications mounted at different URL paths. 7 8A common example is writing a Single Page Application, where you have a 9backend API and a frontend written in JavaScript that does the routing 10in the browser rather than requesting different pages from the server. 11The frontend is a single HTML and JS file that should be served for any 12path besides "/api". 13 14This example dispatches to an API app under "/api", an admin app 15under "/admin", and an app that serves frontend files for all other 16requests:: 17 18 app = DispatcherMiddleware(serve_frontend, { 19 '/api': api_app, 20 '/admin': admin_app, 21 }) 22 23In production, you might instead handle this at the HTTP server level, 24serving files or proxying to application servers based on location. The 25API and admin apps would each be deployed with a separate WSGI server, 26and the static files would be served directly by the HTTP server. 27 28.. autoclass:: DispatcherMiddleware 29 30:copyright: 2007 Pallets 31:license: BSD-3-Clause 32""" 33import typing as t 34 35if t.TYPE_CHECKING: 36 from _typeshed.wsgi import StartResponse 37 from _typeshed.wsgi import WSGIApplication 38 from _typeshed.wsgi import WSGIEnvironment 39 40 41class DispatcherMiddleware: 42 """Combine multiple applications as a single WSGI application. 43 Requests are dispatched to an application based on the path it is 44 mounted under. 45 46 :param app: The WSGI application to dispatch to if the request 47 doesn't match a mounted path. 48 :param mounts: Maps path prefixes to applications for dispatching. 49 """ 50 51 def __init__( 52 self, 53 app: "WSGIApplication", 54 mounts: t.Optional[t.Dict[str, "WSGIApplication"]] = None, 55 ) -> None: 56 self.app = app 57 self.mounts = mounts or {} 58 59 def __call__( 60 self, environ: "WSGIEnvironment", start_response: "StartResponse" 61 ) -> t.Iterable[bytes]: 62 script = environ.get("PATH_INFO", "") 63 path_info = "" 64 65 while "/" in script: 66 if script in self.mounts: 67 app = self.mounts[script] 68 break 69 70 script, last_item = script.rsplit("/", 1) 71 path_info = f"/{last_item}{path_info}" 72 else: 73 app = self.mounts.get(script, self.app) 74 75 original_script_name = environ.get("SCRIPT_NAME", "") 76 environ["SCRIPT_NAME"] = original_script_name + script 77 environ["PATH_INFO"] = path_info 78 return app(environ, start_response) 79