【问题标题】:Loading global data for server using Flask and gunicorn使用 Flask 和 gunicorn 为服务器加载全局数据
【发布时间】:2015-02-24 17:44:00
【问题描述】:

我已经使用 Flask 框架实现了简单的 API,现在我正在尝试将其部署到 gunicorn 服务器。

我的服务器脚本如下所示:

app = Flask(__name__)

class Server(object):
   def __init__(self, data):
       self.data = data

@app.route("/api_method", methods=['GET', 'POST'])
def api_method():
     return server.data

if __name__ == '__main__':
     with smart_open(sys.argv[1]) as f:
         server = Server(f.read())
     app.run()

当我从控制台将它作为“Flask 应用程序”运行时,一切正常,但是当我尝试在 gunicorn 下运行它时,它看不到服务器。我只能通过向上移动服务器创建来修复它,但我必须对路径进行硬编码。

有什么方法可以在 gunicorn 服务器启动时加载类似于我的 Server 类的东西,然后在 API 方法中读取它?

【问题讨论】:

    标签: python flask gunicorn


    【解决方案1】:

    以下是我对如何执行此操作的建议。

    1.如果需要以这种方式实例化,我不会依赖全局 server 变量。由于它是在应用程序创建后实例化的,因此最好以某种方式将其显式传递给应用程序。这样就可以从其他模块(blueprintspluggable views等)访问,例如可以使用Flask的配置机制:

    app.config['server'] = Server(f.read())
    
    #...then in your view functions...
    
    return app.config['server'].data
    

    或者通过flask的globals objectg

    @app.before_request
    def add_server_to_globals():
        with open(sys.argv[1]) as f:
            g.server = Server(f.read())
    
    #...then in your view functions...
    return g.server.data
    

    或者通过继承 Flask 并通过初始化程序将其传入。

    2. 现在您正在使用 gunicorn,您不能依赖 if __name__ == '__main__' 块,因为它仅在您运行文件时执行,而不是在导入文件时执行。 Gunicorn 通过导入应用程序对象(参见 how APP_MODULE is explained in the docs)并将其交给 gunicorn 的 WSGI 服务器来工作,因此您用于实例化 Server 的代码将不会运行。

    unicorn 的另一个新要求是您不能依赖 sys.argv,因为这将是 gunicorn 的 sys.argv,这与您当前在直接运行文件时传入的任何参数都不同。

    要替换if __name__ == '__main__' 块,您可以使用before_request,就像我在上面所做的那样,在应用程序处理其第一个请求之前运行一些代码。我建议使用环境变量或config file 将您的参数传递给Server 类。例如:

    @app.before_request
    def add_server_to_globals():
        with open(os.environ['SERVER_FILE']) as f:
            g.server = Server(f.read())
    

    然后使用设置的环境变量运行您的应用程序:

    $ SERVER_FILE=blah.txt gunicorn app:app
    [2015-02-24 10:10:20 -0800] [3217] [INFO] Starting gunicorn 19.2.1
    ...
    

    3.您还可以查看烧瓶文档中描述的"application factory" 模式来替换if __name__ == '__main__' 块。但在这种情况下,它不是很有必要。如果您只想实例化 Server 对象一次(出于性能原因),则应用程序工厂的用途是。

    Here's some fully working code based on your example.

    Here's another example using an application factory.

    【讨论】:

    • 实际上具有全局对象 g 的版本仅适用于一个请求。我已经意识到然后加载的对象消失了,这不是我真正想要的。所以 app.config['server'] = ... 是正确的解决方案。
    • 啊,没错。对于那个很抱歉。我对 before_first_requestbefore_request 的看法有误。我已经用正确的信息更新了我的帖子,并添加了一个关于如何将 before_request 与应用程序工厂一起使用的示例。
    • 谢谢,在我的情况下,文件可以很大并且可以在 S3 上远程(我使用 smart_open 库而不是标准 open 的原因)。因此,在每个请求之前仍然加载文件将是一个安静的问题,但 app.config['server'] = Server(f.read()) 对我来说效果很好。
    【解决方案2】:

    简单:)

    with smart_open(sys.argv[2]) as f:
        server = Server(f.read())
    if __name__ == '__main__':
        app.run()
    

    Steven Kryskalla 在他的回答中已经给了你逻辑。

    但请确保将 app.run() 保留在 if __name__=="__main__" 中,因为您不希望在 gunicorn 导入服务器时运行它

    编辑: 实际上必须有 sys.argv[2],因为在 sys.argv[1] 中是服务器应用程序的路径。

    【讨论】:

    • 我实际上已经尝试过了,你基本上是对的,但是有一个特殊性。它实际上更改为 sys.argv[2],因为运行服务器的第二个参数是服务器脚本的路径。所以当我想在原生 Flask 服务器和 gunicorn 服务器上运行相同的代码时可能会出现问题。
    • 是的,你是对的。我只是试图传达简单的修复而不重新检查应该使用什么命令。 :)
    猜你喜欢
    • 2018-01-31
    • 2020-08-20
    • 2016-03-02
    • 2021-08-13
    • 1970-01-01
    • 2021-11-04
    • 2019-03-13
    • 1970-01-01
    • 2021-12-16
    相关资源
    最近更新 更多