Versión: Python 2.5+, 3.x
CGI (Common Gateway Interface, Interfaz de entrada común) es un estándar de programación web que consiste en ejecutar un programa en el servidor y desplegar su resultado hacia el cliente (o navegador web). Se origina a mediados de la década del 90, y rápidamente se estandariza y globaliza, ya que se trata de una de las primeras tecnologías que permitió desarrollar webs de contenido dinámico. Por lo general, dicho programa o programas ejecutados en el servidor web están desarrollados en algún lenguaje de script (PHP, Perl, Python), por cuestiones de portabilidad entre las distintas plataformas; por lo tanto, se los denomina scripts CGI o simplemente CGIs.
En el área de desarrollo web con Python se pueden mencionar dos métodos distintios: vía CGI y vía un framework web como Django, Pyramid, web2py, entre otros. Este artículo no pretende debatir la eficiencia de la tecnología CGI en la web actual ni en comparación a las otras técnicas -tema para el cual, posiblemente, habrá un futuro artículo- sino asentar las bases para comenzar con su desarrollo y que puedas utilizarlo en base a tus necesidades y proyectos.
El Servidor
Para poder dar comienzo a la programación web vía CGI primero es necesario crear un servidor web para desarrollo local. Es decir, será éste el encargado de ejecutar el script CGI que solicita el navegador web y, al finalizar, retornar su contenido. Por lo tanto, crea un nuevo archivo cgiserver.py
y añade lo siguiente:
#!/usr/bin/env python # -*- coding: utf-8 -*- from http.server import HTTPServer, CGIHTTPRequestHandler class Handler(CGIHTTPRequestHandler): cgi_directories = ["/"] httpd = HTTPServer(("", 8000), Handler) httpd.serve_forever()
En Python 2, reemplaza from http.server import HTTPServer, CGIHTTPRequestHandler
por:
from BaseHTTPServer import HTTPServer from CGIHTTPServer import CGIHTTPRequestHandler
Realmente no es necesario comprender el código anterior, aunque tampoco es muy difícil de entender. Simplemente se crea un nuevo servidor HTTP utilizando la librería estándar y se le añade soporte para ejecutar scripts CGI. Reitero, se trata únicamente de un servidor para desarrollo local, no para producción. Nótese la línea número 8, en la que se indican los directorios que contendrán archivos CGIs (en este caso con extensión .py
). Por último, la línea número 11 indica que el servidor escuchará peticiones vía el puerto 8000
.
Una vez guardado el código anterior, ejecútalo.
El primer Script
Ya está corriendo el servidor de desarrollo local, listo para interpretar archivos CGI y retornar su resultado.
A la hora de preparar un script para ser ejecutado vía CGI debemos hacerlo como si se tratara de cualquier otro programa. Aquellos que programen en PHP deberán usar archivos *.php
; programadores de Perl utilizarán archivos *.pl
; por lo tanto, nosotros utilizaremos archivos *.py
. De la misma manera, podrá tener el nombre que desees. Cada archivo CGI deberá realizar dos tareas fundamentales. La primera es indicar los headers o cabecera para que el navegador web sepa cómo interpretar el resultado. La segunda es imprimir el contenido con el formato que hemos especificado.
Vamos a comenzar creando un archivo script.py
, en el mismo directorio en donde tenemos corriendo nuestro servidor cgiserver.py
. Luego, inserta el siguiente código:
#!/usr/bin/env python # -*- coding: utf-8 -*- import cgi # Headers print("Content-Type: text/plain") print() # Contenido (texto plano, como lo hemos especificado) print("""Hola mundo!""")
Usuarios de Python 2.x deberán quitar los paréntesis al utilizar la sentencia print
.
Veamos las porciones del código:
import cgi
Importa el módulo cgi
. No es necesario importarlo si tu código simplemente imprime «Hola Mundo!»; de todas maneras, se le dará uso más adelante.
print("Content-Type: text/plain") print()
Imprime el header el cual indica que se trata de un contenido de texto plano (text/plain
). Se profundizará posteriormente. La segunda llamada a print()
se utiliza para añadir un salto de línea. La cabecera debe incluír siempre dos saltos de línea al finalizar. La función print()
añade automáticamente dicho carácter \n
al final de la cadena. Entendido esto, también podría haberse utilizado:
print("Content-Type: text/plain\n")
Dando ambos códigos como resultado: Content-Type: text/plain\n\n
(dos saltos de línea).
print("""Hola mundo!""")
Una vez especificada la cabecera, se imprime el contenido.
Nótese que en los scripts CGI la salida estándar es lo que visualizará el navegador. Por lo tanto, todo lo que se imprima en stdout
será enviado al navegador.
Ahora ingresa http://localhost:8000/script.py en tu navegador web y verás el mensaje de bienvenida. Si en lugar de dicho mensaje lo que ves es el mismo contenido del archivo, es porque no has posicionado el script en la carpeta que contiene el servidor cgiserver.py
.
Como se dijo anteriormente, el contenido del script será ejecutado por el navegador como éste lo disponga en la cabecera. Por ejemplo, si se imprime código HTML pero no se indica lo mismo en la cabecera, el navegador imprimirá simplemente el mismo código como si se tratase de texto plano.
#!/usr/bin/env python # -*- coding: utf-8 -*- import cgi # Headers print("Content-Type: text/plain") print() print("""<html> <head> <title>Título de la página</title> </head> <h3>Hola CGI!</h3> </html>""")
Cambiando el contenido de script.py
por este código e ingresando a http://localhost:8000/script.py en tu navegador verás que el código HTML no es interpretado.
Para cambiar este comportamiento, la cabecera deberá indicar que se trata de código HTML de la siguiente manera:
print("Content-Type: text/html")
Código completo:
#!/usr/bin/env python # -*- coding: utf-8 -*- import cgi # Headers print("Content-Type: text/html") print() print("""<html> <head> <title>Título de la página</title> </head> <h3>Hola CGI!</h3> </html>""")
Ahora sí verás:
Existe una gran cantidad de opciones para esta cabecera (Content-Type
). Por ejemplo, si quieres que el navegador interprete tu contenido como un archivo PDF deberás utilizar Content-Type: application/pdf
. Puedes ver una abundante lista en este enlace.
Contenido dinámico
Ya hemos visto cómo retornar contenido a través de un script CGI, tanto texto plano como código HTML. El problema del ejemplo anterior es que siempre retornará lo mismo: un código HTML que imprime «Hola CGI!» en pantalla con un determinado tamaño. El objetivo de utilizar CGI es poder desplegar páginas dinámicas, que varíe el contenido dependiendo de las circunstancias y ocasiones. Por ejemplo, si quisieramos crear una página que salude a cada visitante deberíamos crear un archivo HTML para cada visitante con su respectivo nombre. Sería una infinidad de archivos como: saludar_pedro.html
, saludar_juan.html
, saludar_jorge.html
. Teniendo estos tres archivos, si acaso Luis pasara por nuestra página no podríamos saludarlo, ya que no hay un archivo saludar_luis.html
. Al utilizar CGI, podemos crear un único archivo que salude a todo visitante, sin importar su nombre.
A partir de ahora, tendré en cuenta que ya has agregado previamente a todos tus códigos las siguientes líneas:
#!/usr/bin/env python # -*- coding: utf-8 -*- import cgi # Headers print("Content-Type: text/html") print()
Todo código de aquí en adelante irá debajo de éste.
Para saludar a cada visitante utilizaremos el siguiente código:
url_input = cgi.FieldStorage() print(""" <html> <head> <title>Título de la página</title> </head> <h3>Hola %s!</h3> </html>""" % url_input["nombre"].value)
En este código simplemente se obtiene la información enviada por el usuario a través del navegador web, utilizando la clase FieldStorage
. Esta clase retornará los datos pasados después del ?
en la URL junto con los de los formularios. Este script pretende ser llamado de la siguiente manera:
http://localhost:8000/script.py?nombre=Jorge
Así verás en pantalla «Hola Jorge!». La estructura del URL anterior es la siguiente:
http://dominio/script.py?variable=valor&otra_variable=valor&ultima_variable=valor
Como se observa, luego del nombre del script CGI se añade un ?
(signo de interrogación) y a la derecha de éste el nombre de una variable junto con su valor, intercalados por =
(signo igual) y separando cada grupo variable=valor
con un &
(et). Por ejemplo:
http://localhost:8080/script.py?nombre=Pedro&edad=25&idioma=Ingles
Estos datos serán accedidos desde el archivo script.py
de la siguiente manera:
url_input = cgi.FieldStorage() # Se trata como cualquier diccionario url_input["nombre"].value # 'Pedro' url_input["edad"].value # '25' url_input["idioma"].value # 'Ingles'
Nótese que el atributo FieldStorage.value
retorna siempre una cadena, como en el caso de la edad, que por más de que se trate de un valor numérico es responsabilidad del programador convertirlo a un entero.
Al tratarse de un diccionario, puedes verificar rápidamente la presencia de las variables:
if "nombre" not in url_input: print("<html>¡No me has dicho tu nombre!</html>")
Código completo:
url_input = cgi.FieldStorage() if "nombre" not in url_input: print("<html>¡No me has dicho tu nombre!</html>") else: print(""" <html> <head> <title>Título de la página</title> </head> <h3>Hola %s!</h3> </html>""" % (url_input["nombre"].value))
Por lo tanto, al ingresar a http://localhost:8000/script.py?nombre=Luis verás en pantalla «Hola Luis!». Por el contrario, en http://localhost:8000/script.py verás «¡No me has dicho tu nombre!».
Formularios
Una de las actividades más comunes es la de implementar formularios de contacto. Para esto, los campos rellenados por el usuario deben ser enviados hacia el servidor y manejados por nuestro script CGI. No solo este tipo de formularios, sino cualquier tipo de entrada por parte del visitante que necesite ser procesada.
La entrada vía formularios se recibe de la misma manera que vía URL. Por ejemplo, el siguiente código simula un formulario para iniciar sesión. Deberás crear dos archivos: index.py
, el cual imprimirá el código HTML del formulario y login.py
, que recibirá los datos ingresados por el usuario.
index.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import cgi # Headers print("Content-Type: text/html") print() print("""<html> <head><title>Formulario</title></head> <form method="post" action="login.py"> Nombre: <input name="name" type="text" /> <br /> Contraseña: <input name="password" type="password" /> <br /> <button>Ingresar</button> </form> </html>""")
Simplemente código HTML. Se crea un formulario y dentro de éste se inserta un campo para el nombre, otro para la contraseña y un botón para ingresar. Nótese el atributo action
de la etiqueta form
, el cual deberá llevar el nombre del archivo al cual le serán enviados los datos ingresados vía una petición POST
(especificado en el atributo method
).
login.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import cgi # Headers print("Content-Type: text/html") print() print("<html>") form_input = cgi.FieldStorage() if "name" not in form_input or "password" not in form_input: print("Debe rellenar todos los campos.") else: print("Has iniciado sesión como %s." % form_input["name"].value) print("</html>")
De la misma manera se obtienen los datos ingresados por el usuario. Se crea una instancia de la clase FieldStorage
, y luego se accede a sus elementos como un diccionario. El nombre de los ítems del diccionario que contienen los valores del nombre y contraseña depende del atributo name
especificado en las etiquetas input
en el código HTML anterior. Nótese que el primero lleva el nombre de «name» y el segundo «password». Estos valores son totalmente a elección. Si se creara una caja de texto de la siguiente manera:
<input name="age" type="text" />
Luego debería ser accedida como:
form_input["age"].value
Siendo form_input
una instancia de FieldStorage.
Problemas comúnes y soluciones
El texto original en inglés de este apartado se encuentra en este enlace, en la documentación del módulo CGI.
- La mayoría de los servidores HTTP no envían el contenido que imprime el script CGI al navegador web hasta que éste no finalice, por lo que no es posible desarrollar una barra de progreso mientras el script está corriendo.
- Chequea los registros del servidor HTTP (
tail -f logfile
en una ventana aparte puede ser útil). - Siempre chequea la sintaxis del script primero, ejecutándolo localmente (algo como
python script.py
). - Si el script no tiene errores de sintaxis, intenta añadiendo
import cgitb; cgitb.enable()
al comienzo del archivo. Recomiendo utilizar este módulo siempre, ya que crea registros de errores que serán mostrados en el navegador; te salvará en varias ocasiones. - Al invocar programar externos, asegúrate de que pueden ser encontrados. Generalmente, esto significa utilizar el nombre completo de las rutas; utilizar
PATH
no es muy conveniente en la programación vía CGI. - Al momento de leer o escribir archivos externos, asegúrate de que puedan ser utilizados por el
userid
que está ejecutando tu script CGI (que generalmente es eluserid
que ejecuta al servidor web). - No intenes dar un modo
set-uid
a tu script CGI. Esto no funciona en la mayoría de los sistemas, y también es una responsabilidad de seguridad.
Curso online 👨💻
¡Ya lanzamos el curso oficial de Recursos Python en Udemy!
Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter
en 2024.
Consultoría 💡
Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.
Luis says:
Buen dia amigo, por qué veo que la documentación para hacer controladores en Python esta enfocado es en Frameworks y no se aconseja el uso de CGI?
Recursos Python says:
Hola, Luis. Ocurre que CGI es una tecnología vetusta. Es más fácil, rápido y seguro utilizar algún framework (Django, Flask, etc.) vía WSGI. Sin embargo, CGI sigue siendo de utilidad en pequeñas operaciones o para mantener sistemas antiguos.
Saludos
Maria Antonieta says:
Hola ! Buenas tardes, gracias por tan excelentes contenidos. Una consulta, como detengo el servidor cgi? estos codigos de ejemplo me sirven para python3.5 en debian ?
Recursos Python says:
Hola, ¿cómo estás? Sí, sirven en cualquier plataforma soportada por Python. El servidor lo detenés con CTRL + C (en Windows) o CTRL + D (en Linux).
Saludos
Alfonso says:
hola mi problema es el siguiente logre que el servidor funcionara dándole los permisos que requería y llamándolo desde consola para luego ejecutar el script en localhost desde el navegador pero al cargar no me muestra nada de contenido, ni siquiera el hola mundo copiado directamente de tu guiá lo cual me parece extraño y quiero saber si se debe aque no lo estoy haciendo desde un entorno virtual o es otra razón
Recursos Python says:
Hola, ¿qué versión de Python estás usando?
Domingo Román says:
Duda: soy más que neófito (y además, mayorcito ya). Seguí las indicaciones al pie de la letra: tengo el cgi.server.py y es script.py en la misma carpeta pero cuando llego a la instrucción
ingresa http://localhost:8000/script.py en tu navegador web
Me aparece (Chrome):
No se puede acceder a este sitio
localhost rechazó la conexión.
Buscar localhost 8000 script en Google
ERR_CONNECTION_REFUSED
y en Safari algo muy parecido.
Tengo dudas con qué significa, respecto del archivo cgi.server
«Una vez guardado el código anterior, ejecútalo.»
Lo interpreté como ejecutarlo desde la consola. Cuando lo hice el cursor quedó como esperando algo. Imaginé que estaba bien.
Muchas gracias y saludos
Recursos Python says:
Hola. Exactamente, el servidor
cgiserver.py
se bloquea esperando a las peticiones HTTP. ¿El error lo obtienes estando el servidor abierto? Tal vez haya algún tipo de firewall bloqueando la conexión. Si el problema persiste te invito a que pases por el foro para verlo con mayor detalle.Saludos.
Abif says:
Estoy tratando de mandar datos de un servidor a otro (lo que mando son xml codificados). Mi codigo es el siguiente.
#!/usr/bin/python
# Nombre de Fichero: urllib2_1.py
import urllib2
import sys
import cjson
import os
#####################Lectura de un archivo xml##############################
f=open(‘/usr/local/etc/service/cfdi_files/60ee2273-927b-481d-a36f-ce7465505a7b.xml’)
xml=f.read()
f.close()
json = {‘data’:'»cfdi»:%s’ % (xml.encode(‘hex’))}
print json
data=cjson.encode(json)
https = urllib2.Request(url=’https://172.16.150.187/py/index.py?’, data=data)
f=urllib2.urlopen(https)
print f.read()
##################Segundo archivo xml####################################
f=open(‘/usr/local/etc/service/cfdi_files/8367832d-c465-4d5e-aec8-b532969f944c.xml’)
xml=f.read()
f.close()
json = {‘data’:'»cfdi»%s’ % (xml.encode(‘hex’))}
print json
data=cjson.encode(json)
https = urllib2.Request(url=’https://172.16.150.187/py/index.py?’, data=data)
f=urllib2.urlopen(https)
print f.read()
Pero cuando hago la peticiò me marco el Error 502
Me podrian explicar cual es mi error
De lado del receptor el index es el siguiente
#! /usr/bin/python
# -*- coding:utf-8 -*-
import cgi
import cjson
import sys
import os
import datetime
import time
import traceback
class InputProcessed(object):
def read(self, *args):
pass #raise EOFError(‘The wsgi.input stream has already been consumed’)
readline = readlines = __iter__ = read
def get_post_form(environ):
input = environ[‘wsgi.input’]
post_form = environ.get(‘wsgi.post_form’)
if (post_form is not None
and post_form[0] is input):
return post_form[2]
# This must be done to avoid a bug in cgi.FieldStorage
environ.setdefault(‘QUERY_STRING’, »)
fs = cgi.FieldStorage(fp=input,
environ=environ,
keep_blank_values=1)
new_input = InputProcessed()
post_form = (new_input, input, fs)
environ[‘wsgi.post_form’] = post_form
environ[‘wsgi.input’] = new_input
return fs
def application(environ, start_response):
if environ[‘REQUEST_METHOD’]==’GET’:
ctx = cgi.FieldStorage(fp=environ[‘wsgi.input’], environ=environ)
elif environ[‘REQUEST_METHOD’]==’POST’:
ctx=get_post_form(environ)
print ctx
# datos=cjson.decode(ctx[‘data’].value)
print ctx[‘data’].value
datos = ctx[‘data’].value
print ‘-‘*50
# print datos
# data1=json.decode(‘hex’)
# print data1
res={«success»:True,»msg_response»:’ok’}
start_response(‘200 OK’, [(‘Content-Type’,’text/html; charset=utf-8′)])
return [cjson.encode(res)]
~
Recursos Python says:
Por lo que veo en tu servidor CGI estás imprimiendo varias cosas antes de la cabecera HTTP (que estimo que se imprime con la función
start_response
). Esto probablemente cause el error ya que la cabecera debe ser lo primero en imprimirse.De todas formas te invito a pasar por el foro si continuas con problemas para mayor comodidad.
Un saludo.
peter says:
no me lee imagenes para poner en portada creando un codigo html en ese codigo http que tengo para que me lea las imagenes
Recursos Python says:
No puedo ayudarte sin ver una porción de tu código, pero probablemente tengas problemas con la ruta a tus imágenes. Las rutas son relativas a la ubicación del archivo
cgiserver.py
. Si tu archivo está en, por ejemplo,/home/web/cgiserver.py
y tu imagen en/home/web/imagenes/imagen1.png
entonces el código HTML es<img src="imagenes/imagen1.png" />
.Saludos.
Leonor says:
Hola! estoy comenzando en el mundo de python, mi pregunta es, si para correr en mi navegador web los archivos *.py tengo que intalar antes apache?.
La otra pregunta es los archivos que menciona se deben guardar en la ubicacion de apache, porque esa parte no entiendo
Recursos Python says:
¡Hola! No es necesario instalar Apache; Python incluye en su librería estándar un servidor HTTP básico para utilizar durante el desarrollo. Es decir, el archivo
cgiserver.py
cumple el rol de Apache (o cualquier otro servidor web). Tanto este archivo como los demás podés guardarlos en cualquier ubicación.Un saludo.
dante says:
hola una pregunta ¿ y si yo quiero verlo en otra pc de otro lugar? ¿ como debe ser?
gracias por el articulo.
Recursos Python says:
Hola Dante. En primer lugar, habría que modificar el servidor CGI indicando una IP de acceso público o bien
0.0.0.0
para escuchar en todas las direcciones de IP disponibles.httpd = HTTPServer(("0.0.0.0", 8000), Handler)
Ahora, la dirección de acceso debería ser «http://direccion-ip:8000/», siendo «direccion-ip» tu IP pública (puedes averiguarla en este enlace), siempre y cuando tu módem o router habilite el puerto 8000.
Un saludo.
dante says:
muchas gracias por responder tan rápido jeje lo pondré en practica
saludos
josee says:
Excelente manual, pero a mi al ejecutar los archivos en el navegador me los muestra como texto plano, osea me muestra el código, no lo ejecuta. ¿eso que puede ser?
Recursos Python says:
Hola, me alegro que te haya servido el artículo. Respecto a tu problema, estimo que estás experimentando lo que comentas al ingresar a http://localhost:8000/script.py. Recuerda que debes colocar el archivo
script.py
junto con tu servidor,server.py
, en el mismo directorio, ya que le hemos indicado que interprete los archivos CGI en el directorio actual (cgi_directories = ["/"]
). Si quieres especificar otra ubicación, puedes añadirla a la lista anterior (por ejemplo,cgi_directories = ["/", "/scripts/"]
).Espero que resulte para solucionar tu inconveniente. Un saludo.