30. Exercício prático: Versão 12
Neste capítulo, iremos escrever uma aplicação web seguindo a arquitetura MVC (Model-View-Controller). A aplicação será capaz de devolver respostas em três formatos: JSON, XML e HTML. Existe um aumento significativo na complexidade entre o que estamos prestes a fazer e o que fizemos anteriormente. Iremos reutilizar a maioria dos conceitos abordados até agora e detalharemos todos os passos que conduzem à aplicação final.
30.1. Arquitetura MVC
Iremos implementar o padrão arquitetónico MVC (Model–View–Controller) da seguinte forma:

O processamento de um pedido do cliente decorrerá da seguinte forma:
- 1 - Pedido
Os URLs solicitados terão o formato http://machine:port/action/param1/param2/… O [Controlador Principal] utilizará um ficheiro de configuração para «encaminhar» a solicitação para o controlador correto. Para tal, utilizará o campo [ação] do URL. O restante do URL [param1/param2/…] consiste em parâmetros opcionais que serão passados para a ação. O «C» em MVC refere-se aqui à cadeia [Controlador Principal, Controlador / Ação]. Se nenhum controlador puder lidar com a ação solicitada, o servidor web responderá que a URL solicitada não foi encontrada.
- 2 - Processamento
- A ação selecionada [2a] pode utilizar os parâmetros que o [Controlador Principal] lhe passou. Estes podem provir de duas fontes:
- o caminho [/param1/param2/…] da URL,
- dos parâmetros enviados no corpo do pedido do cliente;
- Ao processar a solicitação do utilizador, a ação pode requerer a camada [de negócios] [2b]. Uma vez processada a solicitação do cliente, ela pode desencadear várias respostas. Um exemplo clássico é:
- uma resposta de erro, se a solicitação não puder ser processada corretamente;
- uma resposta de confirmação, caso contrário;
- o [Controlador / Ação] devolverá a sua resposta [2c] ao controlador principal juntamente com um código de estado. Estes códigos de estado representarão de forma única o estado atual da aplicação. Será um código de sucesso ou um código de erro;
- 3 - Resposta
- Dependendo de o cliente ter solicitado uma resposta JSON, XML ou HTML, o [Controlador Principal] instanciará [3a] o tipo de resposta apropriado e instruirá este a enviar a resposta ao cliente. O [Controlador Principal] passará tanto a resposta como o código de estado fornecido pelo [Controlador/Ação] que foi executado;
- se a resposta pretendida for do tipo JSON ou XML, a resposta selecionada formatará a resposta do [Controlador/Ação] que lhe foi fornecida e enviá-la-á [3c]. O cliente capaz de processar esta resposta pode ser um script de consola Python ou um script JavaScript incorporado numa página HTML;
- se a resposta desejada for do tipo HTML, a resposta selecionada irá selecionar [3b] uma das vistas HTML [Vuei] utilizando o código de estado que lhe foi fornecido. Este é o V em MVC. Uma única vista corresponde a um único código de estado. Esta vista V irá apresentar a resposta do [Controlador / Ação] que foi executada. Ela envolve os dados desta resposta em HTML, CSS e JavaScript. Estes dados são chamados de modelo de vista. Este é o M em MVC. O cliente é, na maioria das vezes, um navegador;
Agora, vamos esclarecer a relação entre a arquitetura web MVC e a arquitetura em camadas. Dependendo de como o modelo é definido, estes dois conceitos podem ou não estar relacionados. Consideremos uma aplicação web MVC de camada única:

No exemplo acima, o [Controlador / Ação] incorpora partes das camadas [negócio] e [DAO]. Na camada [web], temos uma arquitetura MVC, mas a aplicação como um todo não possui uma arquitetura em camadas. Aqui, existe apenas uma camada — a camada web — que lida com tudo.
Agora, vamos considerar uma arquitetura web multicamadas:

A camada [Web] pode ser implementada sem seguir o modelo MVC. Temos então uma arquitetura multicamadas, mas a camada Web não implementa o modelo MVC.
Por exemplo, no mundo .NET, a camada [Web] acima pode ser implementada com ASP.NET MVC, resultando numa arquitetura em camadas com uma camada [Web] no estilo MVC. Feito isso, podemos substituir essa camada ASP.NET MVC por uma camada ASP.NET clássica (WebForms), mantendo o resto (lógica de negócio, DAO, driver) inalterado. Temos então uma arquitetura em camadas com uma camada [web] que já não é baseada em MVC.
No MVC, dissemos que o modelo M era o da vista V, ou seja, o conjunto de dados exibidos pela vista V. É dada outra definição do modelo M no MVC:

Muitos autores consideram que o que se encontra à direita da camada [web] constitui o modelo M do MVC. Para evitar ambiguidades, podemos referir-nos a:
- o modelo de domínio quando nos referimos a tudo à direita da camada [web];
- o modelo de vista quando nos referimos aos dados apresentados por uma vista V;
No que se segue, quando nos referirmos ao modelo, estaremos sempre a referir-nos ao modelo de visualização.
30.2. Arquitetura de Aplicações Cliente/Servidor
A aplicação web terá a seguinte arquitetura:

- Em [1], o servidor web terá dois tipos de clientes:
- em [2], um cliente de consola que irá trocar JSON e XML com o servidor;
- em [3], um navegador que receberá HTML do servidor e o exibirá;
- O servidor web [1] mantém as camadas [business] e [DAO] das versões anteriores;
- o cliente web [2] será atualizado para ter em conta os novos URLs de serviço da aplicação web;
- A aplicação HTML apresentada pelo navegador deve ser escrita de raiz;
Iremos desenvolver a aplicação em várias fases:
- Iremos desenvolver a versão JSON do servidor. Iremos testar as URLs de serviço do servidor uma a uma utilizando um cliente Postman. Este método permite-nos construir a estrutura do servidor web sem nos preocuparmos com as vistas da aplicação (=HTML);
- Após testar o servidor JSON com o Postman, iremos testá-lo com um cliente de consola;
- depois, passaremos à versão XML do servidor. Vimos que a transição de JSON para XML é simples;
- por fim, passaremos para a versão HTML do servidor. Iremos construir uma arquitetura MVC e definir as vistas a serem apresentadas. A aplicação HTML será testada utilizando tanto o cliente Postman como um navegador padrão;
30.3. A estrutura de diretórios do código do servidor

- em [1: o servidor web como um todo;
- em [2]: por enquanto, ignoraremos as pastas [static, templates, tests_views], que pertencem à versão HTML do servidor. Fora desta pasta, encontraremos o script principal [main] e a sua configuração;
- em [3], os controladores do servidor web. Estes serão instâncias de classe;
![]() | ![]() |
- em [4], a resposta HTTP do servidor será tratada por classes;
- em [5], mantemos o ficheiro de registo dos servidores anteriores;
Quando criamos a versão HTML do servidor, outras pastas entram em ação:
![]() | ![]() |
- em [6], os elementos estáticos da aplicação HTML;
- em [7], os modelos da aplicação HTML divididos em vistas [9] e fragmentos de vista [8];
- em [9], as classes que implementam os modelos de vista;
30.4. Os URLs de serviço da aplicação
Para construir o servidor web, procederemos da seguinte forma:
- Com base nas vistas da aplicação HTML, definiremos as ações que a aplicação web deve implementar. Utilizaremos aqui as vistas reais, mas estas poderiam ser simplesmente vistas no papel;
- Com base nessas ações, definiremos as URLs de serviço da aplicação HTML;
- vamos implementar estas URLs de serviço utilizando um servidor que devolve JSON. Isto permite-nos definir a estrutura do servidor web sem nos preocuparmos com as páginas HTML a serem servidas. Vamos testar estas URLs de serviço utilizando o Postman;
- Em seguida, testaremos o nosso servidor JSON com um cliente de consola;
- Assim que o servidor JSON tiver sido validado, passaremos à escrita da aplicação HTML;
A primeira vista será a vista de autenticação:

- a ação que conduz a esta primeira vista será denominada [init-session] [1];
- Clicar no botão [Validate] irá acionar a ação [authenticate-user] com dois parâmetros enviados [2-3];
A vista de cálculo de impostos:

- Em [1], a ação [authenticate-user] que conduziu a esta vista;
- em [2], clicar no botão [Validate] aciona a execução da ação [calculate-tax] com três parâmetros enviados [2-5];
- Clicar na ligação [6] aciona a ação [list-simulations] sem parâmetros;
- Clicar no link [7] aciona a ação [end-session] sem parâmetros;
A terceira vista apresenta as simulações realizadas pelo utilizador autenticado:

- em [3], a ação [list-simulations] que conduziu a esta visualização;
- em [2], clicar na ligação [Eliminar] aciona a ação [delete-simulation] com um parâmetro: o número da simulação a ser eliminada da lista;
- clicar no link [3] aciona a ação [display-tax-calculation] sem parâmetros, o que volta a exibir a vista de cálculo de impostos;
- Clicar no link [4] aciona a ação [end-session] sem parâmetros;
Com esta informação inicial, podemos definir os vários URLs de serviço do servidor:
Ação | Função | Contexto de execução |
/init-session | Utilizado para definir o tipo (json, xml, html) das respostas pretendidas | Pedido GET Pode ser enviada a qualquer momento |
/authenticate-user | Autoriza ou recusa o login de um utilizador | Pedido POST. A solicitação deve conter dois parâmetros enviados [user, password] Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido |
/calculate-tax | Realiza uma simulação de cálculo de impostos | Pedido POST. A solicitação deve conter três parâmetros enviados [casado, filhos, salário] Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/list-simulations | Pedido para visualizar a lista de simulações realizadas desde o início da sessão | Pedido GET. Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/delete-simulation/number | Elimina uma simulação da lista de simulações | Pedido GET. Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/display-tax-calculation | Exibe a página HTML para o cálculo de impostos | Pedido GET. Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/end-session | Encerra a sessão de simulação. | Tecnicamente, a sessão web antiga é eliminada e é criada uma nova sessão Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
Estas várias URLs de serviço serão utilizadas tanto para o servidor HTML como para os servidores JSON ou XML. Duas URLs serão utilizadas exclusivamente para os dois últimos servidores: estas são as URLs da versão anterior do cliente/servidor web que estamos a reutilizar aqui:
Ação | Função | Contexto de execução |
/get-admindata | Retorna os dados fiscais utilizados para calcular o imposto | . Utilizado apenas se o tipo de sessão for json ou xml. O utilizador deve estar autenticado |
/calculate-taxes | Calcula o imposto para uma lista de contribuintes enviada na solicitação GET em JSON | . Utilizado apenas se o tipo de sessão for json ou xml. O utilizador deve estar autenticado |
Todos os controladores associados a estas ações procederão da mesma forma:
- eles verificarão os seus parâmetros. Estes encontram-se no objeto:
- [request.path] para parâmetros presentes na URL na forma [/action/param1/param2/…];
- no objeto [request.form] para os que são transmitidos como [x-www-form-urlencoded] no corpo da solicitação;
- no objeto [request.data] para os dados transmitidos como JSON no corpo da solicitação;
- Um controlador é semelhante a uma função ou método que verifica a validade dos seus parâmetros. No caso do controlador, porém, é um pouco mais complicado:
- os parâmetros esperados podem estar em falta;
- Os parâmetros recuperados pelo controlador são cadeias de caracteres. Se o parâmetro esperado for um número, então o controlador deve verificar se a cadeia de caracteres do parâmetro representa efetivamente um número;
- Uma vez verificado que os parâmetros esperados estão presentes e são sintaticamente corretos, deve verificar-se se são válidos no contexto de execução atual. Este contexto está presente na sessão. O exemplo de autenticação é um exemplo de um contexto de execução. Certas ações só devem ser processadas depois de o cliente ter sido autenticado. Geralmente, uma chave na sessão indica se esta autenticação ocorreu ou não;
- assim que as verificações anteriores tenham sido concluídas, o controlador secundário pode prosseguir. Este processo de verificação de parâmetros é muito importante. Não podemos aceitar que um cliente nos envie dados arbitrários em qualquer momento durante o ciclo de vida da aplicação. Temos de manter controlo total sobre o ciclo de vida da aplicação;
- Assim que o seu trabalho estiver concluído, o controlador secundário devolve um dicionário com as chaves [action, state, response] ao controlador principal que o chamou:
- [action] é a ação que acabou de ser executada;
- [state] é um número de três dígitos que indica o resultado do processamento da ação:
- [x00] indica processamento bem-sucedido;
- [x01] indica uma falha no processamento;
- [response] é o dicionário de resultados na forma {‘response’:object}. O objeto terá estruturas diferentes dependendo da ação que está a ser processada;
Vamos agora rever os vários controladores — ou, por outras palavras, as diferentes ações que estes controladores tratam — que conduzem o fluxo de trabalho da aplicação web.
30.5. Configuração do servidor

A configuração da base de dados [config_database] e a configuração da camada do servidor [config_layers] são idênticas às das versões anteriores. O ficheiro [config] inclui agora novas informações:
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
- Até à linha 41, vemos elementos padrão;
- linhas 43–66: na linha 43, o Python Path do servidor é definido. Podemos então importar as dependências do projeto:
- linhas 45–55: a lista de controladores;
- linhas 57–60: a lista de respostas HTTP;
- linhas 62–66: a lista de modelos de visualização;
- linhas 68–189: a configuração da aplicação com uma série de constantes;
- linhas 71–98: já estamos familiarizados com estas linhas das versões anteriores;
- linhas 101–122: o dicionário de controladores:
- as chaves são os nomes das ações;
- os valores são uma instância do controlador responsável por lidar com essa ação. Cada controlador é instanciado como uma única instância (singleton). A mesma instância será executada por diferentes threads do servidor. Portanto, é necessário ter cuidado com os dados partilhados que cada controlador possa querer modificar;
- linhas 125–129: o dicionário das três respostas HTTP possíveis:
- as chaves são o tipo de resposta solicitada pelo cliente (JSON, XML, HTML);
- os valores são uma instância da resposta HTTP. Cada gerador de resposta é instanciado como uma única instância (singleton). O mesmo gerador será executado por diferentes threads do servidor. Por isso, é necessário ter cuidado com os dados partilhados que cada gerador possa querer modificar;
- linhas 132–186: configuração das visualizações HTML. Por enquanto, vamos ignorar estas linhas;
- linhas 191–202: já nos deparámos com estas linhas em versões anteriores;
30.6. O caminho de um pedido do cliente dentro do servidor

Iremos acompanhar o percurso de um pedido do cliente que chega ao servidor até à resposta HTTP enviada de volta. Segue o fluxo do servidor MVC.
30.6.1. O script [main]
O script [main] é idêntico em muitos aspetos ao das versões anteriores. No entanto, apresentamo-lo na íntegra para garantir que começamos com o pé direito:
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | |
- linhas 1–92: todas estas linhas já foram abordadas e explicadas;
- linha 92: o servidor irá gerir uma sessão. Por isso, precisamos de uma chave secreta. Para cada utilizador, iremos armazenar duas informações na sessão:
- se o utilizador se autenticou com sucesso;
- Sempre que realizar um cálculo de impostos, os resultados desse cálculo serão colocados numa lista chamada lista de simulação do utilizador. Esta lista será armazenada na sessão;
- Linhas 100–151: a lista de URLs de serviço do servidor. As funções associadas atuam como um filtro: quaisquer URLs que não estejam presentes nesta lista serão rejeitadas pelo servidor Flask com um erro [404 NOT FOUND]. Assim que esta filtragem estiver concluída, o pedido é sistematicamente encaminhado para um «Front Controller» implementado pela função [front_controller] nas linhas 94–98, que iremos discutir em breve;
- Linhas 100–103: tratamento da rota [/]. O ponto de entrada para a aplicação web será a URL na linha 107. Portanto, na linha 103, redirecionamos o cliente para esta URL:
- A função [url_for] é importada na linha 18. Aqui, ela tem dois parâmetros:
- o primeiro parâmetro é o nome de uma das funções de roteamento, neste caso a da linha 107. Podemos ver que esta função espera um parâmetro [type_response], que é o tipo de resposta (json, xml, html) solicitado pelo cliente;
- o segundo parâmetro recebe o nome do parâmetro da linha 107, [type_response], e atribui-lhe um valor. Se houvesse outros parâmetros, repetiríamos a operação para cada um deles;
- ela retorna a URL associada à função designada pelos dois parâmetros que lhe foram fornecidos. Aqui, isto irá retornar a URL da linha 106, onde o parâmetro é substituído pelo seu valor [/init-session/html];
- A função [redirect] foi importada na linha 18. A sua função é enviar um cabeçalho de redirecionamento HTTP ao cliente:
- o primeiro parâmetro é a URL para a qual o cliente deve ser redirecionado;
- o segundo parâmetro é o código de estado da resposta HTTP enviada ao cliente. O código [status.HTTP_302_FOUND] corresponde a um redirecionamento HTTP;
A função [ front_controller] nas linhas 94–98 realiza o processamento inicial do pedido do cliente:
- Linhas 1–57: Estamos familiarizados com este código. Por exemplo, este era o código da função denominada [main] no script [main] da versão anterior. Uma coisa a notar é o controlador utilizado nas linhas 25–26:
- linha 25: recuperamos a instância do controlador associada ao nome [main-controller] da configuração. Estas são as seguintes linhas:
- (continuação)
- na linha 10 acima, repare que estamos a recuperar uma instância da classe;
- linha 26: solicitamos ao controlador [MainController] que processe a solicitação;
- linhas 30–45: a resposta devolvida pelo [MainController] é enviada ao cliente. Voltaremos a estas linhas um pouco mais tarde;
A função da função [front_controller] e, em seguida, da classe [MainController] é tratar das tarefas comuns a todos os pedidos:
No diagrama acima, ainda estamos na fase 1 do processamento da solicitação. O controlador principal [MainController] continuará com a etapa 1.

30.6.2. O controlador principal [MainController]
O controlador principal [MainController] dá continuidade ao trabalho iniciado pela função [front_controller]:
Todos os controladores implementam a seguinte interface [InterfaceController] [2]:

from abc import ABC, abstractmethod
from werkzeug.local import LocalProxy
class InterfaceController(ABC):
@abstractmethod
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
pass
- A interface [InterfaceController] define apenas o método único [execute] na linha 8. Este método recebe três parâmetros:
- [request]: o pedido do cliente;
- [session]: a sessão do cliente;
- [config]: a configuração da aplicação;
O método [execute] retorna uma tupla de dois elementos:
- o primeiro é o dicionário de resultados na forma {‘action’: action, ‘status’: status, ‘response’: results};
- o segundo é o código de estado HTTP a devolver ao cliente;
O controlador principal [MainController] [1] implementa a interface [InterfaceController] da seguinte forma:
O [MainController] realiza as verificações iniciais para validar o pedido.
- linhas 11–13: O controlador começa por recuperar a ação solicitada pelo cliente. Recorde-se que os URLs dos serviços têm o formato [/action/param1/param2/…] e que este URL se encontra em [request.path];
- linhas 17–23: a ação [init-session] é utilizada para inicializar o tipo de resposta (json, xml, html) solicitado pelo cliente. Esta informação é armazenada na sessão sob a chave [responseType]. Portanto, se a ação não for [init-session], a sessão deve conter a chave [responseType]; caso contrário, a solicitação é inválida;
- linhas 21-22: a estrutura do resultado devolvido por cada controlador, neste caso um resultado de erro:
- [action]: é o nome da ação atual. Isto permitir-nos-á recuperar o seu nome ao registar o resultado da solicitação;
- [status]: é um código de estado de três dígitos:
- [x00] para sucesso;
- [x01] para uma falha;
- [response]: é a resposta à solicitação. A sua natureza é específica para cada solicitação;
- linhas 24–30: a ação [authenticate-user] é utilizada para autenticar o utilizador. Se for bem-sucedida, é adicionada uma chave [user=True] à sessão do utilizador. Certos URLs de serviço são acessíveis apenas a um utilizador autenticado. É isso que é verificado aqui;
- linha 26: apenas as ações [init-session] e [authenticate-user] podem ser executadas por um utilizador que ainda não tenha sido autenticado;
- linhas 28–29: a resposta a enviar em caso de erro;
- linhas 32–34: se ocorrer qualquer um dos dois erros anteriores, a resposta de erro é enviada ao cliente com o estado HTTP 400 BAD REQUEST;
- linhas 35–39: se não ocorreu nenhum erro, o controlo é passado para o controlador responsável por lidar com a ação atual. A sua instância encontra-se na configuração da aplicação;
A classe [MainController] dá continuidade ao trabalho da função [front_controller]: juntas, elas tratam de tudo o que pode ser separado do processamento da solicitação, esperando até o último momento para passar a solicitação a um controlador específico. A divisão do código entre a função [front_controller] e a classe [MainController] é inteiramente subjetiva. Aqui, quis preservar a estrutura da versão anterior: a função [front_controller] já existia com o nome [main]. Na prática, seria possível:
- colocar tudo na função [front_controller] e eliminar a classe [MainController];
- colocar tudo na classe [MainController] e eliminar a função [front_controller]. Eu tenderia a escolher esta solução porque tem a vantagem de simplificar o código do script principal [main];
30.7. Processamento específico da ação
Voltemos à arquitetura MVC da aplicação:

Ainda estamos na etapa 1 acima. Se não houver erros, a etapa 2 terá início. O pedido foi encaminhado para o controlador específico da ação solicitada pelo pedido. Vamos supor que essa ação seja [/init-session], definida pela rota:
Esta ação está ligada a um controlador na configuração [config]:
# actions autorisées et leurs contrôleurs
"controllers": {
# initialisation d'une session de calcul
"init-session": InitSessionController(),
…
},
O [InitSessionController] (linha 4) assume então o controlo. O seu código é o seguinte:
- linha 6: tal como os outros controladores, o [InitSessionController] implementa a interface [InterfaceController];
- linha 10: a URL é do tipo [/init-session/type_response]. Recuperamos a ação [init-session] e o tipo de resposta desejado;
- linha 15: o tipo de resposta desejado só pode ser um dos presentes na configuração de resposta:
# les différents types de réponse (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
- se não for esse o caso, é preparada uma resposta de erro 701 (linha 17);
- linhas 20–25: caso em que o tipo de resposta pretendido é válido;
- linha 22: o tipo de resposta desejado é armazenado na sessão. Isto porque precisaremos de o recordar para pedidos subsequentes;
- linhas 23–24: preparar uma resposta de sucesso 700;
- linha 25: a resposta de sucesso é devolvida ao chamador;
- linha 27: se ocorreu um erro, a resposta de erro é devolvida ao chamador;
30.8. Gerar a resposta HTTP do servidor
Voltemos à arquitetura MVC da aplicação:

Acabámos de abordar os passos 1 e 2. Encontrámos três códigos de estado:
- 700: /init-session bem-sucedida;
- 701: /init-session falhou;
- 101: pedido inválido, quer porque a sessão não foi inicializada, quer porque o utilizador não está autenticado;
Vamos examinar como a resposta do servidor será enviada ao cliente durante o passo 3 acima. Isto acontece na função [front_controller] do script [main]:
- Estamos agora na linha 26: o controlador principal devolveu a sua resposta de erro;
- linhas 27–29: independentemente da resposta do controlador principal (sucesso ou falha), esta resposta é registada no ficheiro de registo;
- linhas 30–33: tal como nas versões anteriores, se o estado HTTP for [500 INTERNAL SERVER ERROR], enviamos um e-mail ao administrador da aplicação com o registo de erros;
- linhas 34–39: enviamos a resposta HTTP, e o resultado devolvido pelo controlador é colocado no corpo desta resposta. Precisamos de saber em que formato (JSON, XML, HTML) o cliente deseja esta resposta. Procuramos o tipo de resposta desejado na sessão. Se não estiver lá, definimos arbitrariamente este tipo como JSON;
- linhas 40–43: a resposta HTTP é construída;
No ficheiro de configuração, cada tipo de resposta (json, xml, html) foi associado a uma instância de classe:
# les différents types de réponse (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
As classes de resposta estão localizadas na pasta [responses] da árvore de diretórios do servidor:

Cada classe de resposta implementa a seguinte interface [InterfaceResponse]:
from abc import ABC, abstractmethod
from flask.wrappers import Response
from werkzeug.local import LocalProxy
class InterfaceResponse(ABC):
@abstractmethod
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
pass
- linhas 8–11: a interface [InterfaceResponse] define um único método [build_http_response] com os seguintes parâmetros:
- [request, session, config]: estes são os parâmetros recebidos pelo controlador de ação;
- [result, status_code]: estes são os resultados produzidos pelo controlador de ação;
Vamos agora apresentar a resposta JSON. Esta é gerada pela seguinte classe [JsonResponse]:
Estamos familiarizados com este código, que já encontrámos muitas vezes. É o código da função [json_response] no módulo [myutils].
30.9. Testes iniciais
No código que analisámos, deparámo-nos com três códigos de estado:
- 700: /init-session bem-sucedida;
- 701: /init-session falhou;
- 101: pedido inválido, quer porque a sessão não foi inicializada, quer porque o utilizador não está autenticado;
Vamos tentar acionar estes erros com uma sessão JSON.
- Iniciamos o servidor web, o SGBD e o servidor de e-mail;
- Iniciamos um cliente Postman;
Teste 1
Primeiro, vamos demonstrar um pedido inválido, uma vez que a sessão ainda não foi inicializada:

- [1-2]: A solicitação [POSThttp://localhost:5000/authentifier-utilisateur] é uma rota válida:
# authenticate-user
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# execute the controller associated with the action
return front_controller()
mas só é aceite se a sessão tiver sido inicializada previamente com a ação [/init-session].
Vamos executar a solicitação e ver o resultado enviado pelo servidor:

- [1-2]: recebemos uma resposta JSON. Quando o tipo de resposta ainda não foi especificado pelo cliente, o servidor utiliza JSON para responder;
- [3-5]: o dicionário JSON da resposta;
- [action]: a ação que foi executada;
- [status]: o código de estado da resposta. Um código [x01] indica um erro;
- [response]: é adaptado a cada ação. Aqui, contém uma mensagem de erro;
Agora vamos iniciar uma sessão com um tipo de resposta incorreto:

- [1-2] é uma rota válida:
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# execute the controller associated with the action
return front_controller()
Entrará, portanto, no pipeline de processamento de pedidos do servidor MVC. No entanto, deverá ser rejeitado durante este processamento, uma vez que o tipo de sessão solicitado está incorreto.
A resposta é a seguinte:

- em [4], um código de erro [x01];
- em [5], a explicação do erro;
Agora, vamos inicializar uma sessão JSON:

A resposta é a seguinte:

Agora, vamos inicializar uma sessão XML. A resposta JSON será substituída por uma resposta XML gerada pela seguinte classe [XmlResponse]:
Este é um código com o qual estamos familiarizados — provém da função [xml_response] no módulo partilhado [myutils].
Inicializamos uma sessão XML:

A resposta do servidor é então a seguinte:

Recebemos a mesma resposta que em JSON, mas desta vez a resposta está formatada como XML.
30.10. A ação [authenticate-user]
A ação [authenticate-user] permite autenticar um utilizador que pretenda utilizar a aplicação de cálculo de impostos. A sua rota é definida da seguinte forma no script [main]:
O servidor espera dois parâmetros POST:
- [user]: o ID do utilizador;
- [password]: a sua palavra-passe;
A lista de utilizadores autorizados é definida na configuração [config]:
# utilisateurs autorisés à utiliser l'application
"users": [
{
"login": "admin",
"password": "admin"
}
],
Aqui, temos uma lista com um único elemento.
A ação [authenticate-user] é tratada pelo seguinte controlador [AuthentifierUtilisateurController]:
- linha 14: recuperar os parâmetros POST;
- linha 19: a lista de erros encontrados na solicitação;
- linhas 20–24: verificamos se existem efetivamente dois parâmetros enviados;
- linhas 27–31: verificar se existe um parâmetro [users];
- linhas 32–36: verificar a presença de um parâmetro [password];
- linhas 38–39: se os parâmetros enviados estiverem incorretos, prepare uma resposta HTTP 400 BAD REQUEST;
- linhas 40–58: verificar se as credenciais [user, password] pertencem a um utilizador autorizado a utilizar a aplicação;
- linhas 51–55: se o utilizador (user, password) não estiver autorizado a utilizar a aplicação, preparar uma resposta HTTP 401 UNAUTHORIZED;
- linhas 56–58: se o utilizador estiver autorizado, registamos na sessão, utilizando a chave [user], que ele se autenticou;
Note que se o utilizador foi autenticado com as credenciais [credenciais1] e não consegue autenticar-se com as credenciais [credenciais2], permanece autenticado com as credenciais [credenciais1].
Vamos executar alguns testes no Postman:
- Iniciamos o servidor web, o SGBD e o servidor de e-mail;
- Usando o cliente Postman:
- iniciamos uma sessão JSON;
- depois, autenticamos;
Aqui estão diferentes cenários.
Caso 1: POST sem parâmetros enviados

- Em [3-5], o POST não tem corpo;
O resultado da solicitação é o seguinte:

- Em [2], recebemos uma resposta HTTP 400 BAD REQUEST;
- Em [5], recebemos um código de erro [201];
Caso 2: POST com credenciais incorretas

- Em [6], as credenciais estão incorretas;
O servidor envia a seguinte resposta:

- em [2], a resposta HTTP 401 UNAUTHORIZED;
- Em [5], a resposta de erro;
Caso 2: POST com credenciais corretas

- Em [6], as credenciais estão corretas;
A resposta do servidor é a seguinte:
- em [2], uma resposta HTTP 200 OK;
- em [5], a resposta de sucesso;
30.11. A ação [calculate_tax]
A ação [calculate_tax] calcula o imposto de um contribuinte. A sua rota é definida da seguinte forma no script [main]:
O servidor espera três parâmetros POST:
- [married]: sim / não;
- [children]: número de filhos do contribuinte;
- [salário]: salário anual do contribuinte;
O controlador [CalculateTaxController] trata da ação [calculate_tax]:
- linha 13: recuperamos o nome da ação atual;
- linha 17: recolhemos os erros numa lista;
- linha 19: recuperamos os parâmetros enviados. Estes são enviados no formato [x-www-form-urlencoded], razão pela qual os recuperamos de [request.form]. Se tivessem sido enviados como JSON, teríamos de os recuperar de [request.data];
- linhas 21–24: verificamos se existem efetivamente três parâmetros enviados;
- linhas 27–36: verificamos a presença e a validade do parâmetro enviado [married];
- linhas 37–48: verificamos a presença e a validade do parâmetro enviado [children];
- linhas 49–60: verificamos a presença e a validade do parâmetro enviado [salary];
- linhas 62–66: se houver um erro, é enviada uma resposta de erro 400 BAD REQUEST com um código de estado [301];
- linhas 69–71: se não houve erro, preparar o cálculo do imposto. Para tal,
- linha 70: recuperar uma referência da camada [business];
- linha 71: recuperar dados da autoridade fiscal na configuração do servidor;
- linhas 72–74: o imposto do contribuinte é calculado;
- linhas 75–77: contamos o número de cálculos de imposto realizados pelo utilizador;
- linha 76: recuperar o número do último cálculo realizado da sessão. Aqui, referimo-nos ao resultado de um cálculo como [simulação];
- linha 77: o número da última simulação é incrementado;
- linha 78: este número é guardado na sessão;
- linhas 79–84: para acompanhar os cálculos realizados pelo utilizador, iremos armazenar a lista de simulações que realizou na sua sessão;
- linha 80: uma simulação será o dicionário de um objeto TaxPayer cuja propriedade [id] terá o valor do número da simulação;
- linhas 82–84: a simulação atual é adicionada à lista de simulações na sessão;
- linhas 86-87: preparamos uma resposta HTTP de sucesso;
- linha 90: devolvemos o resultado;
Vamos executar alguns testes: o servidor web, o SGBD, o servidor de e-mail e um cliente Postman são iniciados.
Caso 1: realizar um cálculo de impostos enquanto a sessão não está inicializada

A resposta é a seguinte:

Caso 2: realizar um cálculo de impostos sem estar autenticado
Primeiro, iniciamos uma sessão JSON com [/init-session/json]. Em seguida, fazemos o mesmo pedido que anteriormente. A resposta é a seguinte:

Caso 3: Realização de um cálculo de impostos com parâmetros em falta
Inicializamos uma sessão JSON, autenticamo-nos e, em seguida, fazemos a seguinte solicitação:

- em [5], falta o parâmetro [married];
A resposta é a seguinte:
Caso 4: Cálculo do imposto com parâmetros incorretos


A resposta do servidor é a seguinte:

Caso 4: Realização de um cálculo de imposto com parâmetros corretos

A resposta do servidor é a seguinte:

30.12. A ação [list-simulations]
A ação [list-simulations] permite ao utilizador visualizar a lista de simulações que realizou desde o início da sessão. A sua rota é definida da seguinte forma no script [main]:
O servidor não espera quaisquer parâmetros. A ação [lister-simulations] é tratada pelo seguinte [ListerSimulationsController]:
- linha 13: a lista de simulações é recuperada da sessão;
- linhas 15-16: é devolvida uma resposta de sucesso;
Vamos executar o seguinte teste no Postman:
- Iniciamos uma sessão JSON;
- Efetua a autenticação;
- Realizamos dois cálculos de impostos;
- Solicitamos a lista de simulações;
O pedido é o seguinte:
- em [3], não há parâmetros;
A resposta do servidor é a seguinte:

- em [4], a lista de simulações do utilizador;
30.13. A ação [delete-simulation]
A ação [delete-simulation] permite que um utilizador elimine uma das simulações da sua lista de simulações. A sua rota é definida da seguinte forma no script [main]:
O servidor espera um único parâmetro: o número da simulação a ser eliminada. A ação [delete-simulation] é tratada pelo seguinte [DeleteSimulationController]:
- linha 10: recuperamos os dois elementos do caminho da solicitação. Eles são recuperados como strings;
- linha 13: o parâmetro [number] é convertido num inteiro. Sabemos que isto é possível devido à assinatura da rota,
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
Sabemos também que se trata de um número inteiro >=0. De facto, não podemos ter um URL como [/delete-simulation/-4]. Este é rejeitado pelo servidor Flask;
- linha 15: recuperamos a lista de simulações da sessão;
- linha 16: utilizando a função [filter], procuramos a simulação com id==número. Obtemos um objeto [filter] que convertemos numa [list];
- linhas 17–20: se o filtro não devolver nada, então a simulação a ser eliminada não existe. Devolvemos uma resposta de erro a indicar isso;
- linhas 21–23: eliminamos a simulação devolvida pelo filtro;
- linha 25: restauramos a nova lista de simulações na sessão;
- linha 27: devolvemos a nova lista de simulações na resposta;
Realizamos um teste de sucesso e um teste de falha. Executamos simulações e, em seguida, solicitamos a lista de simulações:

- As simulações aqui têm os números 2 e 3;
Solicitamos que a simulação com o número 3 seja removida.

A resposta é a seguinte:
Agora, vamos repetir a mesma operação (eliminar a simulação com id=3). A resposta é então a seguinte:


30.14. A ação [end-session]
A ação [end-session] permite que um utilizador termine a sua sessão de simulação. O seu caminho é definido da seguinte forma no script [main]:
O servidor não espera parâmetros. A ação é tratada pelo seguinte [FinSessionController]:
- Linha 13: Apaga todas as chaves da sessão. Isto apaga:
- [typeResponse]: o tipo de respostas HTTP (json, xml, html);
- [simulation_id]: o ID da última simulação realizada;
- [simulations]: a lista das simulações do utilizador;
- [user]: o indicador de que o utilizador foi autenticado;
- retornar a resposta;
Poder-se-á questionar como é que a resposta HTTP da linha 15 será devolvida, agora que o tipo de resposta já não se encontra na sessão. Para descobrir, precisamos de voltar à função |front_controller| no script principal [main] e modificá-la da seguinte forma:
…
# on not# note the type of response required if this information is in the session
type_response1 = session.get('typeResponse', None)
# forward the request to the main controller
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# we log the result sent to the customer
log = f"[front_controller] {résultat}\n"
logger.write(log)
# was there a fatal error?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# send an e-mail to the application administrator
send_adminmail(config, log)
# determine the desired type of response
type_response2=session.get('typeResponse')
if type_response2 is None and type_response1 is None:
# the session type has not yet been set - it will be jSON
type_response = 'json'
elif type_response2 is not None:
# the type of response is known and in the session
type_response = type_response2
else:
type_response=type_response1
# build the response to be sent
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# we send the answer
return response, status_code
- linha 3: o tipo da resposta atualmente na sessão é armazenado;
- linha 6: a ação é executada. Se for:
- [end-session], a chave [typeResponse] já não se encontra na sessão;
- [init-session], a chave [typeResponse] na sessão pode ter mudado de valor;;
- linhas 14–20: a resposta HTTP deve ser enviada. Precisamos de saber de que forma:
- linhas 16–18: se o tipo de resposta não estiver definido nem por [type_response1] na linha 3 nem por [type_response2] na linha 15, então o tipo de resposta não foi definido nem antes nem depois da ação. Nesse caso, usamos JSON (linha 18);
- linhas 19–21: se [type_response2] existir — o tipo de resposta na sessão após a ação — então esse é o tipo a utilizar;
- linhas 22–23: caso contrário, [type_response1], o tipo de resposta antes da ação (que deve ser [end-session]), é o que deve ser utilizado;
30.15. A ação [get-admindata]
Vamos agora discutir os dois URLs reservados para os serviços JSON e XML:
Ação | Função | Contexto de execução |
/get-admindata | Retorna os dados fiscais utilizados para calcular o imposto | . Utilizado apenas se o tipo de sessão for json ou xml. O utilizador deve estar autenticado |
/calculate-taxes | Calcula o imposto para uma lista de contribuintes enviada em JSON | Pedido GET. Utilizado apenas se o tipo de sessão for json ou xml. O utilizador deve estar autenticado |
A URL [/get-admindata] está definida nas rotas do script principal [main] da seguinte forma:
A rota [/get-admindata] é tratada pelo seguinte [GetAdminDataController]:
- linhas 13-21: verificamos se estamos numa sessão JSON ou XML;
- linha 24: devolvemos o dicionário de dados da administração fiscal, que foi colocado na configuração quando o servidor foi iniciado:
# admindata sera une donnée de portée application en lecture seule
config["admindata"] = config["layers"]["dao"].get_admindata()
Vamos utilizar um cliente Postman e solicitar a URL [/get-admindata], após iniciar uma sessão JSON e autenticar-nos:

A resposta do servidor é a seguinte:

30.16. A ação [calculate-taxes]
A ação [calculate-taxes] calcula os impostos para uma lista de contribuintes encontrada no corpo da solicitação como uma string JSON. Já estamos familiarizados com esta ação: na versão anterior, ela era chamada de [calculate_tax_in_bulk_mode].
A sua rota é a seguinte:
Esta ação é tratada pelo seguinte [CalculateTaxesController]:
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 | |
- linhas 16-24: verificamos se estamos efetivamente numa sessão JSON ou XML
- linhas 26–120: este código é-nos geralmente familiar. Provém da função |index_controller| da versão 10 da aplicação, que foi adaptada para cumprir as especificações da interface [InterfaceController] implementada;
- linhas 104–115: código adicionado para ter em conta o novo ambiente deste controlador. Acabámos de realizar cálculos fiscais. Precisamos de armazenar os resultados na lista de simulações mantida na sessão;
- linha 105: recuperamos a lista de simulações na sessão;
- linha 106: recuperamos o número da última simulação realizada;
- linhas 107–112: percorremos a lista de dicionários que contêm os resultados do cálculo de impostos; atribuímos um ID de simulação a cada um, e cada dicionário é adicionado à lista de simulações;
- linhas 113–115: a nova lista de simulações e o número da última simulação realizada são devolvidos à sessão;
Executamos o seguinte teste no Postman após inicializar uma sessão JSON e autenticar:


A resposta do servidor é a seguinte:

Se agora solicitarmos a lista de simulações:
Note-se que na lista de resultados para [/calcul-impots], os contribuintes não têm um atributo [id], enquanto que na lista de simulações, cada simulação tem um número que a identifica.




