Skip to content

13. Ejercicio [IMPOTS] con un servicio WEB

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

13.1. El servidor

Nos situamos en la siguiente arquitectura cliente-servidor:

En el lado del servidor, volveremos a utilizar una arquitectura de tres capas. La capa [dao] se implementará mediante la clase ImpotsMySQL utilizada en la version con la SGBD MySQL del apartado 10. La capa [metier] se implementará mediante la clase [ImpotsMetier] ya estudiada. Por lo tanto, solo queda escribir el servicio web. Este recibe de su clients un parámetro params con el formato params= sí,2,200000, donde el primer elemento indica si el contribuyente está casado o no, el segundo su 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 clases Impuestos*
from impots import *
import cgi,cgitb,re

# se permite la visualización de 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
# perteneciente 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 admitir sesiones
# en PHP, se habría utilizado una sesión para almacenar el objeto de negocio
# aquí, se construye 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 produce 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 del fallo. Para ello, hay que redirigir la salida de error estándar (sys.stderr) a la salida estándar (sys.stdout) (línea 10);
  • línea 26: el encabezado Http que determina la naturaleza del documento enviado, en este caso un texto sin formato. El servicio web devuelve su respuesta en forma de una única línea de texto:
    • <error>mensaje</error> si hay un error;
    • <impuesto>valor</impuesto>, donde valor es el importe 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 «oui,2,200000» se descompone en 3 campos en la matriz items;
  • líneas 58-77: se comprueba la validez de los 3 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");

# conexión
connexion=httplib.HTTPConnection(HOST)
# seguimiento
#connexion.set_debuglevel(1)
# se recorre el bucle de los datos que se van a enviar 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 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. Resultados

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