36. Exercício prático: versão 16

36.1. Introdução
Os URL do nosso aplicativo têm, por enquanto, o formato [/action/param1/param2/…]. Gostaríamos de poder adicionar um prefixo a esses URL. Por exemplo, com o prefixo [/do], teríamos URL no formato [/do/action/param1/param2/…].
36.2. A nova configuração das rotas

- para [2], o cálculo das rotas será alterado;
- em [1], o arquivo [config] é alterado para refletir essa mudança;
A configuração [config] passa a ser a seguinte:
def configure(config: dict) -> dict:
# configuração do syspath
import syspath
config['syspath'] = syspath.configure(config)
# configuração do aplicativo
import parameters
config['parameters'] = parameters.configure(config)
# configuração do banco de dados
import database
config["database"] = database.configure(config)
# instanciação das camadas do aplicativo
import layers
config['layers'] = layers.configure(config)
# configuração da camada MVC
config['mvc'] = {}
# configuração dos controladores da camada [web]
import controllers
config['mvc'].update(controllers.configure(config))
# ações ASV (Ação Mostrar Visualização)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# ações ADS (Ação “Do Something”)
import ads_actions
config['mvc'].update(ads_actions.configure(config))
# configuração das respostas HTTP
import responses
config['mvc'].update(responses.configure(config))
# configuração das rotas
import routes
routes.configure(config)
# aplicamos a configuração
return config
- linhas 37-39: o módulo [routes] (linha 38) é responsável por configurar as rotas (linha 39);
O arquivo de rotas sem token CSRF [configs/routes_without_csrftoken] sofre as seguintes alterações:
# dependências
from flask import redirect, request, session, url_for
from flask_api import status
# configuração do aplicativo
config = {}
# o controlador frontal
def front_controller() -> tuple:
# encaminhamos a solicitação para o controlador principal
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# raiz da aplicação
def index() -> tuple:
# redirecionamento para /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# inicialização da sessão
def init_session(type_response: str) -> tuple:
# é executado o controlador associado à ação
return front_controller()
# autenticar-usuário
def authentifier_utilisateur() -> tuple:
# executa-se o controlador associado à ação
return front_controller()
# calcular-imposto
def calculer_impot() -> tuple:
# executa-se o controlador associado à ação
return front_controller()
…
O arquivo foi despojado de suas rotas. Restam apenas as funções associadas a elas.
Os arquivos de rotas com token CSRF e [configs/routes_with_csrftoken] sofrem o mesmo destino:
# dependências
from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf
# configuração
config = {}
# o controlador frontal
def front_controller() -> tuple:
# a solicitação é encaminhada ao controlador principal
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# raiz do aplicativo
def index() -> tuple:
# redirecionamento para /init-session/html
return redirect(url_for("init_session", type_response="html", csrf_token=generate_csrf()), status.HTTP_302_FOUND)
# init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
# é executado o controlador associado à ação
return front_controller()
# autenticar-usuário
def authentifier_utilisateur(csrf_token: str) -> tuple:
# executa-se o controlador associado à ação
return front_controller()
…
# inicializar-sessão-sem-token-csrf para clientes json e xml
def init_session_without_csrftoken(type_response: str) -> tuple:
# redirecionamento para /init-session/type_response
return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
status.HTTP_302_FOUND)
As rotas são calculadas pelo seguinte módulo [configs/routes]:
from flask import Flask
def configure(config: dict):
# configuração das rotas
# aplicativo Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
config['app'] = app
# importação das rotas do aplicativo web
if config['parameters']['with_csrftoken']:
import routes_with_csrftoken as routes
else:
import routes_without_csrftoken as routes
# injetamos a configuração nas rotas
routes.config = config
# o prefixo das URL do aplicativo
prefix_url = config["parameters"]["prefix_url"]
# token CSRF
with_csrftoken = config["parameters"]['with_csrftoken']
if with_csrftoken:
csrftoken_param = f"/<string:csrf_token>"
else:
csrftoken_param = ""
# as rotas do aplicativo Flask
# raiz do aplicativo
app.add_url_rule(f'{prefix_url}/', methods=['GET'],
view_func=routes.index)
# inicialização da sessão
app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
view_func=routes.init_session)
# inicialização da sessão sem token CSRF
if with_csrftoken:
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
methods=['GET'],
view_func=routes.init_session_without_csrftoken)
# autenticar-usuário
app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
view_func=routes.authentifier_utilisateur)
# cálculo-do-imposto
app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impot)
# cálculo do imposto em lotes
app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impots)
# listar-simulações
app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.lister_simulations)
# excluir-simulação
app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
view_func=routes.supprimer_simulation)
# encerrar-sessão
app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
view_func=routes.fin_session)
# exibir cálculo de imposto
app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_calcul_impot)
# obter-dados-administrativos
app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
view_func=routes.get_admindata)
# exibir-visão-cálculo-imposto
app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_calcul_impot)
# exibir-visão-autenticação
app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_authentification)
# exibir-visualização-lista-simulações
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_simulations)
# exibir-visualização-lista_de_erros
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_erreurs)
# caso específico
if with_csrftoken:
# inicializar-sessão-sem-csrftoken para clientes JSON e XML
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
view_func=routes.init_session_without_csrftoken)
- linhas 6-8: a aplicação Flask é criada e incluída na configuração;
- linhas 10-14: importa-se o arquivo de rotas adequado à situação. Vale lembrar que o arquivo importado, na verdade, não contém rotas. Ele contém apenas as funções associadas a elas;
- linha 17: as funções associadas às rotas precisam conhecer a configuração da aplicação;
- linha 20: observa-se o prefixo URL. Ele pode estar vazio;
- linhas 22-27: as rotas com o token CSRF possuem um parâmetro a mais do que aquelas que não o possuem. Para lidar com essa diferença, utiliza-se a variável [csrftoken_param]:
- ela contém a string vazia se não houver o token CSRF nas rotas;
- ela contém a string [/<string:csrf_token>] se houver um token CSRF;
- linhas 29-96: cada rota do aplicativo está associada a uma função do arquivo de rotas importado nas linhas 10-14;
36.3. Os novos controladores

Na versão anterior, todos os controladores obtinham a ação em processamento da seguinte maneira:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# recuperar os elementos do caminho
params = request.path.split('/')
action = params[1]
Esse código não funciona mais se houver um prefixo, por exemplo, [/do/lister-simulations]. Nesse caso, a ação, na linha 3, seria [do] e, portanto, estaria incorreta.
Transformamos esse código da seguinte maneira:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# recuperação dos elementos do caminho
prefix_url = config["parameters"]["prefix_url"]
params = request.path[len(prefix_url):].split('/')
action = params[1]
Fazemos isso em todos os controladores.
36.4. Os novos modelos

Na versão anterior, cada classe de modelo gerava um modelo da seguinte maneira:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# encapsulando os dados da página no modelo
modèle = {}
# estado do aplicativo
état = résultat["état"]
# o modelo depende do estado
…
# token CSRF
modèle['csrf_token'] = super().get_csrftoken(config)
# ações possíveis a partir da visualização
modèle['actions_possibles'] = […]
# renderizamos o modelo
return modèle
Agora, o modelo terá uma chave adicional, o prefixo URL:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# encapsulamos os dados da página no modelo
modèle = {}
# estado do aplicativo
état = résultat["état"]
# o modelo depende do estado
…
# token CSRF
modèle['csrf_token'] = super().get_csrftoken(config)
# ações possíveis a partir da visualização
modèle['actions_possibles'] = […]
# prefixo dos URL
modèle["prefix_url"] = config["parameters"]["prefix_url"]
# retornamos o modelo
return modèle
36.5. Os novos fragmentos

Todos os fragmentos que contenham URL devem ser alterados.
O fragmento [v-authentification]
<!-- formulário HTML — enviamos seus valores com a ação [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- título -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
O fragmento [v-calcul-impot]
<!-- formulário HTML enviado -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- mensagem em 12 colunas sobre fundo azul -->
…
</form>
O fragmento [v-liste-simulations]
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- tabela de simulações -->
<table class="table table-sm table-hover table-striped">
…
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
O fragmento [v-menu]
<!-- menu Bootstrap -->
<nav class="nav flex-column">
<!-- exibição de uma lista de links HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
36.6. A nova resposta HTML

Na versão anterior, o código da resposta HTML terminava com um redirecionamento:
class HtmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# a resposta HTML depende do código de status retornado pelo controlador
état = résultat["état"]
…
# agora é preciso gerar o URL de redirecionamento, sem esquecer o token CSRF, caso seja solicitado
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# resposta de redirecionamento
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
Agora, esse código passa a ser o seguinte:
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
…
# agora é preciso gerar o URL de redirecionamento, sem esquecer o token CSRF, caso seja solicitado
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# resposta de redirecionamento
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
36.7. Testes

Vamos inserir o seguinte prefixo no arquivo de configuração [parameters]:
…
# token CSRF
"with_csrftoken": True,
# bancos de dados gerenciados: MySQL (MySQL), PostgreSQL (PostgreSQL)
"databases": ["mysql", "pgres"],
# prefixo dos URL do aplicativo
# defina a string como vazia se não quiser um prefixo; caso contrário, use /prefixo
"prefix_url": "/do",
…
Vamos executar o aplicativo e, em seguida, solicitar o URL [http://localhost:5000/do]; a resposta é a seguinte:

- em [1], o prefixo do URL;
- em [2], o token CSRF;
O aplicativo também pode ser testado com os testes de console do [http-clients/09]:

Nos arquivos de configuração [1] e [2], o prefixo de URL deve ser incluído no URL do servidor:
"server": {
# "urlServer": "http://127.0.0.1:5000",
"urlServer": "http://127.0.0.1:5000/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
- linha 3: o URL do servidor agora inclui o prefixo do URL;
Após essa alteração, todos os testes de console devem funcionar.