如何制作一个Python软件包,为Flask的网站提供服务?

Mar*_*oma 1 python apache mod-wsgi flask

我编写了一个Python包hwrt(如果你想试试,请参阅安装说明),它在执行时为网站提供服务

$ hwrt serve
2014-12-04 20:27:07,182 INFO  * Running on http://127.0.0.1:5000/
2014-12-04 20:27:07,183 INFO  * Restarting with reloader
Run Code Online (Sandbox Code Playgroud)

我想让它在http://www.pythonanywhere.com上运行,但是当我开始它时,我得到它

19:19 ~ $ hwrt serve
2014-12-04 19:19:59,282 INFO  * Running on http://127.0.0.1:5000/
Traceback (most recent call last):
  File "/home/MartinThoma/.local/bin/hwrt", line 108, in <module>
    main(args)
  File "/home/MartinThoma/.local/bin/hwrt", line 102, in main
    serve.main()
  File "/home/MartinThoma/.local/lib/python2.7/site-packages/hwrt/serve.py", line 95, in main
    app.run()
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 739, in run
    run_simple(host, port, self, **options)
  File "/usr/local/lib/python2.7/dist-packages/werkzeug/serving.py", line 613, in run_simple
    test_socket.bind((hostname, port))
  File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 98] Address already in use
Run Code Online (Sandbox Code Playgroud)

我只在文档中找到了这个:

烧瓶

从不使用app.run(),它会破坏你的webapp.只需将应用程序导入到wsgi文件中......

通过搜索wsgi文件,我找到了mod_wsgi(Apache).但是,我不明白如何调整我当前的简约Flask应用程序来使用它.目前,背后的脚本hwrt serve是:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Start a webserver which can record the data and work as a classifier."""

import pkg_resources
from flask import Flask, request, render_template
from flask_bootstrap import Bootstrap
import os
import json

# hwrt modules
import hwrt
import hwrt.utils as utils


def show_results(results, n=10):
    """Show the TOP n results of a classification."""
    import nntoolkit
    classification = nntoolkit.evaluate.show_results(results, n)
    return "<pre>" + classification.replace("\n", "<br/>") + "</pre>"

# configuration
DEBUG = True

template_path = pkg_resources.resource_filename('hwrt', 'templates/')

# create our little application :)
app = Flask(__name__, template_folder=template_path)
Bootstrap(app)
app.config.from_object(__name__)


@app.route('/', methods=['POST', 'GET'])
def show_entries():
    heartbeat = request.args.get('heartbeat', '')
    return heartbeat


@app.route('/interactive', methods=['POST', 'GET'])
def interactive():
    if request.method == 'POST':
        raw_data_json = request.form['drawnJSON']
        # TODO: Check recording
        # TODO: Submit recorded json to database
        # Classify
        model_path = pkg_resources.resource_filename('hwrt', 'misc/')
        model = os.path.join(model_path, "model.tar")
        print(model)
        results = utils.evaluate_model_single_recording(model, raw_data_json)
        # Show classification page
        page = show_results(results, n=10)
        page += '<a href="../interactive">back</a>'
        return page
    else:
        # Page where the user can enter a recording
        return render_template('canvas.html')


def get_json_result(results, n=10):
    s = []
    for res in results[:min(len(results), n)]:
        s.append({res['semantics']: res['probability']})
    return json.dumps(s)


@app.route('/worker', methods=['POST', 'GET'])
def worker():
    # Test with
    # wget --post-data 'classify=%5B%5B%7B%22x%22%3A334%2C%22y%22%3A407%2C%22time%22%3A1417704378719%7D%5D%5D' http://127.0.0.1:5000/worker
    if request.method == 'POST':
        raw_data_json = request.form['classify']
        # TODO: Check recording
        # TODO: Submit recorded json to database
        # Classify
        model_path = pkg_resources.resource_filename('hwrt', 'misc/')
        model = os.path.join(model_path, "model.tar")
        results = utils.evaluate_model_single_recording(model, raw_data_json)
        return get_json_result(results, n=10)
    else:
        # Page where the user can enter a recording
        return "Classification Worker (Version %s)" % hwrt.__version__


def get_parser():
    """Return the parser object for this script."""
    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
    parser = ArgumentParser(description=__doc__,
                            formatter_class=ArgumentDefaultsHelpFormatter)
    return parser


def main():
    app.run()

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

小智 5

好吧,对于你的问题,一个不那么不引人注意的答案是关于mod_wsgi与你的应用程序交互的内容.一个典型的烧瓶应用程序看起来像这样:

from flask import Flask
app = Flask(__name__)

app.route("/")
def hello():
    return "Holy moly that tunnel was bright.. said Bit to NIC"

if __name__ == "__main__":
    app.run()
Run Code Online (Sandbox Code Playgroud)

不幸的是,Apache无法知道如何处理这个问题(尽管应用程序可以自行运行).为了让应用程序和Apache一起玩得很好,我们将使用名为mod_wsgi的东西.Mod_WSGI对我们来说重要的是,它提供了一个已知的接口(一种名为wsgi的文件类型),它将包装我们的应用程序并对其进行初始化,以便我们可以通过Apache提供它.

我将假设您正在使用python虚拟环境,但如果您不是,则可以在下面的说明中省略处理此问题的步骤.如果您对虚拟环境如此优秀感到好奇,请随时阅读有关python生态系统的信息.

此外 - 您可以包含一个额外的标志(假设您正在运行wsgi作为守护程序),以便在您touch或更改wsgi文件时自动重新加载守护程序.这在开发和调试过程中非常有用,所以我将在下面介绍.

无论如何,让我们开始吧.我将其分解为以下步骤.

为mod_wsgi配置Apache

  1. 在Apache中启用mod_wsgi:
    • sudo apt-get install libapache2-mod-wsgi
  2. 编辑你的/etc/apache2/sites-available/<yoursite>.conf.

    <VirtualHost interface:port>
         WSGIDaemonProcess yourapp user=someUser processes=2 threads=15
         WSGIProcessGroup yourapp
    
         # In this case / refers to whatever relative URL path hosts flask
         WSGIScriptAlias / /absolute/path/to/yourapp.wsgi
    
         <Directory /path/to/your/main/py/file/ >
             # Use good judgement here when server hardening, this assumes dev env
             Order allow,deny
             Allow from all
             Require all granted
             #The below enables 'auto-reload' of WSGI
             WSGIScriptReloading On
         </Directory>
    
         # If you want to serve static files as well and bypass flask in those cases
         Alias /relative/url/to/static/content/
         <Directory /absolute/path/to/static/root/directory/>
             Order allow,deny
             Allow from all
         </Directory>
    </VirtualHost>
    
    Run Code Online (Sandbox Code Playgroud)
  3. 创建yourapp.wsgi文件并将其放在适当的位置:警惕文件权限!

    #!/usr/bin/python
    import sys
    import logging
    
    # Activate virtual environment.
    # If you are not using venv, skip this.
    # But you really should be using it!
    activate_this = "/path/to/venv/bin/activate_this.py"
    execfile(activate_this, dict(__file__=activate_this))
    
    # Handle logging 
    logging.basicConfig(stream=sys.stderr)
    
    sys.path.insert(0, "/path/to/your/main/py/file/")
    from YourMainPyFileName import app as application
    application.secret_key = "your_secret_key"
    
    Run Code Online (Sandbox Code Playgroud)
  4. 重新加载Apache并解决问题.我可能每隔几周设置一次,以便我有一个不同的项目或想法...我通常必须从头开始修复一件事或另一件事.不要绝望! Flask有很好的文档.

一旦你完成了所有这些,你应该在一个烧瓶自行运行的地方.上面的示例烧瓶应用程序是我用来验证每次设置时所有工作的实际代码.


这是留在这里以防它有一些用处,但与问题没有直接关系......

这里的答案是使用x-send-file.这利用了让Apache做其擅长的事情(提供静态内容),同时首先让flask(或其他python框架)首先完成它的工作.我经常这样做,让烧瓶在单页网页应用程序中处理我的身份验证层,并且到目前为止对结果感到满意.

这样做需要两件事:

首先 - 在Apache2上启用xsendfile sudo apt-get install libapache2-mod-xsendfile.

第二步 - 更改您的apache2配置,以便允许x-send-file标头:

更改您的conf文件/etc/apache2/sites-available/<yoursite>.conf并添加...

  • XSendFile On
  • XSendFilePath /path/to/static/directory

这可以在<Virtualhost></Virtualhost>标签内输入顶级.

别忘了重启Apache sudo service apache2 restart.

最后 - 配置您的烧瓶应用程序以在app.py文件中使用x-send-file:

app.user_x_sendfile = True

注意:必须在应用初始化后完成.因此也可以作为初始化参数传递.

Flask有关于此的文档(摘录如下):

use_x_sendfile

如果要使用X-Sendfile功能,请启用此功能.请记住,服务器必须支持此功能.这仅影响使用send_file()方法发送的文件.

版本0.2中的新功能.

也可以使用USE_X_SENDFILE配置密钥从配置中配置此属性.默认为False.