WSGI es una interface simple y universal entre los servidores web y las aplicaciones web o frameworks (ver más en PEP 333)

WSGI es similar a la especificación Java Servlet o ASP/ASP.NET. En general, es mucho más simple que dichas especificaciones, y se basa en el estandard CGI con mejoras "pitónicas" para hacerla reentrante, persistente, etc.

Resumen de la Especificación

  • Del lado de la aplicación, se especifica un punto de entrada (objeto, método, función), con dos parámetros: las variables de entorno (environ y la función para iniciar la respuesta start_response(status,response_headers) que envía el estado y los encabezados), y debe devolver un iterable con los datos para enviar al cliente.

  • Del lado del servidor, se invoca la aplicación por cada pedido que recibe del cliente HTTP, con las variables de entorno establecidas (estilo CGI)

Ejemplo

# Aqui va mi 'Hola PyAr!, pero con WSGI, una maravilla de Python.

from wsgiref.simple_server import make_server

def hello(environ, start_response):
    start_response('200 OK',[('Content-type','text/plain')])
    return ['Hola PyAr!']

httpd = make_server('',8000, hello).serve_forever()

(copiado de un mail "hello-word" de la lista)

Variables de entorno (diccionario {{{environ}}})

El diccionario environ que se recibe con cada pedido HTTP, contiene las variables estándard de la especificación CGI, entre ellas:

  • REQUEST_METHOD: método "GET", "POST", tec.

  • SCRIPT_NAME : la parte inicial de la "ruta", que corresponde a la aplicación

  • PATH_INFO: la segunda parte de la "ruta", determina la "ubicación" virtual dentro de la aplicación

  • QUERY_STRING: la porción de la URL que sigue al "?", si existe

  • CONTENT_TYPE, CONTENT_LENGTH de la petición HTTP

  • SERVER_NAME, SERVER_PORT, que combinadas con SCRIPT_NAME y PATH_INFO dan la URL

  • SERVER_PROTOCOL: la versión del protocolo ("HTTP/1.0" or "HTTP/1.1")

  • Variables HTTP

Configuración apache + mod_python

mod_python tiene varios handlers o "controladores" (ver documentación):

  • Handler PSP: utilizado para procesar documentos .psp con código python y html mezclado (similar a PHP)

  • Handler CGI: emula el entorno CGI (no confundir con WSGI). No es reentrante ni persistente, y es el método más lento para ejecutar scripts web, pero a su vez es históricamente "compatible" con scripts viejos.

  • Handler Publisher: es un poco mas de "alto nivel". En general, se usaría si uno quiere hacer una aplicación sencilla, con las url mapeadas automáticamente a funciones, etc.

  • Handler propio: más rapido, pero a costa de tener que programar a mas "bajo nivel" (directamente con las interfaces de apache). Aplicaciones mas avanzadas que requieren un mayor control sobre las url, encabezados, etc., usan handlers propios (ejemplo: trac, moin, etc.):

Igualmente, estos Handlers no son compatibles con WSGI, por eso no recomendaría usar ninguno de ellos directamente, sino a través del wrapper WSGI (con ModPythonGateway) que es un handler "propio" que traduce las peticiones al estandar WSGI. Es algo mucho mas estandar, valga la redundancia, y el día de mañana se puede usar cualquier servidor compatible con python, no solo apache.

Además, puede utilizarse directamente mod_wsgi (ver siguiente sección).

Ejemplos de configuración (tanto en /etc/apache2/... en un archivo .htaccess en el mismo directorio):

# handler Publisher:
#  se ejecutará cualquier archivo .py del directorio, llamando a la función de la url:
#  http://www.mysite.com/hello.py/say  ejecutara el script hello.py, funcion say
<Directory /var/www/html/python/>
    SetHandler mod_python
    PythonHandler mod_python.publisher
    PythonDebug On
</Directory>

# Handler PSP:
#  se ejecutará cualquier archivo .psp (código python embebido en texto html)
<Directory /var/www/html/psp/>
   AddHandler mod_python .psp
   PythonHandler mod_python.psp
</Directory>

# Handler CGI:
#  se ejecutará los scripts .py (scripts normales de python) simil linea de comandos
<Directory /var/www/cgi-bin/>
   SetHandler mod_python
   PythonHandler mod_python.cgihandler
   Options ExecCGI
</Directory>

# handler propio:
#  se ejecuta el archivo myscript.py función handler(req)
<Directory /mywebdir>
     AddHandler mod_python .py
     PythonHandler myscript
     PythonDebug On
</Directory>

Para configurar una aplicación wsgi en mod_python:

SetHandler python-program
PythonHandler modpython_gateway::handler
PythonOption wsgi.application app::WSGIApp
PythonPath "['C:/Archivos de programa/Apache Software Foundation/Apache2.2/htdocs/app'] + sys.path"
PythonOption SCRIPT_NAME /app

Descripción:

  • Se habilita el handler propio

  • Se establece el handler a ejecutar (en este caso, el wrapper wsgi)

  • Se especifican las opciones de la aplicación wsgi (app es el nombre de archivo, WSGIApp es el punto de entrada)

  • Se agrega el script de la aplicación al path para poder ejecutarla

  • Se establece el nombre del script a mostrar (sino, en ocasiones, apache puede informar mal o de manera distinta el nombre de script con problemas en el ruteo de urls)

Configuración apache + mod_wsgi

Para usar WSGI directamente desde apache, existe mod_wsgi, que es un módulo mas reciente, totalmente codificado en C para una mejor performance y estabilidad, que simplifica y resuelve las carencias de mod_python:

Ejemplo 1: ejecutar en el mismo proceso que apache (no independiente, estilo mod_python/php/etc.). En este caso se mapea la url /app al script wsgi app.py:

WSGIScriptAlias /app /usr/local/apache/app.py

Ejemplo 2: ejecutar en un proceso (interprete) independiente con un usuario arbitrario diferente de apache (estilo FastCGI, mejorando seguridad y performance):

WSGIDaemonProcess site-1 user=trac group=trac threads=25
WSGIScriptAlias /site-1 /usr/local/apache/app.py
<Directory /usr/local/apache>
WSGIProcessGroup site-1
WSGIApplicationGroup %{GLOBAL}
</Directory>

Configuración lighttpd + wsgi

Ejemplo "avanzado"

Con respecto a la diferencia con PHP/PSP, la mayoría de las aplicaciones web en python tienen un solo punto de entrada (un solo .py), que funciona como "despachador", dependiendo de que url te piden, se llama a una función o a otra (generalmente se usa la variable de entorno SCRIPT_NAME o similar, o directamente usar cherrypy, django, turbogears, etc., para que ruteen las peticiones a las clases/funciones que correspondan)

Ejemplo muy simple con WSGI:

def App(environ, start_response):
        "Punto de entrada WSGI"
        if environ['SCRIPT_NAME'].endswith("xxxx"):
                respuesta_html = xxxx(environ)
        elif environ['SCRIPT_NAME'].endswith("yyyy"):
                respuesta_html = yyyy(environ)
        else:
                respuesta_html = "<html><body><p>la url es
inválida!</p></body></html>"
        start_response ("200 Ok", [('Content-Type','text/html')])
        yield respuesta_html

Entonces, si te llaman www.tuservidor.com/aplicacion/xxxx haces una cosa (xxxx), mientras que si llaman a www.tuservidor.com/aplicacion/yyyy haces otra (yyyy). En comparación con php/psp, sería como llamar a www.tuservidor.com/aplicacion.psp?funcion=xxxx o www.tuservidor.com/aplicacion.psp?funcion=yyyy.

Esto es un poco mas difícil de entender, pero a la larga es mas flexible porque no te limita a tener un archivo (estructura "física") para cada dirección (estructura "lógica"), limpiando un poco la url de extensiones .py, signos de interrogación, etc. , haciéndolas mas fáciles de entender para el usuario.