martes, 8 de diciembre de 2009

Depurando errores en aplicaciones web CGI con Python

Powered by Python

La tendencia de arrendar un servidor compartido para hospedar sitios web parece que va en ascenso. Las limitaciones (e.g. nada de acceso a los log de Apache, ni posibilidad de instalar módulos ni de modificar directamente el httpd.conf) resultan frecuentes en estos casos. ¿Cómo depurar entonces los errores que pueden aparecer al desplegar nuestra aplicación web? Les muestro cómo se haría con scripts de Python ejecutados como CGI. El ejemplo utilzado ilustra además la manera de convertir una aplicación WSGI en un script CGI. Este enfoque es tan sencillo que me he decidido a compartir mis descubrimientos con Ustedes. ¡Espero que les sea útil!

El problema

Sí señores, resulta que pueden ocurrir errores incluso aunque se haya ensayado todo localmente en un servidor de prueba. Tenga en cuenta que las medidas de control de acceso y protección tomadas por el proveedor más la configuración de los servicios en el ordenador remoto hacen que el contexto de ejecución pueda ser totalmente diferente.

En condiciones normales se estila revisar los logs del servidor web pues en ellos quedan registradas las fallas detectadas en tiempo de ejecución. De esta forma se pueden valorar los síntomas y (a veces con una cuota de suerte ;o) se logra tener una pista de las causas. Resulta ser que esto a veces no es posible en los contextos que mencionaba anteriormente. Por otra parte las posibles causas son muchísimas y pueden depender de las configuraciones de los diferentes servicios o de parches específicos aplicados a las aplicaciones subyacentes. Esto implica que a la hora de la verdad comenzamos con las ráfagas de parches a ciegas. ¡Por suerte tenemos a Python!

Una aplicación WSGI sencilla

Las razones anteriores pueden darnos un indicio de que la solución es tomar al toro por los cuernos y hacernos responsables de descubrir los defectos nosotros mismos (especialmente si estamos seguros de que el script se ejecuta pero no cómo esperábamos, e.g. al recibir un error HTTP 500 Internal Server Error en el navegador web). Pero ¿cómo hacer una aplicación sencilla?

La respuesta no es única. Por suerte hay muchos caminos para llegar a Roma ;o). La manera estándar para escribir aplicaciones web con Python es el modelo WSGI. ¡Echémosle un vistazo! Una aplicación WSGI muy sencilla luce más o menos así :

#! /usr/bin/env python

def application(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/plain')]
    start_response(status, headers)
    return ['Hello world ! \n\n', 'The simplest web app ever seen']

Como se puede apreciar solo se necesita escribir una función. Se recomienda que su nombre sea application. Esto se debe a que es el nombre predeterminado que utiliza el módulo mod_wsgi (i.e. Apache) para identificar la aplicación web que se ejecutará al invocar un módulo. La función (o en general cualquier objeto invocable o callable en inglés) tiene que aceptar dos parámetros. El primero es un diccionario que contiene valores que describen el entorno de ejecución. Entre los más relevantes se encuentran el nombre del host, los parámetros de la URL solicitada, el método (e.g. POST, GET ...) y hay otros más.

El segundo argumento es una función que se encarga de iniciar el proceso de envío de la repuesta del servidor hacia el cliente. La razón para hacer esto es que cada servidor web realiza esta operación de una forma diferente, pero siempre es capaz de facilitar una implementación de dicha función para encapsular los detalles. Como se aprecia en el ejemplo, esta función acepta como parámetros el texto que describe el código HTTP y una lista de tuplas binarias. Los primeros están definidos en el estándar RFC 2616. Por otra parte cada tupla representa el nombre y el valor de un encabezamiento que será retornado hacia el cliente. El valor que retorna la función es utilizado para enviar una respuesta hacia el cliente. Todo el contenido no tiene que ser enviado de una sola vez. Si nos fijamos bien, lo que se devuelve es una secuencia con varios segmentos de la respuesta. Estas porciones de la respuesta se envían en el mismo orden y poco a poco hacia el cliente. Esto es importante desde el punto de vista de la eficiencia puesto a que, si se está generando el cuerpo de la respuesta, no hay que esperar a realizar todo el procesamiento para hacer llegar todos los datos al cliente. Es por esto que los servidores y frameworks hechos con Python tienen tiempos de respuesta bastante pequeños.

Para poner a funcionar esta aplicación con mod_wsgi solo hace falta la siguiente directiva en el fichero httpd.conf (o equivalente ;o)

WSGIScriptAlias /helloworld /path/to/script.py

Si se accede a http://example.com/helloworld entonces se obtendría el siguiente texto

Hello world ! 

The simplest web app ever seen

Reutilizando la aplicación WSGI en otros contextos

Sin embargo, una restricción frecuente es que no están instalados los módulos de Apache (que viene siendo el caso cada vez más frecuente :o) que son necesarios para ejecutar este tipo de aplicaciones y solo tenemos a mod_python. Otras muchas veces ni siquiera tenemos eso y solo queda la opción de CGI. En estos casos el estándar WSGI todavía resulta útil para hacer las cosas una sola vez y que todo funcione en varios contextos de ejecución. Solo hay que hacer las siguientes modificaciones

if __name__ == '__main__' :
  from wsgiref.handlers import CGIHandler
  CGIHandler().run(application)

Listo !

Depurando los scripts con el módulo cgitb

El módulo cgitb provee un tratamiento de errores muy simple y que funciona incluso bajo situaciones extremas. Lo único que hay que hacer es añadir las dos líneas siguientes

import cgitb
cgitb.enable()

Después de hacer esto, si ocurre un error en la aplicación entonces ya no obtenemos el lacónico mensaje 500 Internal Server Error, sino más bien un reporte HTML detallando el error (incluso más informativo que el que se mostraría en la consola ;o). La función cgitb.enable intercepta el mecanismo estándar de procesamiento de excepciones para hacer su trabajo. También acepta otros parámetros, por ejemplo :

Llamada Efecto
enable(display=0) No mostrar los tracebacks
enable(logdir="/path/to/dir") Los reportes se encriben en ficheros ubicados en /path/to/dir
enable(format="text") Imprime reportes en modo texto

Comprobando que el servidor ejecuta los scripts

Todo lo que hemos visto es estándar. Pero todavía hay más. A veces es necesario determinar si el error es del servidor o es provocado por la aplicación web. Con estos fines utilizo el script siguiente

#! /usr/bin/env python

from wsgiref.simple_server import demo_app as application

if __name__ == '__main__' :
  from wsgiref.handlers import CGIHandler
  CGIHandler().run(application)

La función wsgiref.simple_server.demo_app es una aplicación WSGI pequeña pero completa, que visualiza en el navegador una página en texto plano con el mensaje Hello world'' y los pares de valores que describen en el contexto de ejecución. Resulta muy útil para verificar que el servidor WSGI funciona correctamente.

Conclusiones

El modelo WSGI se ajusta muy bien a las necesidades de los servidores web y del protocolo HTTP, haciendo énfasis en la eficiencia. El mismo resulta ser muy completo y ofrece soporte para otras tecnologías web. Sin embargo no resuelve los problemas cuando se despliega una aplicación en un hosting compartido o en otros contextos con restricciones especiales. Es por esto que el módulo cgitb resulta tan importante cuando nos encontramos en un callejón sin salida al desarrollar nuestra aplicación web. Como por arte de magia podemos tener un análisis exhaustivo de las causas del error escribiendo solamente dos líneas de código adicionales. Es así de fácil. Espero que le sean útiles estos consejos. Les espero pronto aquí en el blog de Simelo.

No hay comentarios:

Publicar un comentario