web-dev-qa-db-ja.com

ApacheSetEnvがmod_wsgiで期待どおりに機能しない

私が作成したflaskアプリケーションでは、環境変数を使用して構成できる外部ライブラリを使用します。注:この外部ライブラリは自分で作成したので、could必要に応じて変更を加えます。コマンドラインから実行する場合、flask server with:

_# env = python virtual environment
ENV_VAR=foo ./env/bin/python myapp/webui.py
_

期待通りにすべてがうまくいきました。しかし、それをApacheにデプロイし、SetEnvを使用すると、notは機能しなくなります。実際、_os.environ_をstderrに出力すると(Apacheログに表示されるため、wsgiプロセスは非常に異なる環境にあるように見えます(たとえば、_os.environ['PWD']_はway offのようです。実際、それは私の開発フォルダーを指しています。

問題の特定に役立てるために、スタンドアロンのhello-worldアプリとしてのアプリケーションの関連部分を以下に示します。エラー出力と観察結果は、投稿の最後にあります。

アプリフォルダーのレイアウト:

Pythonアプリ:

_.
├── myapp.ini
├── setup.py
└── testenv
    ├── __init__.py
    ├── model
    │   └── __init__.py
    └── webui.py
_

Apacheフォルダー(_/var/www/michel/testenv_):

_.
├── env
│   ├── [...]
├── logs
│   ├── access.log
│   └── error.log
└── wsgi
└── app.wsgi
_

myapp.ini

_[app]
somevar=somevalue
_

setup.py

_from setuptools import setup, find_packages

setup(
    name="testenv",
    version='1.0dev1',
    description="A test app",
    long_description="Hello World!",
    author="Some Author",
    author_email="[email protected]",
    license="BSD",
    include_package_data=True,
    install_requires = [
      'flask',
      ],
    packages=find_packages(exclude=["tests.*", "tests"]),
    Zip_safe=False,
)
_

testenv/init。py

_# empty
_

testenv/model/init。py

_from os.path import expanduser, join, exists
from os import getcwd, getenv, pathsep
import logging
import sys

__version__ = '1.0dev1'

LOG = logging.getLogger(__name__)

def find_config():
    """
    Searches for an appropriate config file. If found, return the filename, and
    the parsed search path
    """

    path = [getcwd(), expanduser('~/.mycompany/myapp'), '/etc/mycompany/myapp']
    env_path = getenv("MYAPP_PATH")
    config_filename = getenv("MYAPP_CONFIG", "myapp.ini")
    if env_path:
        path = env_path.split(pathsep)

    detected_conf = None
    for dir in path:
        conf_name = join(dir, config_filename)
        if exists(conf_name):
            detected_conf = conf_name
            break
    return detected_conf, path

def load_config():
    """
    Load the config file.
    Raises an OSError if no file was found.
    """
    from ConfigParser import SafeConfigParser

    conf, path = find_config()
    if not conf:
        raise OSError("No config file found! Search path was %r" % path)

    parser = SafeConfigParser()
    parser.read(conf)
    LOG.info("Loaded settings from %r" % conf)
    return parser

try:
    CONF = load_config()
except OSError, ex:
    # Give a helpful message instead of a scary stack-trace
    print >>sys.stderr, str(ex)
    sys.exit(1)
_

testenv/webui.py

_from testenv.model import CONF

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello World %s!" % CONF.get('app', 'somevar')

if __name__ == '__main__':
    app.debue = True
    app.run()
_

Apache構成

_<VirtualHost *:80>
    ServerName testenv-test.my.fq.dn
    ServerAlias testenv-test

    WSGIDaemonProcess testenv user=michel threads=5
    WSGIScriptAlias / /var/www/michel/testenv/wsgi/app.wsgi
    SetEnv MYAPP_PATH /var/www/michel/testenv/config

    <Directory /var/www/michel/testenv/wsgi>
        WSGIProcessGroup testenv
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>

    ErrorLog /var/www/michel/testenv/logs/error.log
    LogLevel warn

    CustomLog /var/www/michel/testenv/logs/access.log combined

</VirtualHost>
_

app.wsgi

_activate_this = '/var/www/michel/testenv/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

from os import getcwd
import logging, sys

from testenv.webui import app as application

# You may want to change this if you are using another logging setup
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

LOG = logging.getLogger(__name__)
LOG.debug('Current path: {0}'.format(getcwd()))

# Application config
application.debug = False

# vim: set ft=python :
_

エラーと観察

これは、Apacheエラーログの出力です。

_[Thu Jan 26 10:48:15 2012] [error] No config file found! Search path was ['/home/users/michel', '/home/users/michel/.mycompany/myapp', '/etc/mycompany/myapp']
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101] mod_wsgi (pid=17946): Target WSGI script '/var/www/michel/testenv/wsgi/app.wsgi' cannot be loaded as Python module.
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101] mod_wsgi (pid=17946): SystemExit exception raised by WSGI script '/var/www/michel/testenv/wsgi/app.wsgi' ignored.
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101] Traceback (most recent call last):
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101]   File "/var/www/michel/testenv/wsgi/app.wsgi", line 10, in <module>
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101]     from testenv.webui import app as application
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101]   File "/var/www/michel/testenv/env/lib/python2.6/site-packages/testenv-1.0dev1-py2.6.Egg/testenv/webui.py", line 1, in <module>
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101]     from testenv.model import CONF
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101]   File "/var/www/michel/testenv/env/lib/python2.6/site-packages/testenv-1.0dev1-py2.6.Egg/testenv/model/__init__.py", line 51, in <module>
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101]     sys.exit(1)
[Thu Jan 26 10:48:15 2012] [error] [client 10.115.192.101] SystemExit: 1
_

私の最初の観察は、環境変数_MYAPP_PATH_が_os.environ_に表示されないことです(これはこの出力には表示されませんが、テストしましたが、表示されません!)。そのため、構成「resolver」はデフォルトのパスにフォールバックします。

そして、私の2番目の観察は、構成ファイルの検索パスがos.getcwd()の戻り値として_/home/users/michel_をリストしていることです。私は実際に_/var/www/michel/testenv_の中に何かを期待していました。

私の本能は、私が構成解決を行っている方法が正しくないことを教えてくれます。主な理由は、コードがインポート時に実行されるためです。これは、おそらくconfig-resolutionコードが実行されるという考えにつながりますbefore WSGI環境が適切にセットアップされています。私はそこに何かにいますか?

短い議論/接線の質問

この場合、yoはどのように構成解決を行いますか? 「モデル」サブフォルダーは実際には外部モジュールであり、wsgi以外のアプリケーションでも機能し、データベース接続を構成する方法を提供する必要があります。

個人的には、設定ファイルを上書きしながら検索する方法が好きです。ただ、コードがインポート時に実行されるという事実は、私のクモの感覚を狂ったようにうずきます。この背後にある理論的根拠:構成処理は、このモジュールを使用する他の開発者によって完全に隠されており(抽象化バリア)、「正しく機能」します。モジュールをインポートするだけで(もちろん既存の構成ファイルを使用)、DBの詳細を知らなくてもすぐにジャンプできます。これにより、さまざまなデータベース(dev/test/deploy)を簡単に操作し、データベースを簡単に切り替えることができます。

さて、mod_wsgi内ではもうありません:(

更新:

ちょうど今、上記のアイデアをテストするために、_webui.py_を次のように変更しました。

_import os

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def index():
    return jsonify(os.environ)

if __name__ == '__main__':
    app.debue = True
    app.run()
_

Webページの出力は次のとおりです。

_{
    LANG: "C",
    Apache_RUN_USER: "www-data",
    Apache_PID_FILE: "/var/run/Apache2.pid",
    PWD: "/home/users/michel/tmp/testenv",
    Apache_RUN_GROUP: "www-data",
    PATH: "/usr/local/bin:/usr/bin:/bin",
    HOME: "/home/users/michel/"
}
_

これは、他のデバッグ方法で見られる環境と同じ環境を示しています。だから私のイニシャルは間違っていた。しかし今、私はまだ何か奇妙なことに気づきました。 _os.environment['PWD']_は、開発ファイルがあるフォルダーに設定されます。これは、アプリケーションが実行されているallではありません。まだ見知らぬ人、os.getcwd()は_/home/users/michel_を返しますか?これは、私が_os.environ_で見たものと矛盾しています。 _os.environ['PWD']_と同じではないでしょうか?

ただし、最も重要な問題は残っています。ApacheのSetEnv(この場合は_MYAPP_PATH_)によって設定された値が_os.environ_に見つからないのはなぜですか?

25
exhuma

WSGI環境は、アプリケーションオブジェクトのenviron引数でアプリケーションへの各要求に渡されることに注意してください。この環境は、_os.environ_に保持されているプロセス環境とはまったく関係ありません。 SetEnvディレクティブは_os.environ_に影響を与えず、Apache構成ディレクティブを介してプロセス環境にあるものに影響を与える方法はありません。

したがって、Apacheから_os.environ['PWD']_を取得するには、getenvironまたは_MY_PATH_以外のことを行う必要があります。

Flaskは、_app.environ_ではなくwsgi環境をリクエストに追加します。これは、下にあるwerkzeugによって行われます。そのため、アプリケーションへのリクエストごとに、Apacheは_MYAPP_CONF_キーを追加し、たとえばrequest.environ.get('MYAPP_CONFIG')のように見えるリクエストにアクセスできる場所であればどこからでもアクセスできます。

23
rapadura

@rapaduraの答えは、Apache構成のSetEnv値に直接アクセスできないという点で正しいですが、回避することはできます。

app.wsgiファイルのapplicationのラッパーを追加すると、リクエストごとにos.environを設定できます。例については、次の変更されたapp.wsgiを参照してください。

activate_this = '/var/www/michel/testenv/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

from os import environ, getcwd
import logging, sys

from testenv.webui import app as _application

# You may want to change this if you are using another logging setup
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

LOG = logging.getLogger(__name__)
LOG.debug('Current path: {0}'.format(getcwd()))

# Application config
_application.debug = False

def application(req_environ, start_response):
    environ['MYAPP_CONF'] = req_environ['MYAPP_CONF']
    return _application(req_environ, start_response)

Apache構成でさらに多くの環境変数を設定した場合は、applicationラッパー関数でそれぞれを明示的に設定する必要があります。

11
jerrykan