Programación CGI en Python


Qué es CGI

CGI actualmente es mantenido por NCSA, que define CGI de la siguiente manera:

CGI (Common Gateway Interface), interfaz de puerta de enlace común, es un programa que se ejecuta en un servidor, como un servidor HTTP, y proporciona una interfaz con las páginas HTML del cliente.


Navegación web

Para comprender mejor cómo funciona CGI, podemos partir del flujo al hacer clic en un enlace o URL en una página web:

  • 1. Usa tu navegador para acceder a la URL y conectarte al servidor web HTTP.
  • 2. El servidor web, tras recibir la solicitud, analiza la URL y verifica si el archivo solicitado existe en el servidor. Si existe, devuelve el contenido del archivo; si no, devuelve el mensaje de error correspondiente.
  • 3. El navegador recibe la información del servidor y muestra el archivo recibido o el mensaje de error.

Un programa CGI puede ser un script de Python, un script de PERL, un script de SHELL, un programa en C o C++, etc.


Diagrama de arquitectura CGI

cgiarch


Soporte y configuración del servidor web

Antes de realizar programación CGI, asegúrate de que tu servidor web soporte CGI y de que ya esté configurado el manejador de CGI.

Configuración de soporte CGI en Apache:

Configura el directorio CGI:

ScriptAlias /cgi-bin/ /var/www/cgi-bin/

Todos los programas CGI que ejecutan los servidores HTTP se guardan en un directorio preconfigurado. Este directorio se llama directorio CGI y, por convención, se denomina /var/www/cgi-bin.

La extensión de los archivos CGI es .cgi, aunque Python también puede usar la extensión .py.

Por defecto, el directorio cgi-bin configurado en el servidor Linux es /var/www.

Si deseas especificar otro directorio para ejecutar scripts CGI, puedes modificar el archivo de configuración httpd.conf, como se muestra a continuación:

<Directory "/var/www/cgi-bin">
   AllowOverride None
   Options +ExecCGI
   Order allow,deny
   Allow from all
</Directory>

Añade la extensión .py en AddHandler, de esta manera podremos acceder a los archivos de script de Python que terminan en .py:

AddHandler cgi-script .cgi .pl .py

Primer programa CGI

Creamos el primer programa CGI con Python, el archivo se llama hello.py, está ubicado en el directorio /var/www/cgi-bin, y su contenido es el siguiente:

Ejemplo

#!/usr/bin/python3

print ("Content-type:text/html")
print ()                             # Línea en blanco, le indica al servidor que finalice la cabecera
print ('<html>')
print ('<head>')
print ('<meta charset="utf-8">')
print ('<title>Hello Word - ¡Mi primer programa CGI!</title>')
print ('</head>')
print ('<body>')
print ('<h2>Hello Word! Soy el primer programa CGI del Tutorial para novatos</h2>')
print ('</body>')
print ('</html>')

Después de guardar el archivo, modifica hello.py y establece los permisos del archivo en 755:

chmod 755 hello.py 

El resultado de acceder al programa anterior en el navegador se muestra a continuación:

El script hello.py es un script simple de Python. La primera línea de salida del script, "Content-type:text/html", se envía al navegador y le indica que el tipo de contenido a mostrar es "text/html".

Usa print para generar una línea en blanco con el fin de indicar al servidor que finalice la información de cabecera.


Cabeceras HTTP

El "Content-type:text/html" en el contenido del archivo hello.py es parte de la cabecera HTTP; se envía al navegador para indicarle el tipo de contenido del archivo.

El formato de la cabecera HTTP es el siguiente:

HTTP 字段名: 字段内容

Por ejemplo:

Content-type: text/html

La siguiente tabla describe la información que se usa frecuentemente en las cabeceras HTTP en programas CGI:

CabezaDescripción
Content-type: Información MIME correspondiente al cuerpo de la solicitud. Por ejemplo: Content-type:text/html
Expires: Date Fecha y hora de caducidad de la respuesta
Location: URL Se utiliza para redirigir al receptor a una URL distinta de la solicitada para completar la solicitud o identificar un nuevo recurso
Last-modified: DateFecha y hora de la última modificación del recurso solicitado
Content-length: NLongitud del contenido de la solicitud
Set-Cookie: String Configurar cookie HTTP

Variables de entorno CGI

Todos los programas CGI reciben las siguientes variables de entorno, que desempeñan un papel importante en los programas CGI:

Nombre de variableDescripción
CONTENT_TYPEEl valor de esta variable de entorno indica el tipo MIME de la información transmitida. Actualmente, la variable de entorno CONTENT_TYPE suele ser: application/x-www-form-urlencoded, lo que indica que los datos provienen de un formulario HTML.
CONTENT_LENGTHCuando el servidor se comunica con el programa CGI mediante el método POST, esta variable de entorno indica el número de bytes de datos válidos que se pueden leer desde la entrada estándar (STDIN); es necesario usar esta variable al leer los datos de entrada.
HTTP_COOKIEContenido de las COOKIES en el cliente.
HTTP_USER_AGENTProporciona información del navegador del cliente, incluido el número de versión u otros datos propietarios.
PATH_INFOEl valor de esta variable de entorno representa información de ruta adicional inmediatamente después del nombre del programa CGI. Con frecuencia aparece como parámetro del programa CGI.
QUERY_STRINGSi el método de transmisión de información entre el servidor y el programa CGI es GET, el valor de esta variable de entorno es la información transmitida. Esta información va después del nombre del programa CGI, separada por un signo de interrogación '?'.
REMOTE_ADDREl valor de esta variable de entorno es la dirección IP del cliente que envía la solicitud, por ejemplo, 192.168.1.67. Este valor siempre está presente. Además, es el único identificador que el cliente web debe proporcionar al servidor web, y se puede usar en el programa CGI para distinguir diferentes clientes web.
REMOTE_HOSTEl valor de esta variable de entorno contiene el nombre de host del cliente que envía la solicitud CGI. Si no se admite la consulta deseada, no es necesario definir esta variable de entorno.
REQUEST_METHODProporciona el método con el que se invocó el script. Para scripts que usan el protocolo HTTP/1.0, solo GET y POST tienen significado.
SCRIPT_FILENAMERuta completa del script CGI
SCRIPT_NAMENombre del script CGI
SERVER_NAMEEste es el nombre de host, alias o dirección IP de tu servidor web.
SERVER_SOFTWAREEl valor de esta variable de entorno contiene el nombre y el número de versión del servidor HTTP que invoca al programa CGI. Por ejemplo, el valor anterior es Apache/2.2.14(Unix)

A continuación se muestra un script CGI simple que genera las variables de entorno de CGI:

Ejemplo

#!/usr/bin/python3

import os

print ("Content-type: text/html")
print ()
print ("<meta charset=\"utf-8\">")
print ("<b>Variables de entorno</b><br>")
print ("<ul>")
for key in os.environ.keys():
    print ("<li><span style='color:green'>%30s </span> : %s </li>" % (key,os.environ[key]))
print ("</ul>")

Guarda el código anterior como test.py, cambia los permisos del archivo a 755, y el resultado de la ejecución es el siguiente:


Métodos GET y POST

El navegador cliente transmite información al servidor mediante dos métodos: el método GET y el método POST.

Transferencia de datos usando el método GET

El método GET envía la información del usuario codificada al servidor; los datos se incluyen en la URL de la página solicitada, separados por el signo '?', como se muestra a continuación:

http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2
Otras notas sobre las solicitudes GET:
  • Las solicitudes GET pueden almacenarse en caché
  • Las solicitudes GET se conservan en el historial del navegador
  • Las solicitudes GET se pueden guardar como marcadores
  • Las solicitudes GET no deben usarse al manejar datos sensibles
  • Las solicitudes GET tienen limitación de longitud
  • Las solicitudes GET solo deben usarse para recuperar datos

Ejemplo simple de URL: método GET

La siguiente es una URL simple que envía dos parámetros al programa hello_get.py usando el método GET:

/cgi-bin/hello_get.py?name=Ejemplo&url=http://www.example.com

El siguiente es el código del archivo hello_get.py:

Ejemplo

#!/usr/bin/python3

# Módulo de procesamiento CGI
import cgi, cgitb

# Crear la instancia de FieldStorage
form = cgi.FieldStorage()

# Obtener datos
site_name = form.getvalue('name')
site_url  = form.getvalue('url')

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Ejemplo de prueba CGI de Tutorial de Novato</title>)
print ("</head>")
print ("<body>")
print (<h2>Sitio web oficial de %s: %s</h2> % (site_name, site_url))
print ("</body>")
print ("</html>")

Después de guardar el archivo, modifique hello_get.py y cambie los permisos del archivo a 755:

chmod 755 hello_get.py 

Resultado de la solicitud del navegador:

Ejemplo simple de formulario: método GET

A continuación hay un formulario HTML que utiliza el método GET para enviar dos datos al servidor. El script del servidor enviado también es el archivo hello_get.py. El código de hello_get.html es el siguiente:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py" method="get">
Nombre del sitio:<input type="text" name="name">  <br />

URL del sitio:<input type="text" name="url" />
<input type="submit" value=Enviar />
</form>
</body>
</html>

De forma predeterminada, el directorio cgi-bin solo puede contener archivos de script. Almacenamos hello_get.html en el directorio test y cambiamos los permisos del archivo a 755:

chmod 755 hello_get.html

La demostración en Gif se muestra a continuación:

Envío de datos usando el método POST

Usar el método POST para transferir datos al servidor es más seguro y confiable. Información sensible como contraseñas de usuario debe transferirse mediante POST.

Lo siguiente también es hello_get.py, que también puede procesar los datos de formularios POST enviados por el navegador:

Ejemplo

#!/usr/bin/python3

# Módulo de procesamiento CGI
import cgi, cgitb

# Crear la instancia de FieldStorage
form = cgi.FieldStorage()

# Obtener datos
site_name = form.getvalue('name')
site_url  = form.getvalue('url')

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Ejemplo de prueba CGI de Tutorial de Novato</title>)
print ("</head>")
print ("<body>")
print (<h2>Sitio web oficial de %s: %s</h2> % (site_name, site_url))
print ("</body>")
print ("</html>")

El siguiente es el formulario que utiliza el método POST (method="post") para enviar datos al script del servidor hello_get.py:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py" method="post">
Nombre del sitio:<input type="text" name="name">  <br />

URL del sitio:<input type="text" name="url" />
<input type="submit" value=Enviar />
</form>
</body>
</html>
</form>

La demostración en Gif se muestra a continuación:

Pasar datos de casillas de verificación (checkbox) a través de un programa CGI

El checkbox se utiliza para enviar uno o más datos de opciones. El código HTML es el siguiente:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
</head>
<body>
<form action="/cgi-bin/checkbox.py" method="POST" target="_blank">
<input type="checkbox" name="ejemplo" value="on" />Tutorial de Novato
<input type="checkbox" name="google" value="on" /> Google
<input type="submit" value=Seleccionar sitio />
</form>
</body>
</html>

El siguiente es el código del archivo checkbox.py:

Ejemplo

#!/usr/bin/python3

# Importar el módulo de procesamiento CGI
import cgi, cgitb

# Crear una instancia de FieldStorage
form = cgi.FieldStorage()

# Recibir datos de campos
if form.getvalue('google'):
   google_flag = Sí
else:
   google_flag = No

if form.getvalue('ejemplo'):
   ejemplo_flag = Sí
else:
   ejemplo_flag = No

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Ejemplo de prueba CGI de Tutorial de Novato</title>)
print ("</head>")
print ("<body>")
print (<h2> ¿Tutorial de Novato seleccionado? : %s</h2> % ejemplo_flag)
print (<h2> ¿Google seleccionado? : %s</h2> % google_flag)
print ("</body>")
print ("</html>")

Cambie los permisos de checkbox.py:

chmod 755 checkbox.py

Gif de demostración del acceso del navegador:

Pasar datos de botones de opción (radio) a través de un programa CGI

Radio solo envía un dato al servidor. El código HTML es el siguiente:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
</head>
<body>
<form action="/cgi-bin/radiobutton.py" method="post" target="_blank">
<input type="radio" name="site" value="ejemplo" />Tutorial de Novato
<input type="radio" name="site" value="google" /> Google
<input type="submit" value=Enviar />
</form>
</body>
</html>

El código del script radiobutton.py es el siguiente:

Ejemplo

#!/usr/bin/python3

# Importar el módulo de procesamiento CGI
import cgi, cgitb

# Crear una instancia de FieldStorage
form = cgi.FieldStorage()

# Recibir datos de campos
if form.getvalue('site'):
   site = form.getvalue('site')
else:
   site = Los datos enviados están vacíos

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Ejemplo de prueba CGI de Tutorial de Novato</title>)
print ("</head>")
print ("<body>")
print (<h2> El sitio web seleccionado es %s</h2> % site)
print ("</body>")
print ("</html>")

Cambie los permisos de radiobutton.py:

chmod 755 radiobutton.py

Gif de demostración del acceso del navegador:

Pasar datos de área de texto (Textarea) a través de un programa CGI

Textarea envía datos de varias líneas al servidor. El código HTML es el siguiente:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
</head>
<body>
<form action="/cgi-bin/textarea.py" method="post" target="_blank">
<textarea name="textcontent" cols="40" rows="4">
Escriba contenido aquí...
</textarea>
<input type="submit" value=Enviar />
</form>
</body>
</html>

El código del script textarea.py es el siguiente:

Ejemplo

#!/usr/bin/python3

# Importar el módulo de procesamiento CGI
import cgi, cgitb

# Crear una instancia de FieldStorage
form = cgi.FieldStorage()

# Recibir datos de campos
if form.getvalue('textcontent'):
   text_content = form.getvalue('textcontent')
else:
   text_content = Sin contenido

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Ejemplo de prueba CGI de Tutorial de Novato</title>)
print ("</head>")
print ("<body>")
print (<h2> El contenido ingresado es: %s</h2> % text_content)
print ("</body>")
print ("</html>")
>

Cambie los permisos de textarea.py:

chmod 755 textarea.py

Gif de demostración del acceso del navegador:

Pasar datos de lista desplegable a través de un programa CGI.

El código del cuadro desplegable HTML es el siguiente:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
</head>
<body>
<form action="/cgi-bin/dropdown.py" method="post" target="_blank">
<select name="dropdown">
<option value="ejemplo" selected>Tutorial de Novato</option>
<option value="google">Google</option>
</select>
<input type="submit" value=Enviar/>
</form>
</body>
</html>

El código del script dropdown.py se muestra a continuación:

Ejemplo

#!/usr/bin/python3

# Importar el módulo de procesamiento CGI
import cgi, cgitb

# Crear una instancia de FieldStorage
form = cgi.FieldStorage()

# Recibir datos de campos
if form.getvalue('dropdown'):
   dropdown_value = form.getvalue('dropdown')
else:
   dropdown_value = Sin contenido

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Ejemplo de prueba CGI de Tutorial de Novato</title>)
print ("</head>")
print ("<body>")
print (<h2> La opción seleccionada es: %s</h2> % dropdown_value)
print ("</body>")
print ("</html>")

Cambie los permisos de dropdown.py:

chmod 755 dropdown.py

Gif de demostración del acceso del navegador:


Uso de cookies en CGI

Una gran desventaja del protocolo HTTP es que no juzga la identidad del usuario, lo que causa grandes inconvenientes a los programadores. La aparición de la función de cookie ha compensado esta deficiencia.

Una cookie consiste en escribir datos de registro en el disco duro del cliente a través del navegador del cliente mientras este accede al script. Cuando el cliente accede al script la próxima vez, se recupera la información de datos, logrando así la función de identificación. Las cookies se usan comúnmente en la verificación de identidad.

 

Sintaxis de las cookies

El envío de cookies HTTP se implementa a través de la cabecera HTTP, que precede a la transferencia del archivo. La sintaxis de la cabecera Set-Cookie es la siguiente:

Set-cookie:name=name;expires=date;path=path;domain=domain;secure 
  • name=name:El valor de la cookie que se debe establecer (el nombre no puede usar ";y,signo), cuando hay varios valores de nombre, use ";para separarlos, por ejemplo:name1=name1;name2=name2;name3=name3。
  • expires=date:Período de validez de la cookie, formato: expires="Wdy,DD-Mon-YYYY HH:MM:SS"
  • path=path: La ruta admitida por la cookie. Si path es una ruta, la cookie es efectiva para todos los archivos y subdirectorios de este directorio, por ejemplo: path="/cgi-bin/". Si path es un archivo, la cookie solo es efectiva para este archivo, por ejemplo: path="/cgi-bin/cookie.cgi".
  • domain=domain:El dominio para el cual la cookie es efectiva, por ejemplo: domain="www.example.com"
  • secure:Si se da esta marca, indica que la cookie solo se puede transferir a través de un servidor https con protocolo SSL.
  • La recepción de cookies se implementa mediante la variable de entorno HTTP_COOKIE. El programa CGI puede obtener la información de la cookie consultando esta variable.

Configuración de cookies

La configuración de Cookie es muy simple. La cookie se envía por separado en la cabecera HTTP. El siguiente ejemplo establece name y expires en la cookie:

Ejemplo

#!/usr/bin/python3

print ('Set-Cookie: name="Tutorial de Novato";expires=Wed, 28 Aug 2016 18:30:00 GMT')
print ('Content-Type: text/html')

print ()
print ("""
<html>
  <head>
    <meta charset="utf-8">
<title>Tutorial de Novato (example.com)</title>
  </head>
    <body>
        <h1>Cookie set OK!</h1>
    </body>
</html>
"""
)

Guarde el código anterior en cookie_set.py y cambie los permisos de cookie_set.py:

chmod 755 cookie_set.py

El ejemplo anterior utiliza la cabecera Set-Cookie para establecer la información de Cookie. En las opciones se establecen otros atributos de Cookie, como el tiempo de expiración Expires, el dominio Domain y la ruta Path. Esta información se establece"Content-type:text/html"antes.


Recuperar información de cookies

La página de recuperación de información de Cookie es muy simple. La información de Cookie se almacena en la variable de entorno HTTP_COOKIE del CGI. El formato de almacenamiento es el siguiente:

key1=value1;key2=value2;key3=value3....

A continuación hay un programa CGI simple para recuperar información de cookie:

Ejemplo

#!/usr/bin/python3

# Importar módulo
import os
import http.cookies

print ("Content-type: text/html")
print ()

print ("""
<html>
<head>
<meta charset="utf-8">
<title>Tutorial para novatos (example.com)</title>
</head>
<body>
<h1>Leer información de cookie</h1>
"""
)

if 'HTTP_COOKIE' in os.environ:
    cookie_string=os.environ.get('HTTP_COOKIE')
    c= http.cookies.SimpleCookie()
   # c=Cookie.SimpleCookie()
    c.load(cookie_string)

    try:
        data=c['name'].value
        print ("cookie data: "+data+"<br>")
    except KeyError:
        print ("La cookie no está configurada o ha expirado<br>")
print ("""
</body>
</html>
"""
)

Guarda el código anterior en cookie_get.py y modifica los permisos de cookie_get.py:

chmod 755 cookie_get.py

La demostración en Gif de la configuración de cookie anterior es la siguiente:

Ejemplo de subida de archivos

El formulario HTML para configurar la carga de archivos debe configurarenctypeel atributo comomultipart/form-data, el código es el siguiente:

Ejemplo

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial para novatos (example.com)</title>
</head>
<body>
 <form enctype="multipart/form-data"
                    action="/cgi-bin/save_file.py" method="post">
   <p>Seleccionar archivo:<input type="file" name="filename" /></p>
   <p><input type="submit" value="Subir" /></p>
   </form>
</body>
</html>

El código del archivo de script save_file.py es el siguiente:

Ejemplo

#!/usr/bin/python3

import cgi, os
import cgitb; cgitb.enable()

form = cgi.FieldStorage()

# Obtener el nombre del archivo
fileitem = form['filename']

# Verificar si el archivo se ha subido
if fileitem.filename:
   # Establecer la ruta del archivo
   fn = os.path.basename(fileitem.filename)
   open('/tmp/' + fn, 'wb').write(fileitem.file.read())

   message = 'Archivo "' + fn + '" subido correctamente'
   
else:
   message = 'El archivo no se ha subido'
   
print ("""\
Content-Type: text/html\n
<html>
<head>
<meta charset="utf-8">
<title>Tutorial para novatos (example.com)</title>
</head>
<body>
   <p>%s</p>
</body>
</html>
"""
% (message,))

Guarda el código anterior en save_file.py y modifica los permisos de save_file.py:

chmod 755 save_file.py

La demostración en Gif de la configuración de cookie anterior es la siguiente:

Si tu sistema es Unix/Linux, debes reemplazar el separador de archivos; en Windows solo necesitas usar la sentencia open():

fn = os.path.basename(fileitem.filename.replace("\\", "/" ))

Cuadro de diálogo de descarga de archivos

Primero creamos el archivo foo.txt en el directorio actual para la descarga del programa.

La descarga de archivos se implementa configurando la información de la cabecera HTTP. El código de la función es el siguiente:

Ejemplo

#!/usr/bin/python3

# Cabecera HTTP
print ("Content-Disposition: attachment; filename=\"foo.txt\"")
print ()
# Abrir archivo
fo = open("foo.txt", "rb")

str = fo.read();
print (str)

# Cerrar archivo
fo.close()
Otras extensiones