Skip to content

13. 练习 [税费计算] 与 Web 服务

我们回到第 4.24.38 10 节中已经讲过的 [TAXES] 练习。

13.1. 服务器

我们采用以下客户端-服务器架构:

在服务器端,我们将再次采用三层架构。[DAO]层将由第10节中使用MySQL数据库管理系统(DBMS)的版本中所用的ImpotsMySQL类实现。[业务]层将由已学习的[ImpotsMetier]类实现。 剩下的就是编写 Web 服务了。该服务从客户端接收一个 params 参数,格式为 params= yes,2,200000其中第一个元素表示纳税人是否已婚,第二个表示子女数量,第三个表示年薪。

Web 服务代码如下(impots_web_01):

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

#    -*- coding=Utf-8 -*-
#    import of Impots* class module
from impots import *
import cgi,cgitb,re

#    allow debugging information to be displayed
cgitb.enable()
sys.stderr=sys.stdout

# ------------------------------------------------ 
#  tax web service
# ------------------------------------------------ 

#  the data required to calculate the tax has been placed in table mysqL TABLE
#  belonging to the BASE database. The table has the following structure
#    limits decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
#  taxable person parameters (marital status, number of children, annual salary)
#  are sent by the customer in the form params=marital status, number of children, annual salary
#  results (marital status, number of children, annual salary, tax payable) are returned to the customer
#  in the form <impot>value</impot>
#  or as <error>msg</error>, if parameters are invalid

#  the server returns unformatted text to the client
print "Content-Type: text/plain\n"

#    definition of constants
USER="root"
PWD=""
HOTE="localhost"
BASE="dbimpots"
TABLE="impots"


#  we create the [metier] layer
#    Python doesn't seem to support sessions
#  in PHP, we would have used a session to memorize the metier object
#  here, it is systematically constructed from data in the MySQL database

#    instantiation layer [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()

#  retrieve the line sent by the client to the server
params=cgi.FieldStorage().getlist('params')
#  if no parameters then error
if not params:
  print "<erreur>Le parametre [params] est absent<erreur>"
  sys.exit()

#  we use the params parameter  
items=params[0].strip().lower().split(',')
#  there must be only 3 fields
if len(items)!=3:
  print "<erreur>[%s] : nombre de parametres invalide</erreur>" % (params[0])
  sys.exit()
#  first parameter (marital status) must be yes/no
marie=items[0].strip()
if marie!="oui" and marie != "non":
  print "<erreur>[%s] : 1er parametre invalide</erreur>\n"% (params[0])
  sys.exit()
#  the second parameter (number of children) must be an integer
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])
#  the third parameter (salary) must be an integer
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])
#  tax calculation
impot=metier.calculer(marie,enfants,salaire)
#  return the result
print "<impot>%s</impot>\n" % (impot)
#    end

注:

  • 第 5 行:从 impots.py 文件导入对象
  • 第 9 行:调试 CGI 脚本可能比较棘手。cgitb 模块会在发生错误时发送崩溃原因。为此,必须将标准错误输出(sys.stderr)重定向到标准输出(sys.stdout)(第 10 行);
  • 第 26 行:指定所发送文档性质的 HTTP 头,本例中为未格式化的文本。Web 服务将响应以单行文本形式返回:
    • <error>message</error>(若发生错误);
    • <tax>value</tax>,其中 value 表示税额。
  • 第 43 行:实例化 [dao] 和 [business] 层;
  • 第 44–46 行:若发生实例化错误,脚本将发送响应并终止;
  • 第 49–53 行:检索 'params' 参数。若该参数缺失,脚本将发送响应并终止;
  • 第 56 行:将参数 'yes,2,200000' 拆分为 items 数组中的 3 个字段
  • 第58–77行:检查这3个字段的有效性。若存在错误,脚本发送响应并终止;
  • 第 79 行:计算税额;
  • 第 81 行:并将结果发送给客户端。

在网页浏览器中,将得到以下结果:

13.2. 一个编程客户端


该程序(client_impots_web_01)

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

import httplib,urllib,re

#    constant
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");

#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
#   connexion.set_debuglevel(1)
#  loop over the data to be sent to the server
for params in data:
    #  parameters must be encoded before being sent to the server
    parametres = urllib.urlencode({'params': params})
    #  send request
    connexion.request("POST",URL,parametres)
    #  answer processing (text line)
    reponse=connexion.getresponse().read()
    lignes=reponse.split("\n")
    #  the text line is of the form
    #    <impot>xxx</impot>
    #  or <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
#    locking connection
connexion.close()

13.3. 结果

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