Apache SetEnv no funciona como se esperaba con mod_wsgi

En una aplicación de matraz que escribí, utilizo una biblioteca externa que se puede configurar utilizando variables de entorno. Nota: escribí esta biblioteca externa yo mismo. Asiquepodrí hacer cambios si es necesario. Cuando se ejecuta desde la línea de comandos, se ejecuta el servidor de matraz con:

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

it todos los woks como se esperaba. Pero después de implementarlo en Apache y usarSetEnv lo hacen trabajo más. De hecho, imprimiendoos.environ astderr (por lo que aparece en los registros de apache revela que lawsgil proceso @ parece estar en un entorno muy diferente (por ejemplo,os.environ['PWD'] parece sercamin apagado. De hecho, apunta a mi carpeta de desarrollo.

Para ayudar a identificar el problema, a continuación se detallan las partes relevantes de la aplicación como una aplicación independiente de hello-world. La salida del error y las observaciones se encuentran al final de la publicación.

Diseño de carpeta de la aplicación:

plicación @Python:

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

Carpeta 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="[email protected]",
    license="BSD",
    include_package_data=True,
    install_requires = [
      'flask',
      ],
    packages=find_packages(exclude=["tests.*", "tests"]),
    zip_safe=False,
)
testenv /en es .py
# empty
testenv / model /en es .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 :
Error y observaciones

Esta es la salida del registro de errores de 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

i primera observación es que la variable de entornoMYAPP_PATH no aparece enos.environ (esto no es visible en esta salida, pero lo probé, ¡y no está allí!). Como tal, la configuración "resolver" vuelve a la ruta predeterminada.

Y mi segunda observación es la ruta de búsqueda para las listas de archivos de configuración/home/users/michel como valor de retorno deos.getcwd(). En realidad esperaba algo dentro de/var/www/michel/testenv.

Mi instinto me dice que la forma en que estoy haciendo la resolución de configuración no es correcta. Principalmente porque el código se ejecuta en el momento de la importación. Esto me lleva a la idea de que tal vez el código de resolución de configuración se ejecuteantes d El entorno WSGI está configurado correctamente. ¿Estoy en algo allí?

Discusión breve / pregunta tangencial

Cómo podríat ¿la resolución de configuración en este caso? Dado que la subcarpeta "modelo" es en realidad un módulo externo, que también debería funcionar en aplicaciones que no sean wsgi, y debería proporcionar un método para configurar una conexión de base de datos.

Personalmente, me gusta la forma en que busco los archivos de configuración sin dejar de anularlos. Solo que el hecho de que el código se ejecute en el momento de la importación hace que mis sentidos de araña se estremezcan como locos. La razón detrás de esto: el manejo de la configuración está completamente oculto (barrera de abstracción) por otros desarrolladores que usan este módulo, y "simplemente funciona". Solo necesitan importar el módulo (con un archivo de configuración existente, por supuesto) y pueden saltar directamente sin conocer los detalles de la base de datos. Esto también les brinda una manera fácil de trabajar con diferentes bases de datos (desarrollo / prueba / implementación) y cambiar entre ellas fácilmente.

Ahora, dentro de mod_wsgi ya no lo hace:

Actualizar

Ahora mismo, para probar mi idea anterior, cambié lawebui.py a lo siguiente:

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 salida en la página web es la siguiente:

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

Esto muestra el mismo entorno que el visto por otros métodos de depuración. Así que mi inicial fue incorrecta. Pero ahora me di cuenta de algo extraño todavía. @os.environment['PWD'] está configurado en la carpeta donde tengo mis archivos de desarrollo. Esto no está entodo donde se ejecuta la aplicación. Más extraño aún,os.getcwd() devoluciones/home/users/michel? Esto es inconsistente con lo que veo enos.environ. ¿No debería ser lo mismo queos.environ['PWD']?

in embargo, el problema más importante sigue siendo: ¿por qué el valor es establecido por @ de apachSetEnv (MYAPP_PATH en este caso) no encontrado enos.environ?

Respuestas a la pregunta(4)

Su respuesta a la pregunta