Apache SetEnv ne fonctionne pas comme prévu avec mod wsgi

Dans une application flask que j'ai écrite, j'utilise une bibliothèque externe qui peut être configurée à l'aide de variables d'environnement. Note: j'ai écrit cette bibliothèque externe moi-même. Donc, je pourrais apporter des modifications si nécessaire. Lors de l'exécution à partir de la ligne de commande, une exécution du serveur flask avec:

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

Tout woks comme prévu. Mais après l'avoir déployé sur apache, et en utilisant SetEnv, ne fonctionne plus. En fait, l'impression de os.environ à stderr (donc il apparaît dans l'apache les journaux révèle, que l' wsgi processus semble être dans un environnement très différent (pour l'une, os.environ['PWD'] semble être chemin off. En fait, il pointe vers mon dossier de développement.

Pour aider à identifier le problème, Voici les parties pertinentes de l'application en tant qu'application autonome hello-world. La sortie d'erreur, et les observations sont à la toute fin du post.

Mise en page du dossier de L'application:

Application Python:

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

Dossier 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/modèle/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()

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

Erreur et observations

Ceci est la sortie du journal des erreurs 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

Ma première observation est que la variable d'environnement MYAPP_PATH n'apparaît pas dans os.environ (ce n'est pas visible dans cette sortie, mais je l'ai testé, et ce n'est pas là!). En tant que tel, la configuration "resolver" revient au chemin par défaut.

Et ma deuxième observation est le chemin de recherche pour les listes de fichiers de configuration /home/users/michel comme valeur de retour de os.getcwd(). En fait, je m'attendais à quelque chose à l'intérieur /var/www/michel/testenv.

Mon instinct me dit que la façon dont je fais la résolution de configuration n'est pas correcte. Principalement parce que le code est exécuté au moment de l'importation. Cela me conduit à l'idée, que peut-être le code de résolution de configuration est exécuté avant que l'environnement WSGI ne soit correctement configuré. Suis-je sur quelque chose là-bas?

Courte discussion / question tangentielle

Comment Vous feriez la résolution de configuration dans ce cas? Étant donné que le sous-dossier" model " est en réalité un module externe, qui devrait également fonctionner dans des applications non-wsgi, et devrait fournir une méthode pour configurer une connexion à la base de données.

Personnellement, j'aime la façon dont je recherche les fichiers de configuration tout en étant capable pour le remplacer. Seulement, le fait que le code soit exécuté au moment de l'importation rend mon spider-sens picotant comme un fou. La logique derrière ceci: la gestion de la Configuration est complètement cachée (abstraction-barrière) par d'autres développeurs utilisant ce module, et cela "fonctionne juste". Ils ont juste besoin d'importer le module (avec un fichier de configuration existant bien sûr) et peuvent sauter directement sans connaître les détails de la base de données. Cela leur donne également un moyen facile de travailler avec différentes bases de données (dev / test/deployment) et basculer entre eux facilement.

Maintenant, à l'intérieur de mod_wsgi il ne le fait plus :(

Mise à jour:

Tout à l'heure, pour tester mon idée ci-dessus, j'ai changé le webui.py à la suivante:

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

La sortie sur la page web est la suivante:

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

Cela montre le même environnement que celui vu par d'autres méthodes de débogage. Donc, mon initial était faux. Mais maintenant, j'ai réalisé quelque chose d'étranger encore. {[25] } est défini sur le dossier où j'ai mes fichiers de développement. C'est pas à all où l'application est en cours d'exécution. Inconnu encore, os.getcwd() retourne /home/users/michel? Ceci est incompatible avec ce que je vois dans os.environ. Ne devrait-il pas être le même que os.environ['PWD']?

Le problème le plus important reste cependant: pourquoi la valeur est-elle définie par apache SetEnv (MYAPP_PATH dans ce cas) ne se trouve pas dans os.environ?

24
demandé sur exhuma 2012-01-26 14:07:58

2 réponses

Notez que l'environnement WSGI est passé à chaque requête à l'application dans l'argument environ de l'objet application. Cet environnement est totalement indépendant de l'environnement de processus qui est conservé dans os.environ. La directive SetEnv n'a aucun effet sur os.environ et les directives de configuration Apache n'ont aucun moyen d'affecter ce qui se trouve dans l'environnement de processus.

Donc, vous devez faire autre chose que getenviron ou os.environ['PWD'] pour obtenir le MY_PATH d'apache.

Flacon ajoute l'environnement wsgi à la requête, Pas app.environ, Il est fait par le sous-jacent werkzeug. Ainsi, à chaque requête à l'application, apache ajoutera la clé MYAPP_CONF et youll y accéder partout où vous pouvez accéder à la requête, il semble que, request.environ.get('MYAPP_CONFIG') par exemple.

21
répondu rapadura 2012-01-29 20:49:11

La réponse de@rapadura est correcte en ce sens que vous n'avez pas d'accès direct aux valeurs SetEnv dans votre configuration Apache, mais vous pouvez la contourner.

Si vous ajoutez un wrapper autour de application votre app.wsgi fichier, vous pouvez définir la os.environ sur chaque demande. Voir la modification suivante app.wsgi pour un exemple:

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)

Si vous avez défini plus de variables D'environnement dans votre configuration Apache, vous devez définir explicitement chacune d'entre elles dans la fonction wrapper application.

10
répondu jerrykan 2013-04-30 04:01:01