Apache SetEnv无法像mod_wsgi一样按预期工作

在我写的烧瓶应用程序中,我使用了一个可以使用环境variables进行configuration的外部库。 注意:我自己写了这个外部库。 所以我可以在必要时进行更改。 从命令行运行时,运行烧瓶服务器:

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

这一切都像预期的一样。 但是,在部署到Apache后,使用SetEnv不再工作。 实际上,将os.environ打印到stderr (所以在apache日志中显示的是, wsgi进程似乎处于一个非常不同的环境中(例如, os.environ['PWD']似乎是closures的其实它指向我的开发文件夹。

为了帮助识别问题,以下是应用程序的相关部分,作为独立的hello-world应用程序。 错误输出和观察是在post的最后。

应用文件夹布局:

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="author@example.com", 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() 

Apacheconfiguration

 <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 

我的第一个观察是环境variablesMYAPP_PATH没有出现在os.environ (这在这个输出中是不可见的,但是我testing了它,它不在那里!)。 因此,configuration“parsing器”回退到默认path。

我的第二个观察是configuration文件列表/home/users/michel的searchpath作为os.getcwd()返回值。 我实际上期待/var/www/michel/testenv里面的东西。

我的直觉告诉我,我做configuration解决scheme的方式是不正确的。 主要是因为代码是在导入时执行的。 这使我想到,也许configurationparsing代码在WSGI环境正确设置之前执行。 我在那里?

简短的讨论/切题

在这种情况下你将如何做configuration分辨率? 假定“模型”子文件夹实际上是一个外部模块,它也应该在非wsgi应用程序中工作,并且应该提供一种configuration数据库连接的方法。

就我个人而言,我喜欢searchconfiguration文件的方式,同时仍然可以覆盖它。 只是,代码在import时被执行的事实使我的蜘蛛感觉像疯了一样刺痛。 这背后的基本原理:使用这个模块的开发人员完全隐藏了configuration处理(抽象障碍),而且“正常工作”。 他们只需要导入模块(当然有一个现有的configuration文件),并可以在不知道任何数据库细节的情况下跳转到正确的位置。 这也为他们提供了一个简单的方法来处理不同的数据库(开发/testing/部署),并轻松地在它们之间切换。

现在,mod_wsgi里面不再有:(

更新:

就在现在,为了testing我的上述想法,我将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() 

网页上的输出如下:

 { 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/" } 

这显示了与其他debugging方法相同的环境。 所以我最初虽然是错的。 但是现在我意识到一些陌生人了。 os.environment['PWD']被设置为我有我的开发文件的文件夹。 这并不是应用程序正在运行的地方。 陌生人呢, os.getcwd()返回/home/users/michel ? 这与我在os.environ看到的不一致。 是不是应该和os.environ['PWD']

最重要的问题仍然是:为什么在os.environ找不到apache的MYAPP_PATH (在这种情况下, MYAPP_PATH )设置的值?

请注意,WSGI环境在应用程序对象的environ参数中的每个请求environ应用程序。 这个环境与os.environ保存的进程环境完全无关。 SetEnv指令对os.environ没有影响,Apache配置指令无法影响进程环境中的内容。

所以,除了getenviron或者os.environ['PWD'] ,你必须做其他的事情来从apache获取MY_PATH

Flask将wsgi environ添加到请求中,而不是app.environ ,它由底层werkzeug 。 因此,在每个对应用程序的请求中,apache都会添加MYAPP_CONF键,并且您可以访问它的任何地方,比如request.environ.get('MYAPP_CONFIG')

@rapadura答案是正确的,因为你没有直接访问你的Apache配置中的SetEnv值,但你可以解决它。

如果你在app.wsgi文件的application中添加一个包装,你可以在每个请求中设置os.environ 。 有关示例,请参阅以下修改的app.wsgi

 activate_this = '/var/www/michel/testingv/env/bin/activate_this.py' execfile(activate_this, dict(__file__=activate_this)) from os import environ, getcwd import logging, sys from testingv.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包装函数中明确地设置它们中的每一个。