dispatcher.py 2.5 KB

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