应用调度¶

应用调度是在 WSGI 层面组合多个 Flask 应用的过程。可以组合多个 Flask 应用,也可以组合 Flask 应用和其他 WSGI 应用。通过这种组合,如果有必要的话,甚至可以在同一个解释器中一边运行 Django ,一边运行 Flask 。这种组合的好处取决于应用内部是如何工作的。

应用调度与 模块化 的最大不同在于应用调度中的每个应用是完全独立的,它们以各自的配置运行,并在 WSGI 层面被调度。

说明¶

下面所有的技术说明和举例都归结于一个可以运行于任何 WSGI 服务器的application 对象。对于生产环境,参见 部署方式 。对于开发环境,Werkzeug 提供了一个内建开发服务器,它使用werkzeug.serving.run_simple() 来运行:

  1. from werkzeug.serving import run_simple
  2. run_simple('localhost', 5000, application, use_reloader=True)

注意 run_simple 不能用于生产环境,生产环境服务器参见 成熟的 WSGI 服务器

为了使用交互调试器,应用和简单服务器都应当处于调试模式。下面是一个简单的“ hello world ”示例,使用了调试模式和run_simple:

  1. from flask import Flask
  2. from werkzeug.serving import run_simple
  3.  
  4. app = Flask(__name__)
  5. app.debug = True
  6.  
  7. @app.route('/')
  8. def hello_world():
  9. return 'Hello World!'
  10.  
  11. if __name__ == '__main__':
  12. run_simple('localhost', 5000, app,
  13. use_reloader=True, use_debugger=True, use_evalex=True)

组合应用¶

如果你想在同一个 Python 解释器中运行多个独立的应用,那么你可以使用werkzeug.wsgi.DispatcherMiddleware 。其原理是:每个独立的 Flask应用都是一个合法的 WSGI 应用,它们通过调度中间件组合为一个基于前缀调度的大应用。

假设你的主应用运行于 / ,后台接口位于 /backend:

  1. from werkzeug.wsgi import DispatcherMiddleware
  2. from frontend_app import application as frontend
  3. from backend_app import application as backend
  4.  
  5. application = DispatcherMiddleware(frontend, {
  6. '/backend': backend
  7. })

根据子域调度¶

有时候你可能需要使用不同的配置来运行同一个应用的多个实例。可以把应用创建过程放在一个函数中,这样调用这个函数就可以创建一个应用的实例,具体实现参见应用工厂 方案。

最常见的做法是每个子域创建一个应用,配置服务器来调度所有子域的应用请求,使用子域来创建用户自定义的实例。一旦你的服务器可以监听所有子域,那么就可以使用一个很简单的 WSGI 应用来动态创建应用了。

WSGI 层是完美的抽象层,因此可以写一个你自己的 WSGI 应用来监视请求,并把请求分配给你的 Flask 应用。如果被分配的应用还没有创建,那么就会动态创建应用并被登记下来:

  1. from threading import Lock
  2.  
  3. class SubdomainDispatcher(object):
  4.  
  5. def __init__(self, domain, create_app):
  6. self.domain = domain
  7. self.create_app = create_app
  8. self.lock = Lock()
  9. self.instances = {}
  10.  
  11. def get_application(self, host):
  12. host = host.split(':')[0]
  13. assert host.endswith(self.domain), 'Configuration error'
  14. subdomain = host[:-len(self.domain)].rstrip('.')
  15. with self.lock:
  16. app = self.instances.get(subdomain)
  17. if app is None:
  18. app = self.create_app(subdomain)
  19. self.instances[subdomain] = app
  20. return app
  21.  
  22. def __call__(self, environ, start_response):
  23. app = self.get_application(environ['HTTP_HOST'])
  24. return app(environ, start_response)

调度器示例:

  1. from myapplication import create_app, get_user_for_subdomain
  2. from werkzeug.exceptions import NotFound
  3.  
  4. def make_app(subdomain):
  5. user = get_user_for_subdomain(subdomain)
  6. if user is None:
  7. # 如果子域没有对应的用户,那么还是得返回一个 WSGI 应用
  8. # 用于处理请求。这里我们把 NotFound() 异常作为应用返回,
  9. # 它会被渲染为一个缺省的 404 页面。然后,可能还需要把
  10. # 用户重定向到主页。
  11. return NotFound()
  12.  
  13. # 否则为特定用户创建应用
  14. return create_app(user)
  15.  
  16. application = SubdomainDispatcher('example.com', make_app)

根据路径调度¶

根据 URL 的路径调度非常简单。上面,我们通过查找 Host 头来判断子域,现在只要查找请求路径的第一个斜杠之前的路径就可以了:

  1. from threading import Lock
  2. from werkzeug.wsgi import pop_path_info, peek_path_info
  3.  
  4. class PathDispatcher(object):
  5.  
  6. def __init__(self, default_app, create_app):
  7. self.default_app = default_app
  8. self.create_app = create_app
  9. self.lock = Lock()
  10. self.instances = {}
  11.  
  12. def get_application(self, prefix):
  13. with self.lock:
  14. app = self.instances.get(prefix)
  15. if app is None:
  16. app = self.create_app(prefix)
  17. if app is not None:
  18. self.instances[prefix] = app
  19. return app
  20.  
  21. def __call__(self, environ, start_response):
  22. app = self.get_application(peek_path_info(environ))
  23. if app is not None:
  24. pop_path_info(environ)
  25. else:
  26. app = self.default_app
  27. return app(environ, start_response)

与根据子域调度相比最大的不同是:根据路径调度时,如果创建函数返回None ,那么就会回落到另一个应用:

  1. from myapplication import create_app, default_app, get_user_for_prefix
  2.  
  3. def make_app(prefix):
  4. user = get_user_for_prefix(prefix)
  5. if user is not None:
  6. return create_app(user)
  7.  
  8. application = PathDispatcher(default_app, make_app)

原文: https://dormousehole.readthedocs.io/en/latest/patterns/appdispatch.html