如何在Flask / WSGI服务器中使用应用程序工厂,为什么可能不安全?

15

关于应用可调用性、WSGI服务器和Flask循环导入的问题

我可能有些困惑。我希望能够安全地从应用工厂创建Flask/WSGI应用程序,并且仍然能够轻松地在WSGI服务器中使用它们。

简述:

  1. 我可以安全地避免在init导入时创建应用程序(建议这样做),而是稍后创建吗?

  2. 如何使该应用程序与WSGI服务器协作得好?特别是当我传递配置和其他设置而不是从ENV中提取它们时。

例如:

def make_app(configdict, appname):
    app = Flask(appname)
    app.config.update(configdict)
    init_db(configdict)
    set_app_in_global_namespace(app)

    #importing now will allow from pkg import app        
    from mypackage import views

    return app

我想使用上面的“factory”,因为我希望轻松控制测试等配置。

然后,我可能希望创建一个wsgi.py模块,将应用程序提供给WSGI服务器。

最终,事情看起来有点像这样:

init.py::

app = None

def make_app(configdict, appname):
    flaskapp = Flask(appname)
    flaskapp.config.update(configdict)
    init_db(configdict)

    global app
    app = flaskapp    

    #importing now will allow from pkg import app        
    from mypackage import views

    return flaskapp

wsgi.py::

from mypackage import app

app = make_app(configfromsomewhere, "myname")
uWSGI::
uwsgi --module=mypackage.wsgi:app

但是wsgi.py并不像wsgi.py --settings=x --host=10.0.0.1这样可以调用,所以我不知道如何传递配置信息。

我之所以问这个问题是因为虽然这种方法似乎...还好...但有点混乱。

当所有内容都在ENV中时,生活会更容易。

而且不仅如此:

那么使用应用工厂有哪些不安全的地方

建议在这里给出:http://flask.pocoo.org/docs/patterns/packages

1. the Flask application object creation has to be in the
__init__.py file. That way each module can import it safely and
the __name__ variable will resolve to the correct package.

2. all the view functions (the ones with a route() decorator on
  top) have to be imported in the __init__.py file. Not the object
  itself, but the module it is in. Import the view module after
  the application object is created.

回复2:显然,路由装饰器期望已实例化的应用程序具有特定的功能,否则无法正常运行。没问题。

回复1:好的,我们需要正确设置名称。但是什么是不安全的?为什么?如果未初始化就导入和使用应用程序是否不安全?嗯,它会出错,但这不是不安全的。它是备受瞩目的线程本地存储吗?可能是。但是,如果我从随意的模块中随意挑选应用程序实例,我应该预料到会有麻烦。

影响 - 我们不会从除了视图之外的任何其他地方引用应用程序对象 - 实质上,我们使我们的模块化保持紧凑,并传递字典、错误对象或甚至WebObs。

http://flask.pocoo.org/docs/patterns/appdispatch http://flask.pocoo.org/docs/deploying/#deployment http://flask.pocoo.org/docs/patterns/packages/#larger-applications http://flask.pocoo.org/docs/becomingbig

1个回答

28
根据Flask文档,应用工厂很有用,因为:
  1. 测试。你可以拥有不同配置的应用实例来测试每种情况。

  2. 多个实例。想象一下,你想运行相同应用程序的不同版本。当然,你可以在web服务器中设置具有不同配置的多个实例,但如果使用工厂,您可以在同一应用程序进程中运行相同应用程序的多个实例,这可能会很方便。

但是,如其他测试技巧部分所述,如果使用应用工厂,则不会自动调用函数before_request()after_request()
在接下来的几段中,我将展示如何使用应用工厂模式与uWSGI应用程序服务器和nginx(我只使用过这些,但我可以尝试帮助您在其他服务器上进行配置)。

应用工厂

假设你的应用程序位于文件夹yourapplication内,其中有__init__.py文件:
import os
from flask import Flask

def create_app(cfg=None):
    app = Flask(__name__)

    load_config(app, cfg)

    # import all route modules
    # and register blueprints

    return app

def load_config(app, cfg):
    # Load a default configuration file
    app.config.from_pyfile('config/default.cfg')

    # If cfg is empty try to load config file from environment variable
    if cfg is None and 'YOURAPPLICATION_CFG' in os.environ:
        cfg = os.environ['YOURAPPLICATION_CFG']

    if cfg is not None:
        app.config.from_pyfile(cfg)

现在你需要一个文件来创建应用实例:
from yourapplication import create_app

app = create_app()

if __name__ == "__main__":
    app.run()

在上面的代码中,我假设已经设置了一个环境变量,其中包含配置文件的路径,但是您也可以将配置文件路径传递给工厂,如下所示:
app = create_app('config/prod.cfg')

或者,你可以使用一个包含环境和相应配置文件的字典:

CONFIG_FILES = {'development': 'config/development.cfg',
                'test'       : 'config/test.cfg',
                'production' : 'config/production.cfg' }

在这种情况下,load_config 函数应该像下面这样:

def load_config(app, env):
    app.config.from_pyfile('config/default.cfg')

    var = "YOURAPPLICATION_ENV"
    if env is None and var in os.environ:
        env = os.environ[var]

    if env in CONFIG_FILES:
        app.config.from_pyfile(CONFIG_FILES[env])

Nginx和uWSGI

以下是一个nginx配置文件的示例:

server {
    listen             80;
    server_name        yourapplication.com;
    access_log         /var/www/yourapplication/logs/access.log;
    error_log          /var/www/yourapplication/logs/error.log;

    location / {
        try_files $uri @flask;
    }

    location @flask {
        include        uwsgi_params;
        uwsgi_pass     unix:/tmp/yourapplication.sock;

        # /env is the virtualenv directory
        uwsgi_param    UWSGI_PYHOME                /var/www/yourapplication/env;

        # the path where the module run is located
        uwsgi_param    UWSGI_CHDIR                 /var/www/yourapplication;

        # the name of the module to be called
        uwsgi_param    UWSGI_MODULE                run;

        # the variable declared in the run module, an instance of Flask
        uwsgi_param    UWSGI_CALLABLE              app;
    }
}

而uWSGI配置文件的外观如下:

[uwsgi]
plugins=python
vhost=true
socket=/tmp/yourapplication.sock
env = YOURAPPLICATION_ENV=production
logto = /var/www/yourapplication/logs/uwsgi.log

如何使用before_request()after_request()

使用这些函数的问题在于,如果你在其他模块中调用它们,则必须在应用程序实例化之前导入这些模块。再次提醒,文档有关于此的说明:

缺点是您无法在导入蓝图时使用应用程序对象。但是,可以在请求中使用它。如何访问具有配置的应用程序?请使用current_app:

from flask import current_app, Blueprint, render_template
admin = Blueprint('admin', __name__, url_prefix='/admin')

@admin.route('/')
def index():
    return render_template(current_app.config['INDEX_TEMPLATE'])

你也可以考虑创建一个扩展(extension),然后就可以在没有现有Flask实例的情况下导入类,因为类扩展(extension)只会在被创建之后使用Flask实例。


所以,刚刚意识到这一点 - 我可以通过使用current_app在其他位置使用应用程序 -> 这就是带有请求的应用程序。 这似乎是一个很长的方法 - 我可能会避免路由装饰器,完全手动设置路由 - 然后我可以忽略导入顺序 - 我想是这样的? - lifeisstillgood
1
你可以使用类来代替带有装饰器的函数作为你的视图。可以参考这个代码片段Pluggable Views文档Flask-Views项目。此外,还可以查看flask-skeletonflask-boilerplate以获取如何组织应用程序的一些想法。 - mayconbordin
1
请注意,如果您正在使用测试请求上下文,则不会自动调用before_request()函数,同样适用于after_request()函数。没有before/after请求函数适用于测试请求上下文,而不是工厂函数。但是,您仍然需要将其挂钩到工厂或在实例化应用程序后定义它们。 - justanr
在设置其他扩展(如Flask-Admin)时,使用应用程序配置怎么样?例如,如果我想要我的应用程序配置的值来决定管理员视图是否呈现编辑选项(can_create = current_app.config.get('SOME_CONFIG')),或者对于可以从多个地方调用但需要应用程序配置的实用函数?在这种情况下,current_app为空,因为我们在请求上下文之外。有没有办法处理这种情况? - RobertoCuba
@RobertoCuba 请查看此应用程序文件 - mayconbordin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接