cgi — Soporte de Interfaz de Entrada Común (CGI)

Código fuente: Lib/cgi.py

Obsoleto desde la versión 3.11: The cgi module is deprecated (see PEP 594 for details and alternatives).


Módulo de soporte para scripts de la Interfaz de Entrada Común (CGI)

Este módulo define una serie de utilidades para el uso de scripts CGI escritos en Python.

Introducción

Un script de CGI es invocado por un servidor HTTP, generalmente para procesar entradas de usuario entregadas mediante un elemento HTML <FORM> o <ISINDEX>.

Muy a menudo, los scripts CGI viven en el directorio especial cgi-bin del servidor. El servidor HTTP coloca todo tipo de información sobre la solicitud (como el nombre de host del cliente, la dirección URL solicitada, la cadena de búsqueda (query string) y muchas otras consultas) en el entorno de shell del script, ejecuta el script y envía la salida del script al cliente.

La entrada del script también está conectada al cliente, y a veces los datos del formulario se leen de esta manera; en otras ocasiones los datos del formulario se pasan a través de la parte «cadena de caracteres de búsqueda (query string)» de la dirección URL. Este módulo está diseñado para ocuparse de los diferentes casos y proporcionar una interfaz más simple al script de Python. También proporciona una serie de utilidades que ayudan en la depuración de scripts, y la última adición es la compatibilidad con cargas de archivos desde un formulario (si el navegador lo admite).

La salida de un script CGI debe constar de dos secciones, separadas por una línea en blanco. La primera sección contiene una serie de encabezados, indicando al cliente qué tipo de datos sigue. El código de Python para generar una sección de encabezado mínima tiene este aspecto:

print("Content-Type: text/html")    # HTML is following
print()                             # blank line, end of headers

La segunda sección suele ser HTML, lo que permite al software cliente mostrar texto bien formateado con encabezado, imágenes en línea, etc. Aquí está el código Python que imprime una simple pieza de HTML:

print("<TITLE>CGI script output</TITLE>")
print("<H1>This is my first CGI script</H1>")
print("Hello, world!")

Usando el módulo CGI

Empieza escribiendo import cgi.

Cuando escribas un nuevo script, considera añadir estas líneas:

import cgitb
cgitb.enable()

Esto activa un manejador de excepciones especial que mostrará informes detallados en el explorador Web si se produce algún error. Si prefiere no mostrar en detalle su programa a los usuarios de su script, puede tener los informes guardados en archivos en su lugar, con código como este:

import cgitb
cgitb.enable(display=0, logdir="/path/to/logdir")

Es muy útil usar esta característica durante el desarrollo de scripts. Los informes producidos por cgitb proporcionan información que puede ahorrarle mucho tiempo en el seguimiento de errores. Siempre puede eliminar la línea cgitb más adelante cuando haya probado su script y esté seguro de que funciona correctamente.

To get at submitted form data, use the FieldStorage class. If the form contains non-ASCII characters, use the encoding keyword parameter set to the value of the encoding defined for the document. It is usually contained in the META tag in the HEAD section of the HTML document or by the Content-Type header. This reads the form contents from the standard input or the environment (depending on the value of various environment variables set according to the CGI standard). Since it may consume standard input, it should be instantiated only once.

La instancia FieldStorage puede ser indexada como un diccionario Python. Permite las pruebas de afiliación con el operador in, y también es compatible con el método de diccionario estándar keys() y la función incorporada len(). Los campos de formulario que contienen cadenas de caracteres vacías son ignoradas y no aparecen en el diccionario; para mantener estos valores, proporciona un valor verdadero para el parámetro de palabra clave opcional keep_blank_values al crear la instancia FieldStorage.

Por ejemplo, el código siguiente (que supone que el encabezado Content-Type y la línea en blanco ya se han impreso) comprueba que los campos name y addr son ambos establecidos a una cadena de caracteres no vacía:

form = cgi.FieldStorage()
if "name" not in form or "addr" not in form:
    print("<H1>Error</H1>")
    print("Please fill in the name and addr fields.")
    return
print("<p>name:", form["name"].value)
print("<p>addr:", form["addr"].value)
...further form processing here...

Aquí los campos, a los que se accede a través de form[key], son por sí mismos las instancias de FieldStorage (o MiniFieldStorage, dependiendo de la codificación del formulario). El atributo value de la instancia produce el valor de cadena de caracteres del campo. El método getvalue() retorna este valor de cadena de caracteres directamente; también acepta un segundo argumento opcional como valor predeterminado para retornar si la clave solicitada no está presente.

Si los datos del formulario enviados contienen más de un campo con el mismo nombre, el objeto recuperado por form[key] no es una instancia FieldStorage o MiniFieldStorage, sino una lista de dichas instancias. De forma similar, en esta situación, form.getvalue(key) retornaría una lista de cadenas de caracteres. Si espera esta posibilidad (cuando su formulario HTML contiene múltiples campos con el mismo nombre), utilice el método getlist(), que siempre retorna una lista de valores (para que no sea necesario poner en mayúsculas y minúsculas en el caso de un solo elemento). Por ejemplo, este código concatena cualquier número de campos de nombre de usuario, separados por comas:

value = form.getlist("username")
usernames = ",".join(value)

Si un campo representa un archivo cargado, el acceso al valor a través del atributo value o el método getvalue() lee todo el archivo en memoria como bytes. Puede que esto no sea lo que quiera que ocurra. Puede probar un archivo cargado probando el atributo filename o el atributo file. Después, puede leer los datos del atributo file antes de que se cierre automáticamente como parte de la recolección de elementos no utilizados de la instancia FieldStorage (los métodos read() y readline() retornarán bytes):

fileitem = form["userfile"]
if fileitem.file:
    # It's an uploaded file; count lines
    linecount = 0
    while True:
        line = fileitem.file.readline()
        if not line: break
        linecount = linecount + 1

Los objetos FieldStorage también permiten ser usados en una sentencia with , lo que automáticamente los cerrará cuando termine la sentencia.

Si un error es encontrado al obtener el contenido de un archivo cargado (por ejemplo, cuando el usuario interrumpe el envío del formulario haciendo clic en un botón Atrás o Cancelar), el atributo done del objeto para el campo se establecerá en el valor -1.

El borrador de archivo de carga estándar presenta la posibilidad de cargar varios archivos desde un campo (utilizando una codificación recursiva multipart/*). Cuando esto ocurre, el elemento será un elemento similar a un diccionario FieldStorage. Esto se puede determinar probando su atributo type, que debe ser multipart/form-data (o tal vez otro tipo MIME que coincida multipart/*). En este caso, se puede iterar recursivamente al igual que el objeto de formulario de nivel superior.

Cuando se envía un formulario en el formato «antiguo» (como la cadena de caracteres de consulta (query string) o como una sola parte de datos de tipo application/x-www-form-urlencoded), los elementos serán realmente instancias de la clase MiniFieldStorage. En este caso, los atributos list, file y filename siempre son None.

Un formulario enviado a través de POST que también tiene una cadena de caracteres de consulta (query string) contendrá los elementos FieldStorage y MiniFieldStorage.

Distinto en la versión 3.4: El atributo file se cierra automáticamente con el recolector de basura de la instancia creada FieldStorage.

Distinto en la versión 3.5: Agregado soporte para el protocolo de administrador de contexto a la clase FieldStorage .

Interfaz de Nivel Superior

La sección anterior explica cómo leer datos de un formulario CGI usando la clase FieldStorage. Esta sección describe un nivel de interfaz superior que se añadió a esta clase para permitir que uno lo haga de una manera más legible e intuitiva. La interfaz no hace que las técnicas descritas en las secciones anteriores estén obsoletas – por ejemplo, siguen siendo útiles para procesar la carga de archivos de manera eficiente.

La interfaz consiste en dos métodos simples. Usando los métodos puedes procesar datos de formulario de una manera genérica, sin la necesidad de preocuparte si solo se publicaron uno o más valores con un solo nombre.

En la sección anterior, aprendiste a escribir el siguiente código cada vez que esperabas que un usuario publicara más de un valor con un nombre:

item = form.getvalue("item")
if isinstance(item, list):
    # The user is requesting more than one item.
else:
    # The user is requesting only one item.

Esta situación es común, por ejemplo, cuando un formulario contiene un grupo de múltiples casillas de verificación (checkboxes) con el mismo nombre:

<input type="checkbox" name="item" value="1" />
<input type="checkbox" name="item" value="2" />

En la mayoría de las situaciones, sin embargo, solo hay un control de formulario con un nombre determinado en un formulario y así, espera y solo necesita un valor asociado con este nombre. Así que escribe un script que contiene, por ejemplo, este código:

user = form.getvalue("user").upper()

El problema con el código es que nunca debe esperar que un cliente proporcione una entrada válida a los scripts. Por ejemplo, si un usuario curioso anexa otro par user=foo a la cadena de caracteres de consulta (query string), el script se bloquearía, porque en esta situación la llamada al método getvalue("user") retorna una lista en lugar de una cadena de caracteres. Llamar al método upper() en una lista no es válido (ya que las listas no tienen un método con este nombre) y se produce una excepción AttributeError.

Por lo tanto, la forma adecuada de leer los valores de datos de formulario era usar siempre el código que comprueba si el valor obtenido es un valor único o una lista de valores. Eso es molesto y conduce a scripts menos legibles.

Un enfoque más conveniente es utilizar los métodos getfirst() y getlist() proporcionados por esta interfaz de nivel superior.

FieldStorage.getfirst(name, default=None)

Este método siempre retorna solo un valor asociado con el campo de formulario name. El método retorna solo el primer valor en caso de que se registraran más valores con dicho nombre. Tenga en cuenta que el orden en que se reciben los valores puede variar de un navegador a otro y no debe darse por sentado. 1 Si no existe ningún campo o valor de formulario, el método retorna el valor especificado por el parámetro opcional default. Este parámetro tiene como valor predeterminado None si no se especifica.

FieldStorage.getlist(name)

Este método siempre retorna una lista de valores asociados con el campo de formulario name. El método retorna una lista vacía si no existe tal campo o valor de formulario para name. Retorna una lista que consta de un elemento si solo existe un valor de este tipo.

Usando estos métodos puede escribir código compacto agradable:

import cgi
form = cgi.FieldStorage()
user = form.getfirst("user", "").upper()    # This way it's safe.
for item in form.getlist("item"):
    do_something(item)

Funciones

Estas son útiles si desea más control, o si desea emplear algunos de los algoritmos implementados en este módulo en otras circunstancias.

cgi.parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False, separator="&")

Analiza una consulta en el entorno o desde un archivo (el archivo predeterminado es sys.stdin). Los parámetros keep_blank_values, strict_parsing y separator se pasan a urllib.parse.parse_qs() sin cambios.

cgi.parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator="&")

Analiza la entrada de tipo multipart/form-data (para cargas de archivos). Los argumentos son fp para el archivo de entrada, pdict para un diccionario que contiene otros parámetros en el encabezado Content-Type y encoding, la codificación de la solicitud.

Retorna un diccionario al igual que urllib.parse.parse_qs(): las claves son los nombres de campo, cada valor es una lista de valores para ese campo. Para los campos que no son de archivo, el valor es una lista de cadenas de caracteres.

Esto es fácil de usar pero no muy bueno si espera que se carguen megabytes — en ese caso, utilice la clase FieldStorage en su lugar, que es mucho más flexible.

Distinto en la versión 3.7: Se agregaron los parámetros encoding y errors. Para los campos que no son de archivo, el valor es ahora una lista de cadenas de caracteres, no bytes.

Distinto en la versión 3.9.2: Agregado el parámetro separator.

cgi.parse_header(string)

Analiza un encabezado MIME (como Content-Type) en un valor principal y un diccionario de parámetros.

cgi.test()

Robust test CGI script, usable as main program. Writes minimal HTTP headers and formats all information provided to the script in HTML format.

cgi.print_environ()

Da formato al entorno del shell en HTML.

cgi.print_form(form)

Da formato a un formulario en HTML.

cgi.print_directory()

Da formato al directorio actual en HTML.

cgi.print_environ_usage()

Imprime una lista de variables de entorno útiles (utilizadas por CGI) en HTML.

Preocuparse por la seguridad

There’s one important rule: if you invoke an external program (via os.system(), os.popen() or other functions with similar functionality), make very sure you don’t pass arbitrary strings received from the client to the shell. This is a well-known security hole whereby clever hackers anywhere on the Web can exploit a gullible CGI script to invoke arbitrary shell commands. Even parts of the URL or field names cannot be trusted, since the request doesn’t have to come from your form!

Para estar en el lado seguro, si debe pasar una cadena de caracteres de un formulario a un comando de shell, debe asegurarse de que la cadena de caracteres contiene solo caracteres alfanuméricos, guiones, guiones bajos y puntos.

Instalando su script de CGI en un sistema Unix

Lea la documentación del servidor HTTP y consulte con el administrador del sistema local para encontrar el directorio donde se deben instalar los scripts CGI; por lo general, esto se encuentra en un directorio cgi-bin en el árbol del servidor.

Asegúrese de que el script es legible y ejecutable por «otros»; el modo de archivo Unix debe ser octal 0o755 (utilice chmod 0755 filename). Asegúrese de que la primera línea del script contiene #! a partir de la columna 1 seguida del nombre de ruta del intérprete de Python, por ejemplo:

#!/usr/local/bin/python

Asegúrese que el intérprete de Python exista y sea ejecutable por «otros».

Asegúrese de que cualquier archivo que su script necesite leer o escribir sea legible o tenga permiso de escritura, respectivamente, por «otros» — su modo debe ser 0o644 para legible y 0o666 para escribir. Esto se debe a que, por razones de seguridad, el servidor HTTP ejecuta el script como usuario «nadie», sin ningún privilegio especial. Sólo puede leer (escribir, ejecutar) archivos que todo el mundo puede leer (escribir, ejecutar). El directorio actual en tiempo de ejecución también es diferente (normalmente es el directorio cgi-bin del servidor) y el conjunto de variables de entorno también es diferente de lo que se obtiene al iniciar sesión. En particular, no cuente con la ruta de búsqueda del shell para ejecutables (PATH) o la ruta de búsqueda del módulo Python (PYTHONPATH) que se establecerá en cualquier cosa interesante.

Si necesita cargar módulos desde un directorio el cual no está en la ruta de búsqueda de módulos predeterminada de Python, puede cambiar la ruta en su script, antes de importar otros módulos. Por ejemplo:

import sys
sys.path.insert(0, "/usr/home/joe/lib/python")
sys.path.insert(0, "/usr/local/lib/python")

(¡De esta manera, el directorio insertado de último será buscado primero!)

Las instrucciones para sistemas que no son Unix pueden variar; consulte la documentación de su servidor HTTP (usualmente tendrá una sección sobre scripts CGI).

Probando su script de CGI

Desafortunadamente, un script CGI generalmente no se ejecutará cuando lo pruebe desde la línea de comandos, y un script que funcione perfectamente desde la línea de comandos puede fallar misteriosamente cuando se ejecuta desde el servidor. Hay una razón por la que debe probar el script desde la línea de comandos: si contiene un error de sintaxis, el intérprete de Python no lo ejecutará en absoluto y lo más probable es que el servidor HTTP envíe un error críptico al cliente.

Asumiendo que su script no tiene errores de sintaxis, pero este no funciona, no tiene más opción que leer la siguiente sección.

Depurando scripts de CGI

First of all, check for trivial installation errors — reading the section above on installing your CGI script carefully can save you a lot of time. If you wonder whether you have understood the installation procedure correctly, try installing a copy of this module file (cgi.py) as a CGI script. When invoked as a script, the file will dump its environment and the contents of the form in HTML format. Give it the right mode etc., and send it a request. If it’s installed in the standard cgi-bin directory, it should be possible to send it a request by entering a URL into your browser of the form:

http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home

Si esto da un error de tipo 404, el servidor no puede encontrar el script – quizás necesite instalarlo en un directorio diferente. Si este da otro error, hay un problema con la instalación que debería intentar solucionar antes de continuar. Si obtiene una lista bien formateada del entorno y el contenido del formulario (en este ejemplo, los campos deberían estar listados como «addr» con el valor «At Home» y «name» con el valor «Joe Blow»), el script cgi.py ha sido instalado correctamente. Si sigue el mismo procedimiento para su propio script, ya debería poder depurarlo.

El siguiente paso podría ser llamar a la función test() del módulo cgi de su script: reemplace su código principal con la declaración única

cgi.test()

Esto debería producir los mismos resultados que los obtenidos al instalar el archivo cgi.py en sí.

Cuando un script de Python normal lanza una excepción no controlada (por cualquier razón: de un error tipográfico en un nombre de módulo, un archivo que no se puede abrir, etc.), el intérprete de Python imprime un traceback sutil y sale. Aunque el intérprete de Python seguirá haciendo esto cuando el script CGI lance una excepción, lo más probable es que el traceback termine en uno de los archivos de registro del servidor HTTP o se descarte por completo.

Afortunadamente, una vez que haya logrado que su script ejecute algún código, puede enviar fácilmente tracebacks al navegador web utilizando el módulo cgitb. Si aún no lo ha hecho, solo añada las líneas:

import cgitb
cgitb.enable()

al principio de su script. Luego intente ejecutarlo de nuevo; cuando un problema ocurra, debería ver un informe detallado que probablemente muestre la causa del error.

Si sospecha que puede haber un problema al importar el módulo cgitb, puede usar un enfoque aún más robusto (que solo usa módulos integrados):

import sys
sys.stderr = sys.stdout
print("Content-Type: text/plain")
print()
...your code here...

Esto se basa en el intérprete de Python para imprimir el traceback. El tipo de contenido de la salida se establece en texto plano, lo que deshabilita todo el procesamiento HTML. Si el script funciona, el cliente mostrará el código HTML sin formato. Si lanza una excepción, lo más probable es que después que se hayan impreso las dos primeras líneas, se mostrará un traceback. Dado que no se está realizando ninguna interpretación HTML, el traceback será legible.

Problemas comunes y soluciones

  • La mayoría de servidores HTTP almacenan en búfer la salida de los scripts CGI hasta que el script esté completado. Esto significa que no es posible mostrar un reporte del progreso en la parte del cliente mientras que el script se esté ejecutando.

  • Compruebe las instrucciones de instalación anteriores.

  • Verifique los archivos de registro (logs) del servidor HTTP. (¡Usar tail -f logfile en una ventana separada puede ser útil!)

  • Siempre verifique un script para encontrar errores de sintaxis primero, haciendo algo como python script.py.

  • Si su script no tiene ningún error sintáctico, pruebe añadiendo import cgitb; cgitb.enable() en la parte superior del script.

  • Cuando se invoquen programas externos, asegúrese de que pueden ser encontrados. Generalmente esto significa usar nombres de ruta absolutos — PATH generalmente no se establece en un valor útil en un script CGI.

  • Al leer o escribir archivos externos, asegúrese de que puedan ser leídas o escritas por el userid por el que su script CGI se va a ejecutar: es típico que esto sea el userid bajo el que el servidor web se está ejecutando, o algún userid especificado explícitamente por la función suexec del servidor web.

  • No intente darle un modo set-uid a un script CGI. Esto no funciona en la mayoría de sistemas, además de ser un riesgo de seguridad.

Notas al pie

1

Tenga en cuenta que algunas versiones recientes de las especificaciones de HTML establecen en qué orden se deben suministrar los valores de campo, pero saber si se recibió una solicitud de un navegador adaptado, o incluso desde un navegador siquiera, es tedioso y propenso a errores.