flask多应用挂载:DispatcherMiddleware

问题背景

有一段祖传代码,近期需要重构部分接口,会对整体代码结构有很大改动, 于是按照独立的flask 构建了新版本的接口通过DispatcherMiddleware 进行整合.

样例代码如下

# demo.py
from flask import Flask
from werkzeug.middleware.dispatcher import DispatcherMiddleware

api_v1 = Flask("api_v1")
api_v2 = Flask("api_v2")
app = DispatcherMiddleware(api_v1, {"/v2": api_v2})

# .flaskenv
FLASK_APP=demo:api_v1

本地调试 使用flask run 运行服务

Error: A valid Flask application was not obtained from "demo:app"

flask run 会去找Flask对象来执行,上面的DispatcherMiddleware 已经不是.

先看看DispatcherMiddleware代码:

class DispatcherMiddleware(object):
    """Combine multiple applications as a single WSGI application.
    Requests are dispatched to an application based on the path it is
    mounted under.

    :param app: The WSGI application to dispatch to if the request
        doesn't match a mounted path.
    :param mounts: Maps path prefixes to applications for dispatching.
    """

    def __init__(self, app, mounts=None):
        self.app = app
        self.mounts = mounts or {}

    def __call__(self, environ, start_response):
        script = environ.get("PATH_INFO", "")
        path_info = ""

        while "/" in script:
            if script in self.mounts:
                app = self.mounts[script]
                break

            script, last_item = script.rsplit("/", 1)
            path_info = "/%s%s" % (last_item, path_info)
        else:
            app = self.mounts.get(script, self.app)

        original_script_name = environ.get("SCRIPT_NAME", "")
        environ["SCRIPT_NAME"] = original_script_name + script
        environ["PATH_INFO"] = path_info
        return app(environ, start_response)

翻看 Flask代码:

class Flask(_PackageBoundObject):
	# 此处省略N行....
    def wsgi_app(self, environ, start_response):
        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. Instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.
	   """
	   pass
    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)

下面这段话 指出了挂载应用的正确姿势app.wsgi_app = MyMiddleware(app.wsgi_app), 这样我们可以继续使用原始的app 对象

        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. Instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.
	   """

最后的代码如下:

from flask import Flask
from werkzeug.middleware.dispatcher import DispatcherMiddleware


api_v1 = Flask("api_v1")
api_v2 = Flask("api_v2")
# app = DispatcherMiddleware(api_v1, {"/v2": api_v2})

api_v1.wsgi_app=DispatcherMiddleware(api_v1.wsgi_app, {"/v2": api_v2})
#api_v1.run()

运行 flask run 服务正常

参考

  1. https://github.com/pallets/flask/issues/3256
  2. https://stackoverflow.com/questions/52483686/why-do-i-get-the-error-a-valid-flask-application-was-not-obtained-from-when
  3. https://flask.palletsprojects.com/en/1.0.x/api/#flask.Flask.wsgi_app
posted @ 2021-05-12 21:34  码上的生活  阅读(762)  评论(0编辑  收藏  举报