24. Exercício prático: Versão 7
24.1. Introdução
A versão 7 da aplicação de cálculo de impostos é idêntica à versão 6, exceto pelos seguintes detalhes:
- o cliente web enviará múltiplas solicitações HTTP simultaneamente. Na versão anterior, estas solicitações eram enviadas sequencialmente. O servidor só podia, portanto, processar uma única solicitação de cada vez;
- o servidor será multithread: poderá processar várias solicitações simultaneamente;
- Para acompanhar a execução destas solicitações, o servidor web será equipado com um registador que irá registar momentos-chave no processamento das solicitações num ficheiro de texto;
- o servidor enviará um e-mail ao administrador da aplicação quando encontrar um problema que o impeça de iniciar, normalmente uma questão relacionada com a base de dados associada ao servidor web;
A arquitetura da aplicação permanece inalterada:

A estrutura de diretórios dos scripts é a seguinte:

A pasta [http-servers/02] é criada inicialmente através da cópia da pasta [http-servers/01]. Em seguida, são feitas alterações nessa pasta.
24.2. Os utilitários

24.2.1. A classe [Logger]
A classe [Logger] permite que determinadas ações do servidor web sejam registadas num ficheiro de texto:
- Linhas 10–11: Definimos um atributo de classe. Um atributo de classe é uma propriedade partilhada por todas as instâncias da classe. É referenciado utilizando a notação [Classe.atributo_da_classe] (linhas 30, 39). O atributo de classe [lock] servirá como objeto de sincronização para todos os threads que executam o código nas linhas 31–36;
- linhas 14–19: O construtor recebe o caminho absoluto do ficheiro de registo. Este ficheiro é então aberto, e o descritor de ficheiro recuperado é armazenado na classe;
- linha 17: o ficheiro de registo é aberto no modo «append» (a). Cada linha escrita será acrescentada ao final do ficheiro;
- linhas 22–39: o método [write] permite que uma mensagem passada como parâmetro seja gravada no ficheiro de registo. Duas informações são anexadas a esta mensagem:
- linha 24: a data atual;
- linha 25: a hora atual;
- linha 27: o nome do thread que está a escrever o registo. É importante lembrar aqui que uma aplicação web atende vários utilizadores simultaneamente. A cada pedido é atribuído um thread para o executar. Se este thread for pausado — normalmente para uma operação de E/S (rede, ficheiros, base de dados) —, o processador é transferido para outro thread. Devido a estas possíveis interrupções, não podemos ter a certeza de que uma thread conseguirá escrever uma linha no ficheiro de registo sem ser interrompida. Existe, portanto, o risco de que os registos de duas threads diferentes possam ficar misturados. O risco é baixo, talvez até nulo, mas decidimos, mesmo assim, mostrar como sincronizar o acesso de duas threads a um recurso partilhado, neste caso o ficheiro de registo;
- linha 30: antes de escrever, a thread solicita a chave da porta de entrada. A chave solicitada é a criada na linha 11. É, de facto, única: um atributo de classe é único para todas as instâncias da classe;
- No momento T1, uma thread chamada Thread1 obtém a chave. Pode então executar a linha 33;
- No momento T2, a thread Thread1 é pausada antes mesmo de terminar de escrever o log;
- No momento T3, a thread Thread2, que adquiriu o processador, também deve escrever um registo. Chega assim à linha 30, onde solicita a chave da porta da frente. É informada de que outra thread já a possui. É então automaticamente pausada. Este será o caso para todas as threads que solicitarem esta chave;
- No momento T4, o thread Thread1, que estava em pausa, recupera o processador. Em seguida, conclui a gravação do registo;
- Linhas 32–36: A gravação no ficheiro de registo ocorre em duas etapas:
- linha 33: o descritor de ficheiro obtido na linha 17 funciona com um buffer. A operação [write] na linha 33 escreve neste buffer, mas não diretamente no ficheiro. O buffer é então descarregado para o ficheiro sob certas condições:
- o buffer está cheio;
- o descritor de ficheiro é submetido a uma operação [close] ou [flush];
- linha 36: forçamos a linha de registo a ser gravada no ficheiro. Fazemos isto porque queremos ver os registos das diferentes threads intercalados. Se não o fizermos, os registos de uma única thread serão todos gravados ao mesmo tempo — quando o descritor for fechado na linha 45. Seria então muito mais difícil perceber que certas threads foram interrompidas: teríamos de verificar os carimbos de data/hora nos registos;
- linha 39: a thread Thread1 devolve o bloqueio que lhe foi atribuído. Agora, este pode ser atribuído a outra thread;
- linha 22: o método [write] é, portanto, sincronizado: apenas uma thread de cada vez escreve no ficheiro de registo. A chave do mecanismo está na linha 30: aconteça o que acontecer, apenas uma thread recupera a chave para avançar para a linha seguinte. Mantém-na até a devolver (linha 39);
- linhas 41–45: o método [close] liberta os recursos alocados ao descritor do ficheiro de registo;
Os registos gravados no ficheiro de registo terão o seguinte aspeto:
24.2.2. A classe [SendAdminMail]
A classe [SendAdminMail] permite enviar uma mensagem ao administrador da aplicação quando esta falha.

A classe [SendAdminMail] é configurada no script [config] [2] da seguinte forma:
A classe [SendAdminMail] recebe o dicionário das linhas 2–13, bem como a configuração de envio de e-mail. A classe é a seguinte:
- linhas 24-54: este é o código já abordado no exemplo |smtp/02|;
- linha 20: recuperamos a referência de um logger. Isto é utilizado nas linhas 45 e 49;
24.3. O servidor web
24.3.1. Configuração
A configuração do servidor é muito semelhante à do servidor discutido anteriormente. Apenas o ficheiro [config.py] sofreu uma ligeira alteração:
- linhas 40–66: Adicionamos elementos relacionados com o logger (linha 49) e aqueles relacionados com o envio de um e-mail de alerta ao administrador da aplicação (linhas 51–63) ao dicionário de configuração do servidor;
- linha 65: para ver melhor os threads em ação, vamos forçar alguns deles a fazer uma pausa. [sleep_time] é a duração da pausa expressa em segundos;
- linhas 27–28: Note que estamos a utilizar o [index_controller] da versão 6 anterior;
24.3.2. O script principal [main]
O script principal [main] é o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
- linhas 1-10: o script espera um parâmetro [mysql / pgres] que especifique o SGBD a utilizar;
- linhas 12–14: a aplicação é configurada (Python Path, camadas, base de dados);
- linhas 16–28: dependências exigidas pela aplicação;
- linhas 30-43: gestão da autenticação;
- linhas 46–51: uma função que envia um e-mail ao administrador da aplicação;
- a função espera dois parâmetros:
- config: um dicionário com as chaves [adminMail] e [logger];
- a mensagem a enviar;
- linhas 49–50: preparamos a configuração do e-mail;
- enviamos o e-mail;
- linhas 54–74: verificamos se o ficheiro de registo existe;
- linhas 70–74: se não foi possível abrir o ficheiro de registo, enviamos um e-mail ao administrador e saímos;
- linhas 76–79: registamos o arranque do servidor;
- linhas 81–98: recuperamos os dados da administração fiscal da base de dados;
- linhas 88–98: se não foi possível recuperar estes dados, registamos o erro tanto na consola como no ficheiro de registo;
- linhas 100–101: O segmento principal deixará de registar (os segmentos criados não utilizarão o mesmo descritor de ficheiro);
- linhas 103–105: se não foi possível estabelecer ligação à base de dados, interrompemos;
- linha 122: o servidor é iniciado no modo multithread;
A função [index] (linha 114) é a seguinte:
- Linha 4: a função executada quando um utilizador solicita a URL /. Como o servidor é multithread (linha 112), será criada uma thread para executar a função. Esta thread pode ser interrompida e pausada a qualquer momento para retomar a execução um pouco mais tarde. Tenha sempre isto em mente quando o código aceder a um recurso partilhado por todas as threads. Neste caso, esse recurso é o ficheiro de registo: todas as threads escrevem nele;
- linha 8: criamos uma instância do logger. Assim, todas as threads terão uma instância diferente do logger. No entanto, todos estes loggers apontam para o mesmo ficheiro de registo. É importante notar que, quando uma thread fecha o seu logger, isso não tem efeito sobre os loggers das outras threads;
- Linhas 9–12: Armazenamos o logger no dicionário [config] da aplicação sob uma chave com o nome da thread. Assim, se houver n threads a executar-se simultaneamente, serão criadas n entradas no dicionário [config]. [config] é um recurso partilhado entre todas as threads. Por conseguinte, poderá ser necessária sincronização. Fiz aqui uma suposição. Presumi que, se duas threads criassem simultaneamente as suas entradas no ficheiro [config] e uma delas fosse interrompida pela outra, isso não teria qualquer impacto. A thread interrompida poderia, posteriormente, concluir a criação da entrada. Se os testes demonstrassem que esta suposição é falsa, o acesso à linha 12 teria de ser sincronizado;
- linha 10: colocamos o logger num dicionário;
- linha 11: [threading.current_thread()] é a thread que está a executar esta linha e, portanto, a thread que está a executar a função [index]. Registamos o seu nome. Cada thread tem um nome único;
- linha 12: armazenamos a configuração da thread. A partir de agora, procederemos sempre da seguinte forma: se houver informações que não possam ser partilhadas entre threads, estas serão colocadas na configuração geral, mas associadas ao nome da thread;
- linha 14: registamos o pedido que estamos a executar atualmente;
- linhas 15–24: colocamos aleatoriamente em pausa certas threads para que cedam o processador a outra thread;
- linha 16: recuperamos a duração da pausa (em segundos) da configuração;
- linha 17: ocorre uma pausa apenas se a duração da pausa não for 0;
- linha 19: um número inteiro aleatório no intervalo [0, 1]. Portanto, apenas os valores 0 e 1 são possíveis;
- linha 20: o thread é pausado apenas se o número aleatório for 1;
- linha 22: registamos o facto de que o thread está prestes a ser pausado;
- linha 24: o thread é pausado por [sleep_time] segundos;
- linha 26: quando o thread acorda, o módulo [index_controller] executa a solicitação;
- linhas 28–32: se esta execução causar um [500 INTERNAL SERVER ERROR], é enviado um e-mail ao administrador;
- linhas 30-31: configuramos o dicionário [config_mail] que iremos passar para a classe [SendAdminMail];
- linha 32: a mensagem enviada ao administrador é a string JSON do resultado que será enviado ao cliente;
- linhas 33–34: registamos a resposta que será enviada ao cliente (linha 36);
- linhas 37–44: tratamos quaisquer exceções;
- linhas 39–40: se o logger existir, registamos o erro que ocorreu;
- linhas 47–48: fechamos o logger, caso exista. Em última análise, o thread cria um logger no início da solicitação e o fecha assim que a solicitação for processada;
24.3.3. O controlador [index_controller]
O controlador [index_controller] que executa as solicitações é o da versão anterior:

24.3.4. Execução
Iniciamos o servidor Flask, o servidor de e-mail |hMailServer| e o cliente de e-mail |Thunderbird|. Não iniciamos o SGBD. O servidor pára com os seguintes registos de consola:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/02/flask/main.py mysql
[serveur] démarrage du serveur
L'erreur suivante s'est produite : MyException[27, (mysql.connector.errors.InterfaceError) 2003: Can't connect to MySQL server on 'localhost:3306' (10061 Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée)
(Background on this error at: http://sqlalche.me/e/13/rvf5)]
Process finished with exit code 2
O ficheiro de registo [logs.txt] é o seguinte:
2020-07-23 11:51:38.324752, MainThread : [serveur] démarrage du serveur
2020-07-23 11:51:40.355510, MainThread : L'erreur suivante s'est produite : MyException[27, (mysql.connector.errors.InterfaceError) 2003: Can't connect to MySQL server on 'localhost:3306' (10061 Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée)
(Background on this error at: http://sqlalche.me/e/13/rvf5)]
2020-07-23 11:51:42.464206, MainThread : [SendAdminMail] Message envoyé à [guest@localhost.com] : [L'erreur suivante s'est produite : MyException[27, (mysql.connector.errors.InterfaceError) 2003: Can't connect to MySQL server on 'localhost:3306' (10061 Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée)
(Background on this error at: http://sqlalche.me/e/13/rvf5)]]
Utilizando o Thunderbird, verifique os e-mails do administrador [guest@localhost.com]:

Em seguida, inicie o DBMS e solicite a URL [http://127.0.0.1:5000/?mari%C3%A9=oui&enfants=3&salaire=200000]. Os registos ficam da seguinte forma:
2020-07-23 11:56:38.891753, MainThread : [serveur] démarrage du serveur
2020-07-23 11:56:38.987999, MainThread : [serveur] connexion à la base de données réussie
2020-07-23 11:56:40.586747, MainThread : [serveur] démarrage du serveur
2020-07-23 11:56:40.655254, MainThread : [serveur] connexion à la base de données réussie
2020-07-23 11:56:54.528360, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=3&salaire=200000' [GET]>
2020-07-23 11:56:54.530653, Thread-2 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
- linhas 1-4: note que o servidor inicia duas vezes porque o modo [Debug=True] desencadeia um segundo arranque;
- linhas 5-6: os registos dão-nos uma ideia do tempo de execução de um pedido, neste caso 2,293 milissegundos;
24.4. O cliente web

O diretório [http-clients/02] é criado através da cópia do diretório [http-clients/01]. Em seguida, fazemos algumas modificações.
24.4.1. A configuração
A configuração [config] da aplicação [http-clients/02] é idêntica à da aplicação [http-clients/01], com algumas pequenas diferenças:
- linhas 31-32: vamos usar o mesmo logger |Logger| que o utilizado para o servidor;
- linha 49: o caminho absoluto para o ficheiro de registo;
- linha 60: o modo [debug=True] é utilizado para registar as respostas do servidor web no ficheiro de registo;
24.4.2. A camada [dao]
O código da classe [ImpôtsDaoWithHttpClient] sofre uma ligeira alteração:
- Linha 17: Armazenamos a configuração geral. Veremos mais tarde que, quando o construtor da classe [ImpôtsDaoWithHttpClient] é executado, o dicionário [config] ainda não contém a chave [logger] utilizada na linha 37. É por isso que não podemos inicializar [self.__logger] (linha 23) no construtor;
- linha 21: adicionámos uma chave [debug] à configuração que controla o registo nas linhas 33–39;
- linha 34: se estivermos no modo [debug];
- linhas 36–37: inicialização opcional da propriedade [self.__logger]. Quando o método [calculate_tax] é utilizado, a chave [logger] faz parte do dicionário [config];
- linha 39: registamos o documento de texto associado à resposta HTTP do servidor;
A camada [dao] será executada simultaneamente por múltiplas threads. No entanto, aqui criamos uma única instância desta camada (ver config_layers). Devemos, portanto, verificar se o código não envolve acesso de escrita a dados partilhados, tipicamente as propriedades da classe [ImpôtsDaoWithHttpClient] que implementa a camada [dao]. No entanto, no código acima, a linha 37 modifica uma propriedade da instância da classe. Aqui, isto não tem consequências porque todas as threads partilham o mesmo logger. Se não fosse este o caso, o acesso à linha 37 teria de ser sincronizado.
24.4.3. O script principal
O script principal [main] desenvolve-se da seguinte forma:
- O script principal difere do do cliente anterior na medida em que irá gerar múltiplas threads de execução para enviar pedidos ao servidor. O cliente na versão 6 enviava todos os seus pedidos sequencialmente. O pedido #i só era feito depois de recebida a resposta ao pedido #[i-1]. Aqui, queremos ver como o servidor se comporta quando recebe múltiplos pedidos simultâneos. Para isso, precisamos de threads;
- linha 21: as threads geradas serão colocadas numa lista. É importante compreender que o script [main] também é executado por uma thread chamada [MainThread]. Esta thread principal irá criar outras threads que serão responsáveis pelo cálculo do imposto para um ou mais contribuintes;
- linha 26: criamos um logger. Este será partilhado por todas as threads;
- linha 32: recuperamos todos os contribuintes cujos impostos precisam de ser calculados;
- linhas 39–51: distribuímos estes contribuintes por várias threads;
- linhas 40–41: cada thread processará 1 a 4 contribuintes. Este número é determinado aleatoriamente;
- [random.randint(1, 4)] gera aleatoriamente um número da lista [1, 2, 3, 4];
- o segmento não pode ter mais do que [l-i] contribuintes, em que [l-i] representa o número de contribuintes a quem ainda não foi atribuído um segmento;
- por isso, tomamos o mínimo dos dois valores;
- linha 43: uma vez conhecido [nb_taxpayers], o número de contribuintes processados pelo segmento, retiramos estes da lista de contribuintes:
- [slice(10,12)] é o conjunto de índices [10, 11, 12];
- [taxpayers[slice(10,12)]] é a lista [taxpayers[10], taxpayers[11], taxpayers[12] ;
- linha 45: incrementamos o valor de i, que controla o ciclo na linha 39;
- linha 47: criamos uma thread:
- [target=thread_function] define a função que o thread irá executar. Esta é a função das linhas 16–17. Ela espera três parâmetros;
- [ags] é a lista dos três parâmetros esperados pela função [thread_function];
Criar um thread não o executa. Simplesmente cria um objeto;
- Linhas 48–49: A thread que acabou de ser criada é adicionada à lista de threads criadas pela thread principal;
- linha 51: a thread é iniciada. Ela será então executada em paralelo com as outras threads ativas. Aqui, ela executará a [thread_function] com os argumentos que lhe foram fornecidos;
- linhas 53–54: a thread principal aguarda cada uma das threads que lançou. Vejamos um exemplo:
- a thread principal lançou três threads [th1, th2, th3];
- A thread principal aguarda cada uma das threads (linhas 53–54) na ordem do ciclo for: [th1, th2, th3];
- Suponha que as threads terminem na ordem [th2, th1, th3];
- A thread principal aguarda que th1 termine. Quando th2 termina, nada acontece;
- Quando th1 termina, o segmento principal aguarda th2. No entanto, th2 já terminou. O segmento principal passa então para o segmento seguinte e aguarda th3;
- quando th3 termina, a thread principal termina a espera e prossegue para a execução da linha 57;
- a linha 57 grava os resultados no ficheiro de resultados. Este é um bom exemplo de referências a objetos:
- linha 43: a lista [thread_payers] associada a um thread contém cópias das referências de objeto contidas na lista [taxpayers];
- sabemos que o cálculo do imposto irá modificar os objetos apontados pelas referências na lista [thread_payers]. Estes objetos serão atualizados com os resultados do cálculo do imposto. No entanto, as próprias referências não são modificadas. Portanto, as referências na lista inicial [taxpayers] «vêem» ou «apontam» para os objetos modificados;
A função [thread_function] executada pelos threads é a seguinte:
- As funções executadas simultaneamente por múltiplas threads são frequentemente difíceis de escrever: deve verificar sempre que o código não tenta modificar dados partilhados entre threads. Quando isto ocorre, deve implementar o acesso sincronizado aos dados partilhados que estão prestes a ser modificados;
- Linha 3: A função recebe três parâmetros:
- [dao]: uma referência à camada [dao]. Estes dados são partilhados;
- [logger]: uma referência ao logger. Estes dados são partilhados;
- [taxpayers]: uma lista de contribuintes. Estes dados não são partilhados: cada thread gere uma lista diferente;
- Vamos analisar as duas referências [dao, logger]:
- vimos que o objeto apontado pela referência [dao] tinha uma referência [self.__logger] que foi modificada pelas threads, mas para definir um valor comum a todas as threads;
- a referência [logger] aponta para um descritor de ficheiro. Vimos que poderia haver um problema ao escrever registos no ficheiro. Por esta razão, a escrita no ficheiro foi sincronizada;
- linhas 5–6: registamos o nome da thread e o número de contribuintes que esta deve gerir;
- linhas 8–14: cálculo dos impostos dos contribuintes;
- linha 16: registamos o fim da thread;
24.4.4. Execução
Vamos iniciar o servidor web conforme descrito na secção anterior (servidor web, SGBD, hMailServer, Thunderbird) e, em seguida, executar o script [main] do cliente. Nos ficheiros [data/output/errors.txt, data/output/results.json], obtemos os mesmos resultados que na versão anterior. No ficheiro [data/logs/logs.txt], temos os seguintes registos:
2020-07-24 10:05:20.942404, Thread-1 : début du thread [Thread-1] avec 1 contribuable(s)
2020-07-24 10:05:20.943458, Thread-1 : début du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555}
2020-07-24 10:05:20.943458, Thread-2 : début du thread [Thread-2] avec 3 contribuable(s)
2020-07-24 10:05:20.946502, Thread-3 : début du thread [Thread-3] avec 1 contribuable(s)
2020-07-24 10:05:20.946502, Thread-2 : début du calcul de l'impôt de {"id": 2, "marié": "oui", "enfants": 2, "salaire": 50000}
2020-07-24 10:05:20.947003, Thread-3 : début du calcul de l'impôt de {"id": 5, "marié": "non", "enfants": 3, "salaire": 100000}
2020-07-24 10:05:20.947003, Thread-4 : début du thread [Thread-4] avec 3 contribuable(s)
2020-07-24 10:05:20.950324, Thread-4 : début du calcul de l'impôt de {"id": 6, "marié": "oui", "enfants": 3, "salaire": 100000}
2020-07-24 10:05:20.948449, Thread-5 : début du thread [Thread-5] avec 3 contribuable(s)
2020-07-24 10:05:20.953645, Thread-5 : début du calcul de l'impôt de {"id": 9, "marié": "oui", "enfants": 2, "salaire": 30000}
2020-07-24 10:05:20.976143, Thread-1 : {"réponse": {"result": {"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:20.976695, Thread-1 : fin du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}
2020-07-24 10:05:20.976695, Thread-1 : fin du thread [Thread-1]
2020-07-24 10:05:21.973914, Thread-2 : {"réponse": {"result": {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}}}
2020-07-24 10:05:21.973914, Thread-2 : fin du calcul de l'impôt de {"id": 2, "marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}
2020-07-24 10:05:21.973914, Thread-2 : début du calcul de l'impôt de {"id": 3, "marié": "oui", "enfants": 3, "salaire": 50000}
2020-07-24 10:05:21.977130, Thread-4 : {"réponse": {"result": {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:21.977130, Thread-4 : fin du calcul de l'impôt de {"id": 6, "marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}
2020-07-24 10:05:21.977130, Thread-4 : début du calcul de l'impôt de {"id": 7, "marié": "oui", "enfants": 5, "salaire": 100000}
2020-07-24 10:05:21.982634, Thread-3 : {"réponse": {"result": {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:21.982634, Thread-5 : {"réponse": {"result": {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:21.983134, Thread-3 : fin du calcul de l'impôt de {"id": 5, "marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-24 10:05:21.983134, Thread-5 : fin du calcul de l'impôt de {"id": 9, "marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}
2020-07-24 10:05:21.983134, Thread-3 : fin du thread [Thread-3]
2020-07-24 10:05:21.983763, Thread-5 : début du calcul de l'impôt de {"id": 10, "marié": "non", "enfants": 0, "salaire": 200000}
2020-07-24 10:05:22.008562, Thread-5 : {"réponse": {"result": {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:22.008562, Thread-5 : fin du calcul de l'impôt de {"id": 10, "marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}
2020-07-24 10:05:22.009062, Thread-5 : début du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000}
2020-07-24 10:05:22.016848, Thread-5 : {"réponse": {"result": {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:22.017349, Thread-5 : fin du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-24 10:05:22.017349, Thread-5 : fin du thread [Thread-5]
2020-07-24 10:05:23.008486, Thread-2 : {"réponse": {"result": {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}}}
2020-07-24 10:05:23.008486, Thread-2 : fin du calcul de l'impôt de {"id": 3, "marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}
2020-07-24 10:05:23.009749, Thread-2 : début du calcul de l'impôt de {"id": 4, "marié": "non", "enfants": 2, "salaire": 100000}
2020-07-24 10:05:23.011722, Thread-4 : {"réponse": {"result": {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:23.013723, Thread-4 : fin du calcul de l'impôt de {"id": 7, "marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}
2020-07-24 10:05:23.013723, Thread-4 : début du calcul de l'impôt de {"id": 8, "marié": "non", "enfants": 0, "salaire": 100000}
2020-07-24 10:05:23.024135, Thread-2 : {"réponse": {"result": {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:23.024135, Thread-2 : fin du calcul de l'impôt de {"id": 4, "marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-24 10:05:23.025178, Thread-2 : fin du thread [Thread-2]
2020-07-24 10:05:23.025178, Thread-4 : {"réponse": {"result": {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-24 10:05:23.026191, Thread-4 : fin du calcul de l'impôt de {"id": 8, "marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-24 10:05:23.026191, Thread-4 : fin du thread [Thread-4]
- Estes registos mostram que foram lançadas cinco threads para calcular os impostos de 11 contribuintes. Estas cinco threads enviaram pedidos simultâneos ao servidor de cálculo de impostos. É importante compreender como isto funciona:
- O thread [Thread-1] é iniciado primeiro. Quando tem a CPU, executa o código até enviar o seu pedido HTTP. Como tem de aguardar o resultado deste pedido, é automaticamente colocado em espera. Em seguida, perde a CPU e outro thread assume o controlo;
- linhas 1–10: o mesmo processo repete-se para cada uma das 5 threads. Assim, as 5 threads são lançadas antes mesmo de a thread [Thread-1] ter recebido a sua resposta na linha 11;
- As threads não terminam na ordem em que foram lançadas. Assim, a thread [Thread-3] termina primeiro, linha 23;
No lado do servidor, os registos no ficheiro [data/logs/logs.txt] são os seguintes:
2020-07-24 10:05:01.692980, MainThread : [serveur] démarrage du serveur
2020-07-24 10:05:01.877251, MainThread : [serveur] connexion à la base de données réussie
2020-07-24 10:05:03.596162, MainThread : [serveur] démarrage du serveur
2020-07-24 10:05:03.661160, MainThread : [serveur] connexion à la base de données réussie
2020-07-24 10:05:20.968053, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=2&salaire=50000' [GET]>
2020-07-24 10:05:20.969132, Thread-2 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:20.970316, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=3&salaire=100000' [GET]>
2020-07-24 10:05:20.970316, Thread-3 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:20.971335, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=2&salaire=55555' [GET]>
2020-07-24 10:05:20.972563, Thread-4 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:20.974796, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=non&enfants=3&salaire=100000' [GET]>
2020-07-24 10:05:20.974796, Thread-5 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:20.976143, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=2&salaire=30000' [GET]>
2020-07-24 10:05:20.976143, Thread-6 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:21.970615, Thread-2 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'taux': 0.14, 'décôte': 384, 'réduction': 347}}}
2020-07-24 10:05:21.973914, Thread-3 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'taux': 0.3, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:21.977130, Thread-6 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'taux': 0.0, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:21.977130, Thread-5 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:22.001693, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=3&salaire=50000' [GET]>
2020-07-24 10:05:22.003013, Thread-7 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:22.003013, Thread-8 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=5&salaire=100000' [GET]>
2020-07-24 10:05:22.003013, Thread-8 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:22.005871, Thread-9 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=non&enfants=0&salaire=200000' [GET]>
2020-07-24 10:05:22.006370, Thread-9 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'taux': 0.45, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:22.014170, Thread-10 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=3&salaire=200000' [GET]>
2020-07-24 10:05:22.014170, Thread-10 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:23.003533, Thread-7 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'taux': 0.14, 'décôte': 720, 'réduction': 0}}}
2020-07-24 10:05:23.006434, Thread-8 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:23.018026, Thread-11 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=non&enfants=2&salaire=100000' [GET]>
2020-07-24 10:05:23.019074, Thread-11 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-24 10:05:23.021447, Thread-12 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=non&enfants=0&salaire=100000' [GET]>
2020-07-24 10:05:23.022447, Thread-12 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
- Podemos ver que 11 threads processaram os 11 contribuintes;
- algumas threads foram colocadas em espera (linhas 6, 8, 12, 14, 20, 22) e outras não (linhas 9, 23, 25, 29, 31);
24.5. Testes da camada [DAO]
Tal como fizemos na |versão anterior|, estamos a testar a camada [DAO] do cliente. O princípio é exatamente o mesmo:

A classe de teste será executada no seguinte ambiente:

- A configuração [2] é idêntica à configuração [1], que acabámos de examinar;
A classe de teste [TestHttpClientDao] é a seguinte:
- Criamos uma |configuração de execução| para este teste;
- Iniciamos o servidor web com todo o seu ambiente;
- executamos o teste;
Os resultados são os seguintes:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-clients/02/tests/TestHttpClientDao.py
tests en cours...
...........
----------------------------------------------------------------------
Ran 11 tests in 6.128s
OK
Process finished with exit code 0