Skip to content

29. 应用练习:第11版

29.1. 简介

在税款计算客户端/服务器应用程序的先前版本中,实现该计算业务规则的 [métier] 层位于服务器端。现在我们计划将其移至客户端。 这样做有什么好处?服务器原先承担的工作将转移至客户端。设想一个由 N 个客户端查询的服务器,N 项税务业务计算将由客户端完成。在之前的版本中,这些 N 项业务计算均由服务器完成。由于不再执行业务计算,服务器将更快地响应客户端请求,从而能够同时服务更多的客户端。

客户端/服务器架构如下:

Image

  • [métier] [10] 层已在客户端复制为 [12]
  • 客户端新增了脚本 [main2] [11]

Web客户端将有两种方式来计算[3]中找到的纳税人列表的税款:

  • 使用上一版本的方法。该方法利用服务器端的 [métier] 和 [10] 层。脚本 [main] 将采用此方法;
  • 仅向服务器请求税务管理数据([2-4]),然后使用客户端本地的 [métier] 和 [12] 层;

我们将对比这两种方法的性能。

29.2. Web 服务器

Web 服务器的目录结构如下:

Image

  • 文件 [http-servers/06] 最初是通过复制文件 [http-servers/05] 生成的。 实际上,我们将保留前一版本(10版)的既有成果,仅在此基础上新增一项功能。该功能体现在新增的控制器 [get_admindata_controller] [1] 上。 另一个控制器[calculate_tax_controller],其实就是旧控制器[index_controller]更名后的版本;

29.3. 配置

服务器将提供两个 URL 服务:

  • [/calculate-tax] 用于计算传递在 POST 主体中的纳税人列表的税款。 因此,它对应于先前第 10 版中的 URL [/]
  • [/get-admindata] 生成税务管理数据链 jSON;

配置 [config] 将上述每个 URL 与负责处理它的控制器关联起来:


    # Web 应用程序控制器字典
    import calculate_tax_controller, get_admindata_controller
    controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
    config['controllers'] = controllers

29.4. 主脚本 [main]

主脚本 [main] 对上一版本的脚本 [main] 进行了重组:


# 等待 mysql 或 pgres 参数
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
    sgbd = sys.argv[1].lower()
    erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()

# 配置应用程序
import config
config = config.configure({'sgbd': sgbd})

# 依赖项


# 从税务管理部门获取数据
erreur = False
try:
    # admindata 将作为应用程序范围内的只读数据
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # 成功日志
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # 记录错误
    …

# 若发生错误则终止
if erreur:
    sys.exit(2)

# Flask 应用程序可以启动
app = Flask(__name__)

# 主控制器
def main_controller() -> tuple:
    # 获取请求的操作
    dummy, action=request.path.split('/')
    logger = None
    try:
        # 日志记录器
        logger = Logger(config["logsFilename"])
        # 将其存储在与线程关联的配置中
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        # 记录该请求
        logger.write(f"[index] requête : {request}\n")
        # 若收到中断请求,则中断该线程
        sleep_time = config["sleep_time"]
        if sleep_time != 0:
            # 暂停是随机的,以便某些线程被中断而其他线程不被中断
            aléa = randint(01)
            if aléa == 1:
                # 暂停前的日志
                logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                # 暂停
                time.sleep(sleep_time)
        # 由请求控制器执行该请求
        controller = config['controllers'][action]
        résultat, status_code = controller.execute(request, config)
        # 是否发生致命错误?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # 向应用程序管理员发送邮件
            config_mail = config["adminMail"]
            config_mail["logger"] = logger
            SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
        # 将响应记录到日志中
        logger.write(f"[index] {résultat}\n")
        # 发送响应
        print(résultat)
        return json_response(résultat, status_code)
    except BaseException as erreur:
        # 如果可能,记录错误
        if logger:
            logger.write(f"[index] {erreur}")
        # 准备回复给客户
        résultat = {"réponse": {"erreurs"[f"{erreur}"]}}
        # 发送响应
        return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # 若日志文件已打开,则关闭
        if logger:
            logger.close()

# 计算税款
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
    # 将控制权移交给主控制器
    return main_controller()

# 获取税务部门的数据
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
    # 移交给首席审计员
    return main_controller()

# 仅控制
if __name__ == '__main__':
    # 启动服务器
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • 第 88-93 行:函数 [calculate_tax] 处理 URL 和 [/calculate-tax]
  • 第 95-100 行:函数 [get_admindata] 处理 URL 和 [/get-admindata]
  • 这两个函数本身不执行任何操作。它们会立即将控制权移交给第37-86行的主控制器[main_controller]
  • 第37-86行:主控制器[main_controller]与上一版本的[index]函数除了一处细节外并无二致: 在函数 [index] 仅处理单个 URL 的情况下,此处的 [main_controller] 则处理两个 URL。 因此,它需要让其中一个控制器 [calculate_tax_controller, get_admin,data_controller] 来处理这些文件;
  • 第39-40行:获取请求的操作[calculate_tax][get_admindata]。该信息位于URL到[request.path]的路径中。 根据具体情况,[request.path] 可能对应 [/get-admindata] [/calculate_tax]。第 40 行的拆分将产生两个元素:
    • / 符号前部分的空字符串;
    • / 之后部分对应请求的操作名称;
  • 第62-63行:一旦获取了URL的操作,便知晓应使用哪个控制器来处理URL。该信息存储在[config]的配置中;

29.5. 控制器

控制器 [calculate_tax_controller] 正是上一版本中的 [index_controller]

而控制器 [get_admindata_controller] 则如下所示:


from flask_api import status
from werkzeug.local import LocalProxy

def execute(request: LocalProxy, config: dict) -> tuple:
    # 返回响应
    return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
  • URL [/get-admindata] 应生成税务管理数据的字符串 jSON;
  • 第6行:这些数据由主脚本[main]获取,并以对象[AdminData]的形式存入字典[config]中。此处返回该对象的字典;

29.6. Postman 测试

启动Web服务器、SGBD以及邮件服务器[hMailServer]。随后使用Postman客户端,对多位纳税人的税款进行计算:

Image

在 Postman 控制台中,客户端与服务器的交互如下:


POST /calculate-tax HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 5e71461a-fec8-4315-85e8-41721de939e5
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 824

[ 
{ 
"marié": "oui", 
"enfants": 2, 
"salaire": 55555 
}, 

{ 
"marié": "oui", 
"enfants": 3, 
"salaire": 200000 
} 
]

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 1461
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:02:07 GMT

{"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347…]}}

现在让我们使用 URL 和 [/get-admindata] 来调用 GET:

Image

Postman 控制台中的客户端/服务器对话如下:


GET /get-admindata HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 4af342c4-7ecb-4ab2-9e12-d653f81da424
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 596
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:07:24 GMT

{"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 93749.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}

29.7. Web客户端

Image

Image

文件 [http-clients/06] 最初是通过复制文件 [http-clients/05] 获得的。修改工作主要包括:

  • 修改配置 [config_layers],使其现在包含一个 [métier] 层。此前它仅有一个 [dao] 层;
  • [dao] 层中添加一个新方法;
  • 编写脚本 [main2],该脚本将基于客户端的 [métier] 层来计算纳税人的税款;

29.7.1. 客户端层的配置

层配置涉及两个方面:

  • 在配置 [config] 时,需将包含 [métier] 层实现的文件夹添加到客户端的依赖项中。该文件夹此前已包含在依赖项中:

absolute_dependencies = [
        # 项目文件夹
        # BaseEntity, MyException
        f"{root_dir}/classes/02/entities",
        # InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
        f"{root_dir}/impots/v04/interfaces",
        # AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
        f"{root_dir}/impots/v04/services",
        # ImpotsDaoWithAdminDataInDatabase
        f"{root_dir}/impots/v05/services",
        # AdminData, ImpôtsError, TaxPayer
        f"{root_dir}/impots/v04/entities",
        # 常量、区间
        f"{root_dir}/impots/v05/entities",
        # ImpôtsDaoWithHttpClient
        f"{script_dir}/../services",
        # 配置脚本
        script_dir,
        # 日志记录器
        f"{root_dir}/impots/http-servers/02/utilities",
    ]

随后需修改文件 [config_layers]


def configure(config: dict) -> dict:
    # 应用程序层的实例化

    # [métier] 层
    from ImpôtsMétier import ImpôtsMétier
    métier = ImpôtsMétier()

    # DAO层
    from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
    dao = ImpôtsDaoWithHttpClient(config)

    # 返回图层配置
    return {
        "dao": dao,
        "métier": métier
    }
  • 第 4-6 行:实例化 [métier] 层;
  • 第13-16行:将[métier]层添加到层字典中;

29.7.2. 实现 [dao]

Image

[dao] 层将提供以下 [InterfaceImpôtsDaoWithHttpClient] 接口:


from abc import abstractmethod

from AbstractImpôtsDao import AbstractImpôtsDao

class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):

    # 计算税款
    @abstractmethod
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        pass
  • 第 5 行:接口 [InterfaceImpôtsDaoWithHttpClient] 继承了抽象类 [AbstractImpôtsDao],该类负责管理对客户端文件系统的访问。需要提醒的是,该类包含一个抽象方法 [get_admindata]
  • 第7-10行:我们在上一版本中定义的方法[calculate_tax_in_bulk_mode]用于计算纳税人列表的税款;

该接口由以下 [ImpôtsDaoWithHttpClient] 类实现:


# 导入

import json

import requests
from flask_api import status

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpClient import InterfaceImpôtsDaoWithHttpClient

class ImpôtsDaoWithHttpClient(InterfaceImpôtsDaoWithHttpClient):

    # 构建器
    def __init__(self, config: dict):
        # 父级初始化
        AbstractImpôtsDao.__init__(self, config)
        # 保存配置项
        # 通用配置
        self.__config = config
        # 服务器
        self.__config_server = config["server"]
        # 调试模式
        self.__debug = config["debug"]
        # 日志记录器
        self.__logger = None

    def get_admindata(self) -> AdminData:
        # 允许异常上报

        # 服务网址
        config_server = self.__config_server
        config_services = config_server['url_services']
        url_service = f"{config_server['urlServer']}{config_services['get-admindata']}"

        # 连接
        if config_server['authBasic']:
            response = requests.get(url_service,
                                    auth=(
                                        config_server["user"]["login"],
                                        config_server["user"]["password"]))
        else:
            response = requests.get(url_service)

        # 调试模式?
        if self.__debug:
            # 日志记录器
            if not self.__logger:
                self.__logger = self.__config['logger']
            # 正在登录
            self.__logger.write(f"{response.text}\n")

        # 状态代码
        status_code = response.status_code
        # 结果以字典形式呈现
        résultat = json.loads(response.text)
        # 如果状态码不为 200OK
        if status_code != status.HTTP_200_OK:
            raise ImpôtsError(58, résultat['réponse']['erreurs'])
        # 返回结果(字典)
        return AdminData().fromdict(résultat["réponse"]["result"])

    # 批量模式下计算税款
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        # 允许异常上报

        
  • 第13行:[ImpôtsDaoWithHttpClient]实现了接口[InterfaceImpôtsDaoWithHttpClient]。因此,它继承自类[AbstractImpôtsDao]
  • 第 65-66 行:前一版本中讨论过的 [calculate_tax_in_bulk_mode] 方法;
  • 第29-62行:方法[get_admindata],其父类[AbstractImpôtsDao]已将其声明为抽象方法。因此,该方法在子类中被实现;
  • 第33-35行:确定Web服务URL,方法[get-admindata]需向其发起请求。 这些服务 URL 在客户端配置 [config] 中定义:

       # 税款计算服务器
        "server": {
            "urlServer""http://127.0.0.1:5000",
            "authBasic"True,
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                "calculate-tax""/calculate-tax",
                "get-admindata""/get-admindata"
            }
        },
  • (续)
    • 第 9-12 行:Web 服务器的两个 URL;
  • 第37-44行:以同步方式查询服务URL;
  • 第46-42行:若配置要求,则记录服务器的响应;
  • 第 57 行:已知服务器发送了字典中的一条字符串 jSON;
  • 第58-60行:如果响应状态HTTP不为200,则抛出异常;
  • 第61-62行:返回封装服务器发送的税务管理数据的[AdminData]对象;

29.8. 脚本 [main, main2]

脚本 [main] 是上一版本的脚本。它使用 [dao] 层中的 [calculate_tax_in_bulk_mode] 方法,因此使用服务器上的 [métier] 层;

脚本 [main2] 与脚本 [main] 功能相同,但使用客户端的 [métier] 层:


# 配置应用程序

import config
config = config.configure({})

# 依赖项
from ImpôtsError import ImpôtsError
from Logger import Logger

logger = None
# 代码
try:
    # 日志记录器
    logger = Logger(config["logsFilename"])
    # 将其存储在配置中
    config["logger"] = logger
    # 启动日志
    logger.write("début du calcul de l'impôt des contribuables\n")
    # 获取 [dao] 层
    dao = config["layers"]["dao"]
    # 检索纳税人
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    # 纳税人?
    if not taxpayers:
        raise ImpôtsError(36f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    # 获取税务部门的数据
    admindata = dao.get_admindata()
    # 通过 [métier] 层计算纳税人的税款
    métier = config['layers']['métier']
    for taxpayer in taxpayers:
        métier.calculate_tax(taxpayer, admindata)
    # 将结果保存到文件中 jSON
    dao.write_taxpayers_results(taxpayers)
# 除 BaseException 外均视为错误:
# # 显示错误
# print(f"发生以下错误:{错误}")
finally:
    # 关闭日志记录器
    if logger:
        # 结束日志
        logger.write("fin du calcul de l'impôt des contribuables\n")
        # 关闭日志器
        logger.close()
    # 已完成
    print("Travail terminé...")
  • 第 26-27 行:从服务器获取税务部门的数据;
  • 第 28-31 行:随后在本地计算纳税人的税款;

29.9. 客户端测试

在每个 [main, main2] 脚本中,我们都会记录脚本的开始和结束时间。这样就可以计算出脚本的执行时间。让我们做一些预测:

  • 上一版本的脚本 [main]
    • 创建 N 个线程并同时执行;
    • 每个线程处理一批纳税人,并通过向服务器发送单次请求来计算其税款;
    • 由于 N 个线程同时运行,第 N+1 个请求会在第 N 个请求收到响应之前就被发起。因此,这 N 个请求的开销虽然高于单次请求,但可能不会高出太多。此外,服务器端还需进行 11 次(即纳税人数量)业务计算;
  • 该版本的脚本 [main2]
    • 仅向服务器发起一次请求;
    • 在客户端本地执行11项业务计算;

无论在服务器端还是客户端,业务计算所需的时间都是一样的。因此,差异主要体现在请求上。可以预期,[main]的执行时间会比[main2]稍长一些。

我们启动版本 11 的服务器、SGBD 以及邮件服务器 [hMailServer]。在服务器端,我们将参数 [sleep_time] 设为零,以确保两项测试在相同条件下执行。

运行 1 [main]

执行 [main] 生成以下日志:


2020-07-29 14:35:50.016079, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:35:50.016079, Thread-1 : début du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.016079, Thread-2 : début du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.016079, Thread-3 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.016079, Thread-4 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.024426, Thread-5 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.050473, Thread-1 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.050473, Thread-1 : fin du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.050473, Thread-3 : {"réponse": {"results": [{"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-3 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-5 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-5 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-2 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-2 : fin du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.051214, Thread-4 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-4 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, MainThread : fin du calcul de l'impôt des contribuables

执行耗时为 [051214-016079] 纳秒(第 17 行至第 1 行),即 35 毫秒和 135 纳秒。

可以看出,从向服务器发出第一个请求到客户端收到最后一个响应,耗时相同,即 [051214-016079](第 15 行至第 1 行),35 毫秒和 135 纳秒。

执行 2 [main2]

执行 [main2] 生成以下日志:


2020-07-29 14:41:03.303520, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:41:03.345084, MainThread : {"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}
2020-07-29 14:41:03.349975, MainThread : fin du calcul de l'impôt des contribuables

[349975-303520]的执行时间为(第3行至第1行)纳秒,即46毫秒和455纳秒。 出乎意料的是,[main][main2] 运行得更快。

可以看出,[main2] 的单次请求耗时 [345084-303520](第 2 行减去第 1 行),即 41 毫秒和 564 纳秒。 随后,税款计算耗时 [349975-345084](第 3 行 – 第 2 行),即 4 毫秒又 91 纳秒。正是 HTTP 这个请求决定了执行时间。 令人惊讶的是,我们可以看到,[main2]的单个请求耗时比[41 millisecondes]的四个并发请求([main]和[35 millisecondes])更长。

服务器端日志如下:


2020-07-29 14:35:27.047721, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:27.140927, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:28.790716, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:28.847518, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:50.039178, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.039178, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.043220, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.044307, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.045796, Thread-2 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'taux': 0.3, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.045796, Thread-3 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.046825, Thread-6 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'taux': 0.14, 'décôte': 384, 'réduction': 347}, {'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'taux': 0.14, 'décôte': 720, 'réduction': 0}, {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-4 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'taux': 0.45, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-5 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'taux': 0.0, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:41:03.341582, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/get-admindata' [GET]>
2020-07-29 14:41:03.341582, Thread-7 : [index] {'réponse': {'result': {'limites': [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], 'coeffr': [0.0, 0.14, 0.3, 0.41, 0.45], 'coeffn': [0.0, 1394.96, 5798.0, 13913.7, 20163.4], 'plafond_decote_couple': 1970.0, 'valeur_reduc_demi_part': 3797.0, 'plafond_revenus_celibataire_pour_reduction': 21037.0, 'plafond_qf_demi_part': 1551.0, 'abattement_dixpourcent_max': 12502.0, 'plafond_impot_celibataire_pour_decote': 1595.0, 'plafond_decote_celibataire': 1196.0, 'plafond_revenus_couple_pour_reduction': 42074.0, 'id': 1, 'abattement_dixpourcent_min': 437.0, 'plafond_impot_couple_pour_decote': 2627.0}}}
  • 第 5 行:客户端 [main] 的首次请求;
  • 第14行:对客户端[main]的最后一次响应。两者之间间隔6毫秒和647纳秒;
  • 第15-16行:客户端的唯一请求 [main2]。响应是即时的;