Programación CGI en Python


Qué es CGI

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

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

Nota:

A partir de Python 3.11, el módulo cgi está oficialmente marcado como "obsoleto" (Deprecated) y se elimina a partir de Python 3.13.

Se recomienda migrar aFlask、DjangooFastAPIy otros marcos web modernos.


Navegación web

Para comprender mejor cómo funciona CGI, podemos partir del proceso de 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 recibe la información de la solicitud, analiza la URL y verifica si el archivo solicitado existe en el servidor. Si existe, devuelve el contenido del archivo; de lo contrario, devuelve un mensaje de error.
  • 3. El navegador recibe la información del servidor y muestra el archivo recibido o el mensaje de error.

Los programas CGI pueden ser scripts de Python, scripts de PERL, scripts de SHELL, programas 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 sea compatible con CGI y de que ya esté configurado el manejador de CGI.

Configuración de soporte CGI en Apache:

Establece el directorio CGI:

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

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

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

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>

Agrega la extensión .py en AddHandler para poder acceder a los archivos de scripts de Python que terminan en .py:

AddHandler cgi-script .cgi .pl .py

Primer programa CGI

Creamos el primer programa CGI con Python, con el nombre de archivo hello.py, ubicado en el directorio /var/www/cgi-bin, con el siguiente contenido:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

print "Content-type:text/html"
print                               # 空行,告诉服务器结束头部
print '<html>'
print '<head>'
print '<meta charset="utf-8">'
print '<title>Hello World - 我的第一个 CGI 程序!</title>'
print '</head>'
print '<body>'
print '<h2>Hello World! 我是来自Ejemplo的第一CGI程序</h2>'
print '</body>'
print '</html>'

Después de guardar el archivo, modifica hello.py y cambia sus permisos a 755:

chmod 755 hello.py 

El programa anterior se accede en el navegadorhttp://localhost/cgi-bin/hello.pyEl resultado mostrado es el siguiente:

Hello World! 我是来自Ejemplo的第一CGI程序

El script hello.py es un script de Python simple. 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 mostrado es "text/html".

Usa print para generar una línea en blanco que indique al servidor el final de la información de cabecera.


Cabeceras HTTP

" 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 de la solicitud correspondiente a la entidad. 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 ubicación distinta de la URL solicitada para completar la solicitud o identificar un nuevo recurso
Last-modified: DateHora de última modificación del recurso solicitado
Content-length: NLongitud del contenido de la solicitud
Set-Cookie: String Configurar cookies 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 la 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 significa que los datos provienen de un formulario HTML.
CONTENT_LENGTHSi la transferencia de información entre el servidor y el programa CGI es mediante POST, esta variable de entorno es el número de bytes de datos válidos que se pueden leer desde la entrada estándar STDIN. Esta variable de entorno debe usarse al leer los datos de entrada.
HTTP_COOKIEContenido de las cookies en el cliente.
HTTP_USER_AGENTProporciona información del navegador del cliente que incluye el número de versión u otros datos propietarios.
PATH_INFOEl valor de esta variable de entorno representa otra información de ruta que sigue inmediatamente al nombre del programa CGI. A menudo aparece como parámetro del programa CGI.
QUERY_STRINGSi la transferencia de información entre el servidor y el programa CGI es mediante 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 de arriba. 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 programas 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 invoca 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 el programa CGI. Por ejemplo, el valor anterior es Apache/2.2.14(Unix).

El siguiente es un script CGI simple que muestra las variables de entorno de CGI:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename:test.py

import os

print "Content-type: text/html"
print
print "<meta charset=\"utf-8\">"
print "<b>环境变量</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 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.

Transmisión 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 un signo "?", como se muestra a continuación:

http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2
Algunos comentarios adicionales sobre las solicitudes GET:
  • Las solicitudes GET se pueden almacenar en caché
  • Las solicitudes GET permanecen 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 usa el método GET para enviar dos parámetros al programa hello_get.py:

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

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# filename:test.py

# CGI处理模块
import cgi, cgitb 

# 创建 FieldStorage 的实例化
form = cgi.FieldStorage() 

# 获取数据
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 CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2>%s官网:%s</h2>" % (site_name, site_url)
print "</body>"
print "</html>"

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

chmod 755 hello_get.py 

El resultado de la solicitud en el navegador:

Ejemplo simple de formulario: método GET

Lo siguiente es un formulario HTML que usa el método GET para enviar dos datos al servidor. El script del servidor al que se envía es también el archivo hello_get.py. El código de hello_get.html es el siguiente:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ejemplo(example.com)</title>
</head>
<body>
<form action="../cgi-bin/hello_get.py" method="get">
站点名称: <input type="text" name="name">  <br />

站点 URL: <input type="text" name="url" />
<input type="submit" value="提交" />
</form>
</body>
</html>

Por defecto, el directorio cgi-bin solo puede contener archivos de script. Guardamos hello_get.html en el directorio test y modificamos los permisos del archivo a 755:

chmod 755 hello_get.html

La demostración en Gif es la siguiente:

Envío de datos usando el método POST

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

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# CGI处理模块
import cgi, cgitb 

# 创建 FieldStorage 的实例化
form = cgi.FieldStorage() 

# 获取数据
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 CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2>%s官网:%s</h2>" % (site_name, site_url)
print "</body>"
print "</html>"

Lo siguiente es el formulario que, mediante el método POST (method="post"), envía datos al script del servidor hello_get.py:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ejemplo(example.com)</title>
</head>
<body>
<form action="../cgi-bin/hello_get.py" method="post">
站点名称: <input type="text" name="name">  <br />

站点 URL: <input type="text" name="url" />
<input type="submit" value="提交" />
</form>
</body>
</html>

La demostración en Gif es la siguiente:

Transmisión de datos de casillas de verificación (checkbox) mediante un programa CGI

checkbox se utiliza para enviar datos de una o varias opciones. El código HTML es el siguiente:

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

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 引入 CGI 处理模块 
import cgi, cgitb 

# 创建 FieldStorage的实例 
form = cgi.FieldStorage() 

# 接收字段数据
if form.getvalue('google'):
   google_flag = "是"
else:
   google_flag = "否"

if form.getvalue('ejemplo'):
   ejemplo_flag = "是"
else:
   ejemplo_flag = "否"

print "Content-type:text/html"
print
print "<html>"
print "<head>"
print "<meta charset=\"utf-8\">"
print "<title>Ejemplo CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2> Ejemplo是否选择了 : %s</h2>" % ejemplo_flag
print "<h2> Google 是否选择了 : %s</h2>" % google_flag
print "</body>"
print "</html>"

Modificar los permisos de checkbox.py:

chmod 755 checkbox.py

Gif de demostración del acceso mediante el navegador:

Transmisión de datos de botones de opción (radio) mediante un programa CGI

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

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

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 引入 CGI 处理模块 
import cgi, cgitb 

# 创建 FieldStorage的实例 
form = cgi.FieldStorage() 

# 接收字段数据
if form.getvalue('site'):
   site = form.getvalue('site')
else:
   site = "提交数据为空"

print "Content-type:text/html"
print
print "<html>"
print "<head>"
print "<meta charset=\"utf-8\">"
print "<title>Ejemplo CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2> 选中的网站是 %s</h2>" % site
print "</body>"
print "</html>"

Modificar los permisos de radiobutton.py:

chmod 755 radiobutton.py

Gif de demostración del acceso mediante el navegador:

Transmisión de datos de área de texto (textarea) mediante un programa CGI

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

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ejemplo(example.com)</title>
</head>
<body>
<form action="../cgi-bin/textarea.py" method="post" target="_blank">
<textarea name="textcontent" cols="40" rows="4">
在这里输入内容...
</textarea>
<input type="submit" value="提交" />
</form>
</body>
</html>

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 引入 CGI 处理模块 
import cgi, cgitb 

# 创建 FieldStorage的实例 
form = cgi.FieldStorage() 

# 接收字段数据
if form.getvalue('textcontent'):
   text_content = form.getvalue('textcontent')
else:
   text_content = "没有内容"

print "Content-type:text/html"
print
print "<html>"
print "<head>";
print "<meta charset=\"utf-8\">"
print "<title>Ejemplo CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2> 输入的内容是:%s</h2>" % text_content
print "</body>"
print "</html>"

Modificar los permisos de textarea.py:

chmod 755 textarea.py

Gif de demostración del acceso mediante el navegador:

Transmisión de datos de lista desplegable mediante un programa CGI.

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

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

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 引入 CGI 处理模块 
import cgi, cgitb 

# 创建 FieldStorage的实例 
form = cgi.FieldStorage() 

# 接收字段数据
if form.getvalue('dropdown'):
   dropdown_value = form.getvalue('dropdown')
else:
   dropdown_value = "没有内容"

print "Content-type:text/html"
print
print "<html>"
print "<head>"
print "<meta charset=\"utf-8\">"
print "<title>Ejemplo CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2> 选中的选项是:%s</h2>" % dropdown_value
print "</body>"
print "</html>"

Modificar los permisos de dropdown.py:

chmod 755 dropdown.py

Gif de demostración del acceso mediante el navegador:


Uso de cookies en CGI

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

La cookie consiste en que, mientras el cliente accede al script, se escriben datos de registro en el disco duro del cliente a través del navegador de este. Cuando el cliente vuelva a acceder al script, se recuperan los datos, logrando así la función de identificación. La cookie se usa comúnmente en la verificación de identidad.

 

Sintaxis de cookies

El envío de la cookie http se realiza 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:Se debe configurar el valor de la cookie (el nombre no puede usar ";y ",carácter), cuando haya múltiples valores de name, use ";" para separar, 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: Establecer la ruta compatible con la cookie. Si path es una ruta, la cookie es válida para todos los archivos y subdirectorios de ese directorio, por ejemplo: path="/cgi-bin/". Si path es un archivo, la cookie solo es válida para ese archivo, por ejemplo: path="/cgi-bin/cookie.cgi".
  • domain=domain:Dominio donde la cookie es válida, por ejemplo: domain="www.example.com"
  • secure:Si se proporciona este indicador, significa que la cookie solo puede transmitirse a través de un servidor https con protocolo SSL.
  • La recepción de la cookie se logra 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:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 
print 'Content-Type: text/html'
print 'Set-Cookie: name="Ejemplo";expires=Wed, 28 Aug 2016 18:30:00 GMT'
print
print """
<html>
    <head>
        <meta charset="utf-8">
        <title>Ejemplo(example.com)</title>
    </head>
    <body>
        <h1>Cookie set OK!</h1>
    </body>
</html>
"""

Guarde el código anterior como cookie_set.py y modifique los permisos de cookie_set.py:

chmod 755 cookie_set.py

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


Recuperación de 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, con el siguiente formato de almacenamiento:

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

El siguiente es un programa CGI simple para recuperar información de cookie:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 导入模块
import os
import Cookie

print "Content-type: text/html"
print

print """
<html>
<head>
<meta charset="utf-8">
<title>Ejemplo(example.com)</title>
</head>
<body>
<h1>读取cookie信息</h1>
"""

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

    try:
        data=c['name'].value
        print "cookie data: "+data+"<br>"
    except KeyError:
        print "cookie 没有设置或者已过期<br>"
print """
</body>
</html>

"""

Guarde el código anterior como cookie_get.py y modifique los permisos de cookie_get.py:

chmod 755 cookie_get.py

El Gif de configuración de color de cookie anterior se muestra a continuación:

Ejemplo de carga de archivos

El formulario HTML para subir archivos necesita configurarenctypeel atributomultipart/form-data, el código es el siguiente:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ejemplo(example.com)</title>
</head>
<body>
 <form enctype="multipart/form-data" 
                     action="../cgi-bin/save_file.py" method="post">
   <p>选中文件: <input type="file" name="filename" /></p>
   <p><input type="submit" value="上传" /></p>
   </form>
</body>
</html>

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

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

form = cgi.FieldStorage()

# 获取文件名
fileitem = form['filename']

# 检测文件是否上传
if fileitem.filename:
   # 设置文件路径 
   fn = os.path.basename(fileitem.filename)
   open('/tmp/' + fn, 'wb').write(fileitem.file.read())

   message = '文件 "' + fn + '" 上传成功'
   
else:
   message = '文件没有上传'
   
print """\
Content-Type: text/html\n
<html>
<head>
<meta charset="utf-8">
<title>Ejemplo(example.com)</title>
</head>
<body>
   <p>%s</p>
</body>
</html>
""" % (message,)

Guarde el código anterior como save_file.py y modifique los permisos de save_file.py:

chmod 755 save_file.py

El Gif de configuración de color de cookie anterior se muestra a continuación:

Si su sistema es Unix/Linux, debe reemplazar el separador de archivos. En Windows, solo necesita usar la instrucción open():

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

Diálogo de descarga de archivos

Primero creamos el archivo foo.txt en el directorio actual, para que el programa lo descargue.

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

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# HTTP 头部
print "Content-Disposition: attachment; filename=\"foo.txt\"";
print
# 打开文件
fo = open("foo.txt", "rb")

str = fo.read();
print str

# 关闭文件
fo.close()
Otras extensiones