私が作成した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
_
_[app]
somevar=somevalue
_
_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,
)
_
_# empty
_
_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)
_
_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()
_
_<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>
_
_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
_に見つからないのはなぜですか?
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')
のように見えるリクエストにアクセスできる場所であればどこからでもアクセスできます。
@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
ラッパー関数でそれぞれを明示的に設定する必要があります。