Skip to content

13. Ejercicio [IMPOTS] con un servicio WEB

Volvemos al ejercicio [IMPOTS], ya tratado en los párrafos 4.2, 4.3, 8 y 10.

13.1. El servidor

Nos situamos en la siguiente arquitectura cliente-servidor:

Del lado del servidor, volveremos a utilizar una arquitectura de tres capas. La capa [dao] será implementada por la clase ImpotsMySQL utilizada en la versión con SGBD y MySQL en el párrafo 10. La capa [metier] será implementada por la clase [ImpotsMetier] que ya hemos analizado. Por lo tanto, solo queda escribir el servicio web. Este recibe de sus clientes un parámetro params en el formato params= sí,2,200000, donde el primer elemento indica si el contribuyente está casado o no, el segundo, el número de hijos, y el tercero, su salario anual.

El servicio web es el siguiente (impots_web_01):


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

# -*- coding=Utf-8 -*-
# importación del módulo de las clases Impuestos*
from impots import *
import cgi,cgitb,re

# se permite mostrar información de depuración
cgitb.enable()
sys.stderr=sys.stdout

# ------------------------------------------------ 
# el servicio web de impuestos
# ------------------------------------------------ 

# los datos necesarios para el cálculo del impuesto se han colocado en la tabla mysqL TABLE
#, que pertenece a la base de datos BASE. La tabla tiene la siguiente estructura
# límites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
# los parámetros de las personas sujetas a impuestos (estado civil, número de hijos, salario anual)
# son enviados por el cliente en el formato params=estado civil, número de hijos, salario anual
# los resultados (estado civil, número de hijos, salario anual, impuesto a pagar) se devuelven al cliente
# en el formato <impuesto>valor</impuesto>
# o en el formato <error>msg</error>, si los parámetros no son válidos

# el servidor devuelve al cliente texto sin formato
print "Content-Type: text/plain\n"

# definición de las constantes
USER="root"
PWD=""
HOTE="localhost"
BASE="dbimpots"
TABLE="impots"


# se crea la capa [metier]
# Python no parece tener soporte para sesiones
# en PHP, se habría utilizado una sesión para almacenar el objeto de negocio
# aquí, lo construimos sistemáticamente a partir de los datos de la base de datos MySQL

# instanciación de la capa [metier]
try:
    metier=ImpotsMetier(ImpotsMySQL(HOTE,USER,PWD,BASE,TABLE))
except (IOError, ImpotsError) as infos:
    print ("<erreur>Une erreur s'est produite : {0}</erreur>".format(infos))
    sys.exit()

# se recupera la línea enviada por el cliente al servidor
params=cgi.FieldStorage().getlist('params')
# si no hay parámetros, se genera un error
if not params:
  print "<erreur>Le parametre [params] est absent<erreur>"
  sys.exit()

# se procesa el parámetro «params»  
items=params[0].strip().lower().split(',')
# solo debe haber 3 campos
if len(items)!=3:
  print "<erreur>[%s] : nombre de parametres invalide</erreur>" % (params[0])
  sys.exit()
# el primer parámetro (estado civil) debe ser sí/no
marie=items[0].strip()
if marie!="oui" and marie != "non":
  print "<erreur>[%s] : 1er parametre invalide</erreur>\n"% (params[0])
  sys.exit()
# el segundo parámetro (número de hijos) debe ser un número entero
match=re.match(r"^\s*(\d+)\s*$",items[1])
if not match:
  print "<erreur>[%s] : 2ieme parametre invalide</erreur>\n"% (params[0])
  sys.exit()
enfants=int(match.groups()[0])
# el tercer parámetro (salario) debe ser un número entero
match=re.match(r"^\s*(\d+)\s*$",items[2])
if not match:
  print "<erreur>[%s] : 3ieme parametre invalide</erreur>\n"% (params[0])
  sys.exit()
salaire=int(match.groups()[0])
# se calcula el impuesto
impot=metier.calculer(marie,enfants,salaire)
# se devuelve el resultado
print "<impot>%s</impot>\n" % (impot)
# fin

Notas:

  • línea 5: se importan los objetos del archivo impots.py;
  • línea 9: depurar un script CGI puede resultar problemático. El módulo cgitb envía, en caso de error, el motivo de la falla. Para ello, es necesario redirigir la salida de error estándar (sys.stderr) hacia la salida estándar (sys.stdout) (línea 10);
  • línea 26: el encabezado HTTP que define el tipo de documento enviado, en este caso un texto sin formato. El servicio web devuelve su respuesta en forma de una sola línea de texto:
    • <erreur>message</erreur> si hay un error;
    • <impot>valeur</impot>, donde valeur es el monto del impuesto.
  • línea 43: instanciación de las capas [dao] y [metier];
  • líneas 44-46: en caso de error en la instanciación, el script envía su respuesta y finaliza;
  • líneas 49-53: se recupera el parámetro «params». Si no está presente, el script envía su respuesta y finaliza;
  • línea 56: el parámetro «sí,2,200000» se descompone en tres campos en la tabla items;
  • líneas 58-77: se verifica la validez de los tres campos. Si hay un error, el script envía su respuesta y finaliza;
  • línea 79: se calcula el impuesto;
  • línea 81: y se envía al cliente.

En un navegador web, se obtienen los siguientes resultados:

13.2. Un cliente programado


El programa (client_impots_web_01)


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

import httplib,urllib,re

# constantes
HOST="localhost"
URL="/cgi-bin/impots_web_01b.py"
data=("oui,2,200000","non,2,200000","oui,3,200000","non,3,200000","x,y,z,t","x,2,200000", "oui,x,200000","oui,2,x");

# inicio de sesión
connexion=httplib.HTTPConnection(HOST)
# seguimiento
#connexion.set_debuglevel(1)
# se recorre el bucle con los datos que se enviarán al servidor
for params in data:
    # los parámetros deben codificarse antes de enviarse al servidor
    parametres = urllib.urlencode({'params': params})
    # envío de la solicitud
    connexion.request("POST",URL,parametres)
    # procesamiento de la respuesta (línea de texto)
    reponse=connexion.getresponse().read()
    lignes=reponse.split("\n")
    # la línea de texto tiene el siguiente formato
    # <impot>xxx</impot>
    # o <error>xxx</error>
    impot=re.match(r"^<impot>(\S+)</impot>\s*$",lignes[0])
    if impot:
        print "params=%s, impot=%s" % (params,impot.groups()[0])
    else:
        erreur=re.match(r"^<erreur>(.+)</erreur>\s*$",lignes[0])
        if erreur:
            print "params=%s, erreur=%s" % (params,erreur.groups()[0])
        else:
            print lignes
# cierre de la conexión
connexion.close()

13.3. Résultats

1
2
3
4
5
6
7
8
params=oui,2,200000, impot=22504.0
params=non,2,200000, impot=33388.0
params=oui,3,200000, impot=16400.0
params=non,3,200000, impot=22504.0
params=x,y,z,t, erreur=[x,y,z,t] : nombre de parametres invalide
params=x,2,200000, erreur=[x,2,200000] : 1er parametre invalide
params=oui,x,200000, erreur=[oui,x,200000] : 2ieme parametre invalide
params=oui,2,x, erreur=[oui,2,x] : 3ieme parametre invalide