python - 我怎样才能制作一个 Python 包来为带有 Flask 的网站提供服务以与 Apache 一起工作?

标签 python apache flask mod-wsgi

我写了一个Python包hwrt (如果您想尝试,请参阅 installation instructions)它在执行时为网站提供服务

$ 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

我想让它在 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

我只在文档中找到这个:

Flask

never use app.run(), it will break your webapp. Just import the app into your wsgi file...

通过搜索 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()

最佳答案

好的,对于您的问题,一个不太不合逻辑的答案是关于 mod_wsgi 与您的应用程序交互的作用。典型的 Flask 应用程序看起来像这样:

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()

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

我假设您使用的是 python 虚拟环境,但如果您不是,则可以在下面的说明中省略处理此问题的步骤。如果您对为什么虚拟环境如此出色感到好奇,feel free read about the python ecosystem .

另外 - 你可以包括一个额外的标志(假设你正在运行 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>
    
  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"
    
  4. 重新加载 Apache 并解决问题。我可能每隔几周就为一个不同的项目或想法设置一次,并且……从头开始做时,我通常必须修复一件事或另一件事。不过不要绝望! Flask has great documentation on this .

一旦你完成了所有这些,你应该在一个地方,flask 可以自己运行。上面的示例 Flask 应用程序是我每次设置它时用来验证一切正常的实际代码。


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

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

这样做需要两件事:

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

其次 - 更改您的 apache2 配置以允许 x-send-file header :

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

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

这可以在 <Virtualhost></Virtualhost> 中进入顶层标签。

不要忘记重启 Apache sudo service apache2 restart .

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

app.user_x_sendfile = True

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

Flask has documentation on this (以下摘录):

use_x_sendfile

Enable this if you want to use the X-Sendfile feature. Keep in mind that the server has to support this. This only affects files sent with the send_file() method.

New in version 0.2.

This attribute can also be configured from the config with the USE_X_SENDFILE configuration key. Defaults to False.

关于python - 我怎样才能制作一个 Python 包来为带有 Flask 的网站提供服务以与 Apache 一起工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27302474/

相关文章:

python - 数据帧操作和合并Python

python - Numpy 切片多维错误产生意外结果

php - .htaccess 不适用于 Apache2

python - flask 单元测试 : how to test request from logged in user

python - 使用 sklearn 时,python 中的 fit、transform 和 fit_transform 有什么区别?

Python 增量填充 x 和 y 值

php - 无法加载动态库 'php_libsodium'

apache - CORS header ‘Access-Control-Allow-Origin’ 与 ‘(null)’ 不匹配,但它不为空

python - 将所有 python-rom 对象放入列表中

proxy - 使用 Flask 和 WebSocket-for-Python (ws4py) 的 IPython Notebook 的 WebSockets 代理