Apache SetEnv не работает должным образом с mod_wsgi - PullRequest
25 голосов
/ 26 января 2012

В приложении-колбе, которое я написал, я использую внешнюю библиотеку, которую можно настроить с помощью переменных среды.Примечание: я написал эту внешнюю библиотеку сам.Так что я мог бы вносить изменения в случае необходимости.При запуске из командной строки запущенного сервера фляг с:

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

все работает как ожидалось.Но после развертывания его в Apache и использования SetEnv он не работает больше.Фактически, распечатка от os.environ до stderr (так, что это появляется в журналах apache, показывает, что процесс wsgi, похоже, находится в совершенно другой среде) (например, os.environ['PWD'] кажется путь выключен. Фактически, он указывает на мою папку для разработки.

Чтобы помочь определить проблему, ниже приведены соответствующие части приложения как автономного приложения 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="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 /модель / 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 config

<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» возвращается к пути по умолчанию.

И мое второе наблюдение - это путь поиска для списков файлов конфигурации /home/users/michel в качестве возвращаемого значения os.getcwd().На самом деле я ожидал чего-то внутри /var/www/michel/testenv.

Мой инстинкт подсказывает мне, что способ, которым я делаю разрешение конфигурации, неправильный.Главным образом потому, что код выполняется во время импорта.Это приводит меня к мысли о том, что, возможно, код с разрешением конфигурации выполняется до , а среда WSGI правильно настроена.Я на что-то там?

Краткое обсуждение / тангенциальный вопрос

Как бы вы сделали разрешение конфигурации в этом случае?Учитывая, что подпапка «модель» в действительности является внешним модулем, который также должен работать в приложениях, отличных от wsgi, и должен обеспечивать метод для настройки соединения с базой данных.

Лично мне нравится то, как яИщите файлы конфигурации, все еще будучи в состоянии переопределить это.Только тот факт, что код выполняется во время импорта, заставляет мои чувства паука покалывать как сумасшедшие.Обоснование этого: обработка конфигурации полностью скрыта (абстракция-барьер) другими разработчиками, использующими этот модуль, и он «просто работает».Им просто нужно импортировать модуль (с существующим конфигурационным файлом, разумеется), и он может сразу перейти, не зная деталей БД.Это также дает им простой способ работать с различными базами данных (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()

Вывод на веб-странице следующий:

{
    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'] установлен в папку, где у меня есть файлы разработки. Это не в все там, где работает приложение. Еще страннее, os.getcwd() возвращает /home/users/michel? Это несовместимо с тем, что я вижу в os.environ. Разве это не должно совпадать с os.environ['PWD']?

Однако сохраняется самая важная проблема: почему значение, установленное с помощью apache SetEnv (MYAPP_PATH в данном случае), не найдено в os.environ?

Ответы [ 2 ]

23 голосов
/ 26 января 2012

Обратите внимание, что среда WSGI передается при каждом запросе к приложению в аргументе environ объекта приложения. Эта среда абсолютно не связана с технологической средой, которая хранится в os.environ. Директива SetEnv не влияет на os.environ, и директивы конфигурации Apache не могут повлиять на то, что находится в среде процесса.

Таким образом, вы должны сделать что-то еще, кроме getenviron или os.environ['PWD'], чтобы получить MY_PATH от apache.

Flask добавляет к запросу среду wsgi, а не app.environ, это делается с помощью нижележащего элемента werkzeug. Таким образом, при каждом запросе к приложению apache будет добавлять ключ MYAPP_CONF, и вы будете обращаться к нему везде, где вы можете получить доступ к запросу, например, request.environ.get('MYAPP_CONFIG').

11 голосов
/ 30 апреля 2013

@ Rapadura ответ верен в том, что у вас нет прямого доступа к SetEnv значениям в вашей конфигурации Apache, но вы можете обойти это.

Если вы добавите оболочку вокруг 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.

...