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