Skip to content

12. Servicios web en Python

  

Los scripts de Python pueden ejecutarse en un servidor WEB. Este último será el encargado de escuchar las solicitudes de los clientes. Desde el punto de vista del cliente, llamar a un servicio WEB equivale a solicitar el URL de dicho servicio. El cliente puede estar escrito en cualquier lenguaje, en particular en Python. Debemos saber «comunicarnos» con un servicio WEB, es decir, comprender el protocolo Http de comunicación entre un servidor web y sus clients. Ese es el objetivo de los programas que siguen.

Los scripts del servicio web serán ejecutados por el servidor web Apache de WampServer. Deben colocarse en un directorio específico: <WampServer>\bin\apache\apachex.y.z\cgi-bin, donde <WampServer> es la carpeta de instalación de WampServer y x.y.z es el directorio de version del servidor web Apache.

 

No basta con colocar los scripts de Python en la carpeta <cgi-bin>. El script debe indicar en la primera línea la ruta del intérprete de Python que se va a utilizar. Esta ruta se incluye en forma de comentario:

#!D:\Programas\ActivePython\Python2.7.2\python.exe

El lector deberá adaptar esta ruta a su propio entorno.

12.1. Aplicación cliente/servidor de fecha y hora

Nuestro primer servicio web será un servicio de fecha y hora: el cliente recibe la fecha y la hora actuales.

12.1.1. El servidor


El programa (web_02)


#!D:\Programas\ActivePython\Python2.7.2\python.exe

import time

# encabezados
print "Content-Type: text/plain\n"

# envío de la hora al cliente
  # hora local: número de milisegundos desde el 01/01/1970
  # «formato de visualización de fecha y hora
  # d: día en 2 dígitos
  # m: mes en dos dígitos
  # y: año en dos dígitos
  # H: hora 0,23
  # M: minutos
  # S: segundos

print time.strftime('%d/%m/%y %H:%M:%S',time.localtime())

Notas:

  • línea 6: el script debe generar por sí mismo algunos de los encabezados Http de la respuesta al cliente. Estos se sumarán a los encabezados Http generados por el propio servidor Apache. El encabezado Http de la línea 6 indica al cliente que se le va a enviar un recurso en formato text/plain, es decir, texto sin formato. Cabe destacar el «\n» al final del encabezado, que generará una línea en blanco tras el encabezado. Es obligatorio: es esta línea en blanco la que indica al cliente Http el final de los encabezados Http de la respuesta. A continuación viene el recurso solicitado por el cliente, en este caso un texto sin formato;
  • línea 18: el recurso enviado al cliente es un texto que muestra la fecha y la hora actuales.

12.1.2. Dos pruebas

El script anterior se puede ejecutar directamente con el intérprete de Python en una ventana de comandos, tal y como hemos hecho hasta ahora. Esto permite eliminar posibles errores de sintaxis o de funcionamiento. Se obtiene el siguiente resultado:

1
2
3
4
cmd>%python% web_02.py
Content-Type: text/plain

24/06/11 11:16:55

Una vez probado el script de esta manera, podemos colocarlo en la carpeta <cgi-bin> del servidor Apache (véase el apartado 12). Iniciemos la aplicación WampServer. Esto inicia tanto un servidor web Apache como un SGBD MySQL. Por el momento, solo utilizaremos el servidor web. A continuación, con un navegador, solicitemos la siguiente URL: URL://localhost/cgi-bin/web_02.py :

  • en [1]: la URL solicitada;
  • en [2]: la respuesta mostrada por el navegador;
  • en [3]: el código fuente recibido por el navegador web. Es efectivamente el enviado por el script de Python.

Con ciertas herramientas (en este caso Firebug, un complemento del navegador Firefox) se puede acceder a los encabezados Http intercambiados con el servidor. Arriba, el navegador web ha recibido los siguientes encabezados Http:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Fri, 24 Jun 2011 09:35:02 GMT
Server: Apache/2.2.6 (Win32) PHP/5.2.5
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/plain

En las líneas 7-8 se reconoce el encabezado Http enviado por el script de Python. Los anteriores han sido generados por el servidor web Apache.

12.1.3. Un cliente programado


El programa (client_web_02)

Ahora escribimos un script que será cliente del servicio web anterior. Utilizamos las funcionalidades del módulo httplib, que facilita la escritura de clients Http.


# -*- coding=utf-8 -*-

import httplib,re

# constantes
HOST="localhost"
URL="/cgi-bin/web_02.py"
# iniciar sesión
connexion=httplib.HTTPConnection(HOST)
# seguimiento
connexion.set_debuglevel(1)
# envío de la solicitud
connexion.request("GET", URL)
# procesamiento de la respuesta
reponse=connexion.getresponse()
# contenido
contenu=reponse.read()
# cierre de la conexión
connexion.close()
print "------\n",contenu,"-----\n"
# recuperación de los elementos de la hora
elements=re.match(r"^(\d\d)/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)\s*$",contenu).groups()
print "Jour=%s,Mois=%s,An=%s,Heures=%s,Minutes=%s,Secondes=%s" % (elements[0],elements[1],elements[2],elements[3],elements[4],elements[5])

Notas:

  • línea 3: el módulo re es necesario para las expresiones regulares, el módulo httplib para las funciones de clients Http;
  • línea 9: se crea una conexión Http con el puerto 80 de HOST definido en la línea 6;
  • línea 11: el seguimiento permite ver los encabezados de la solicitud del cliente y de la respuesta del servidor;
  • línea 13: se solicita el URL del servicio web. Hay dos formas de solicitarlo: mediante un comando Http, GET o POST. La diferencia entre ambos se explica más adelante. Aquí se solicitará con el comando Http GET;
  • línea 15: se lee la respuesta del servidor. Aquí se obtiene la respuesta completa: encabezados Http y recurso solicitado por el cliente. En su respuesta, el servidor pudo haber solicitado al cliente que se redirigiera. En este caso, el cliente httplib realiza automáticamente la redirección. Por lo tanto, la respuesta obtenida es la resultante de la redirección;
  • línea 17: la respuesta se compone de los encabezados Http y del documento solicitado por el cliente. Para obtener solo los encabezados Http, se utilizará [reponse].getHeaders(). Para obtener el documento, se utiliza [reponse].read();
  • línea 19: una vez obtenida la respuesta del servidor web, se cierra la conexión con este;
  • sabemos que el documento enviado por el servidor es una línea de texto con el formato 15/06/11 14:56:36. Líneas 22-26: se utiliza una expresión regular para extraer los diferentes elementos de esta línea.

12.1.4. Resultados

send: 'GET /cgi-bin/web_02.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Tue, 14 Feb 2012 14:51:07 GMT
header: Server: Apache/2.2.17 (Win32) PHP/5.3.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
------
14/02/12 15:51:07
-----

Jour=14,Mois=02,An=12,Heures=15,Minutes=51,Secondes=07

Notas:

  • línea 1: los encabezados Http enviados por el cliente al servidor web;
  • líneas 2-6: los encabezados Http de la respuesta del servidor web;
  • línea 8: el documento enviado por el servidor;
  • línea 11: el resultado de su procesamiento;

12.2. Recuperación por parte del servidor de los parámetros enviados por el cliente

En el protocolo Http, un cliente dispone de dos métodos para pasar parámetros al servidor web:

  1. solicita el URL del servicio en forma de

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

donde los valores válidos deben codificarse previamente para que ciertos caracteres reservados sean sustituidos por su valor hexadecimal.

  1. solicita el URL del servicio en la forma
POST url HTTP/1.0

y, a continuación, entre los encabezados Http enviados al servidor, coloca el siguiente encabezado:

Content-length: N

El resto de los encabezados enviados por el cliente terminan con una línea en blanco. A continuación, puede enviar sus datos en el formato

val1&param2=val2&param3=val3…

donde los vali deben, al igual que en el método GET, codificarse previamente. El número de caracteres enviados al servidor debe ser N, donde N es el valor declarado en el encabezado:

Content-length: N

12.2.1. El servicio web

El siguiente servicio web recibe 3 parámetros de su cliente: nombre, apellidos, edad. Los recupera de una especie de diccionario denominado cgi.FieldStorage proporcionado por el módulo cgi. El valor vali de un parámetro parami se obtiene mediante vali=cgi.FieldStorage().getlist("parami"). Se obtiene una matriz de:

  • 0 elementos si el parámetro parami no está presente en la solicitud del cliente;
  • 1 elemento si el parámetro parami está presente una vez en la solicitud del cliente;
  • n elementos si el parámetro parami está presente n veces en la solicitud del cliente.

Una vez recuperados los parámetros, el script los devuelve al cliente.


El programa (web_03)


#!D:\Programas\ActivePython\Python2.7.2\python.exe

import cgi

# encabezados
print "Content-Type: text/plain\n"

# recuperación por parte del servidor de la información enviada por el cliente
# aquí nombre=P&apellidos=N&edad=A
formulaire=cgi.FieldStorage()

# se devuelven al cliente
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

Se puede realizar una prueba con un navegador web:

En [1], el URL del servicio web. Cabe destacar la presencia de los tres parámetros: nombre, apellidos y edad. En [2], la respuesta del servicio web.

12.2.2. El cliente GET


El programa (client_web_03_GET)


# -*- coding=utf-8 -*-

import httplib,urllib

# constantes
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# los parámetros deben codificarse antes de enviarse al servidor
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# los parámetros se colocan al final del URL
URL+="?"+params
# conexión
connexion=httplib.HTTPConnection(HOST)
# seguimiento
connexion.set_debuglevel(1)
# envío de la solicitud
connexion.request("GET",URL)
# procesamiento de la respuesta
reponse=connexion.getresponse()
# contenido
contenu=reponse.read()
print contenu,"\n"
# cierre de la conexión
connexion.close()

Notas:

  • líneas 8-10: los valores de los 3 parámetros enviados al servicio web;
  • línea 13: hay que codificarlos. Esto se hace mediante el método urlencode del módulo urllib. Este módulo se importa en la línea 3. El método admite como parámetro un diccionario {param1:val1, param2:val2, ...};
  • línea 15: en un comando GET (línea 21), el cliente debe colocar los parámetros codificados al final del URL del servicio web;
  • las siguientes líneas ya se han visto.

12.2.3. Los resultados

1
2
3
4
5
6
7
8
dos>%python% client_03_GET.py
send: 'GET /cgi-bin/web_03.py?nom=de+la+Huche&age=42&prenom=Jean-Paul HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Wed, 15 Jun 2011 13:22:15 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du client [['Jean-Paul'],['de la Huche'],['42']]

Notas:

  • línea 2: fíjese en la codificación de los parámetros (nombre, apellidos, edad);
  • línea 8: la respuesta del servicio web.

12.2.4. El cliente POST

El cliente POST es análogo al cliente GET, salvo que los parámetros codificados ya no forman parte del URL de destino. Se pasan como tercer argumento de la solicitud POST (línea 19).


El programa (client_web_03_POST)


# -*- coding=utf-8 -*-

import httplib,urllib

# constantes
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# los parámetros deben codificarse antes de enviarse al servidor
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# conexión
connexion=httplib.HTTPConnection(HOST)
# seguimiento
connexion.set_debuglevel(1)
# envío de la solicitud
connexion.request("POST",URL,params)
# procesamiento de la respuesta
reponse=connexion.getresponse()
# contenido
contenu=reponse.read()
print contenu,"\n"
# cierre de la conexión
connexion.close()

12.2.5. Los resultados

1
2
3
4
5
6
7
8
dos>%python% client_03_POST.py
send: 'POST /cgi-bin/web_03.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding:identity\r\nContent-Length: 39\r\n\r\nnom=de+la+Huche&age=42&prenom=Jean-Paul'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Fri, 24 Jun 2011 12:03:31 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du service web [prenom=['Jean-Paul'],nom=['de la Huche'],age=['42']]

Notas:

  • cabe destacar la línea 2, el método utilizado por el cliente POST para enviar los parámetros codificados:
    • el encabezado Http Content-Length indica el número de caracteres que se enviarán al servicio web;
    • a continuación, este encabezado Http va seguido de una línea en blanco que indica el final de los encabezados Http;
    • a continuación, se envían los 39 caracteres de los parámetros codificados.
  • línea 8: la respuesta del servicio web.

12.3. Recuperación de las variables de entorno de un servicio web

12.3.1. El servicio web

El script CGI de Python se ejecuta en un entorno del sistema que posee atributos. Sus atributos y sus valores están disponibles en un diccionario os.environ.


El programa (web_04)


#!D:\Programas\ActivePython\Python2.7.2\python.exe

import os

# encabezados
print "Content-Type: text/plain\n"
# información del entorno
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Notas:

  • línea 3: hay que importar el módulo os para disponer de las variables «del sistema».

Si se ejecuta directamente el script anterior (c.a.d, como script de consola y no como cgi), se obtienen en la consola los siguientes resultados:

Content-Type: text/plain

TMP : C:\Users\SERGET~1\AppData\Local\Temp
COMPUTERNAME : GPORTPERS3
USERDOMAIN : Gportpers3
VS100COMNTOOLS : D:\Programs\dotnet\Visual Studio 10\Common7\Tools\
VISUALSTUDIODIR : D:\Documents\Visual Studio 2010
PSMODULEPATH : C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
COMMONPROGRAMFILES : C:\Program Files (x86)\Common Files
PROCESSOR_IDENTIFIER : Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
PROGRAMFILES : C:\Program Files (x86)
PROCESSOR_REVISION : 2a07
SYSTEMROOT : C:\Windows
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
PROGRAMFILES(X86) : C:\Program Files (x86)
WINDOWS_TRACING_FLAGS : 3
TEMP : C:\Users\SERGET~1\AppData\Local\Temp
COMMONPROGRAMFILES(X86) : C:\Program Files (x86)\Common Files
PROCESSOR_ARCHITECTURE : x86
ALLUSERSPROFILE : C:\ProgramData
LOCALAPPDATA : C:\Users\Serge TahÚ\AppData\Local
HOMEPATH : \Users\Serge TahÚ
PROGRAMW6432 : C:\Program Files
USERNAME : Serge TahÚ
LOGONSERVER : \\GPORTPERS3
PROMPT : $P$G
SESSIONNAME : Console
PROGRAMDATA : C:\ProgramData
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
FP_NO_HOST_CHECK : NO
WINDIR : C:\Windows
PYTHON : D:\Programs\python\python2.7.2\python
WINDOWS_TRACING_LOGFILE : C:\BVTBin\Tests\installpackage\csilogfile.log
HOMEDRIVE : C:
SYSTEMDRIVE : C:
COMSPEC : C:\Windows\system32\cmd.exe
NUMBER_OF_PROCESSORS : 8
VBOX_INSTALL_PATH : D:\Programs\systeme\Oracle\VirtualBox\
APPDATA : C:\Users\Serge TahÚ\AppData\Roaming
PROCESSOR_LEVEL : 6
PROCESSOR_ARCHITEW6432 : AMD64
COMMONPROGRAMW6432 : C:\Program Files\Common Files
OS : Windows_NT
PUBLIC : C:\Users\Public
USERPROFILE : C:\Users\Serge TahÚ

En un navegador web (en este caso se ejecuta el script CGI), se obtienen los siguientes resultados:

 

Cabe señalar que, según el contexto de ejecución, el entorno obtenido no es el mismo.

12.3.2. El cliente programado


El programa (client_web_04)


# -*- coding=utf-8 -*-

import httplib

# constantes
HOST="localhost"
URL="/cgi-bin/web_04.py"
# conexión
connexion=httplib.HTTPConnection(HOST)
# envío de la solicitud
connexion.request("GET", URL)
# procesamiento de la respuesta
reponse=connexion.getresponse()
# contenido
print reponse.read()

12.3.3. Resultados

SERVER_SOFTWARE : Apache/2.2.17 (Win32) PHP/5.3.5
SCRIPT_NAME : /cgi-bin/web_04.py
SERVER_SIGNATURE :
REQUEST_METHOD : GET
SERVER_PROTOCOL : HTTP/1.1
QUERY_STRING :
SYSTEMROOT : C:\Windows
SERVER_NAME : localhost
REMOTE_ADDR : 127.0.0.1
SERVER_PORT : 80
SERVER_ADDR : 127.0.0.1
DOCUMENT_ROOT : D:/Programs/sgbd/wamp/www/
COMSPEC : C:\Windows\system32\cmd.exe
SCRIPT_FILENAME : D:/Programs/sgbd/wamp/bin/apache/Apache2.2.17/cgi-bin/web_04.py
SERVER_ADMIN : admin@localhost
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.
2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
HTTP_HOST : localhost
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
REQUEST_URI : /cgi-bin/web_04.py
WINDIR : C:\Windows
GATEWAY_INTERFACE : CGI/1.1
REMOTE_PORT : 58468
HTTP_ACCEPT_ENCODING : identity

Cabe señalar que el cliente programado no recibe exactamente la misma respuesta que el navegador web. Esto se debe a que este último ha enviado al servidor web información que ha sido utilizada por el servidor web para crear su respuesta. En este caso, el cliente programado no ha enviado ninguna información sobre sí mismo.