Apache SetEnv无法按预期使用mod_wsgi

时间:2012-01-26 10:07:58

标签: python apache mod-wsgi flask

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

# env = python virtual environment
ENV_VAR=foo ./env/bin/python myapp/webui.py
所有人都按预期进行了比赛。但是在将其部署到apache并使用SetEnv之后,不再工作了。实际上,将os.environ打印到stderr(因此它显示在apache日志中显示,wsgi进程似乎处于一个非常不同的环境中(对于一个{{1}似乎方式关闭。事实上,它指向我的开发文件夹。

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

App文件夹布局:

Python app:

os.environ['PWD']

Apache文件夹(. ├── myapp.ini ├── setup.py └── testenv ├── __init__.py ├── model │   └── __init__.py └── webui.py ):

/var/www/michel/testenv

MYAPP.INI

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

setup.py

[app]
somevar=somevalue

testenv /的初始化的.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 /模型/的初始化的.py

# empty

testenv / webui.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)

Apache配置

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

app.wsgi

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

错误和观察

这是apache错误日志的输出。

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 :

我的第一个观察是环境变量[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中(这在此输出中不可见,但我测试了它,并且它不存在!)。因此,配置&#34;解析器&#34;回到默认路径。

我的第二个观察是配置文件的搜索路径列出os.environ作为/home/users/michel的返回值。我实际上期待os.getcwd()内的某些内容。

我的直觉告诉我,我正在进行配置解析的方式不对。主要是因为代码是在导入时执行的。这引出了我的想法,即在正确设置WSGI环境之前,可能会执行配置解析代码。我在那里做什么吗?

简短讨论/切向问题

在这种情况下如何进行配置解析?鉴于&#34;模型&#34;子文件夹实际上是一个外部模块,它也应该在非wsgi应用程序中工作,并且应该提供一种配置数据库连接的方法。

就个人而言,我喜欢搜索配置文件的方式,同时仍能覆盖它。只是,代码在导入时执行的事实让我的蜘蛛感觉像疯了似的刺痛。这背后的基本原理:使用这个模块的开发人员完全隐藏了配置处理(抽象障碍),它只是工作&#34;。他们只需要导入模块(当然使用现有的配置文件),并且可以在不知道任何数据库详细信息的情况下直接进入。这也使他们可以轻松地使用不同的数据库(开发/测试/部署)并轻松地在它们之间切换。

现在,在mod_wsgi内部它不再:(

更新

刚才,为了测试我的上述想法,我将/var/www/michel/testenv更改为以下内容:

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/" } 设置为我拥有开发文件的文件夹。这不是运行应用程序的 all 。更奇怪的是,os.environment['PWD']会返回os.getcwd()吗?这与我在/home/users/michel中看到的不一致。它应该与os.environ不一样吗?

但最重要的问题仍然存在:为什么在os.environ['PWD']中找不到apache的SetEnv(在这种情况下为MYAPP_PATH)设置的值?

2 个答案:

答案 0 :(得分:23)

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

因此,您必须执行除getenvironos.environ['PWD']之外的其他操作才能从apache获取MY_PATH

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

答案 1 :(得分:10)

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

如果您在application文件中的app.wsgi附近添加了包装,则可以在每个请求上设置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包装函数中明确设置每个变量。