38. Exercício prático: versão 18
38.1. Implementação

A pasta [impots/http-servers/13] é obtida inicialmente através da cópia da pasta [impots/http-servers/12] e, em seguida, parcialmente modificada.
Começamos por adicionar um novo parâmetro ao ficheiro [configs/parameters]:
…
# token CSRF
"with_csrftoken": False,
# bases de dados geridas: MySQL (MySQL), PostgreSQL (PostgreSQL)
"databases": ["mysql", "pgres"],
# prefixo dos URL da aplicação
# definir a cadeia como vazia se não se pretender um prefixo ou /prefixo caso contrário
"prefix_url": "/do",
# URL raiz do servidor Apache — definir a cadeia como vazia para execução fora do Apache
"application_root": "/impots"
…
Na linha 10, o parâmetro [application_root] representará o alias WSGI do servidor virtual Apache.
Com este parâmetro, podemos corrigir a instrução de [responses/HtmlResponse] que tinha provocado o erro:

…
# agora é necessário 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']['application_root']}{config['parameters']['prefix_url']}{ads['to']}{csrf_token}")
, status.HTTP_302_FOUND
- linha 9: adicionámos a raiz da aplicação no início do destino do redirecionamento URL;
Temos também de corrigir todos os fragmentos para que os URL que contêm comecem pela raiz da aplicação (ou alias WSGI):

O fragmento [v-authentification]
<!-- formulário HTML — enviamos os seus valores com a ação [authentifier-utilisateur] -->
<form method="post" action="{{modèle.application_root}}{{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.application_root}}{{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.application_root}}{{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.application_root}}{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
Todos os fragmentos acima utilizam o modelo [modèle.application_root]. De momento, a chave [application_root] não existe nos modelos gerados pelas classes de modelos.

A classe [AbstractBaseModelForView], que é a classe-pai de todas as classes que geram um modelo, passa a ser a seguinte:
from abc import abstractmethod
from flask import Request
from flask_wtf.csrf import generate_csrf
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class AbstractBaseModelForView(InterfaceModelForView):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
def update_model(self, modèle: dict, config: dict):
# cálculo do token CSRF
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# atualização do modelo passado como parâmetro
modèle.update({
# token CSRF
'csrf_token': csrf_token,
# prefix_url
'prefix_url': configQZXW2HTMLBWyJwYXJhbWV0ZXJzIl0ZQXQZXW2HTMLBWyJwcmVmaXhfdXJsIl0ZQX,
# application_root
'application_root': configQZXW2HTMLBWyJwYXJhbWV0ZXJzIl0ZQXQZXW2HTMLBWyJhcHBsaWNhdGlvbl9yb290Il0ZQX,
})
- linha 15: o método [update_model] tem como função inserir as vistas no modelo:
- linha 24: o token CSRF;
- linha 26: o prefixo de URL;
- linha 28: a raiz da aplicação ou alias WSGI;
As quatro classes filhas chamam a classe pai com o seguinte código:
…
# ações possíveis a partir da vista
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
# finalização do modelo pela classe pai
super().update_model(modèle, config)
# retornamos o modelo
return modèle
- linha 6: cada classe filha chama a sua classe pai para atualizar o modelo que criou;
A versão 18 está pronta. Retomamos os dois servidores virtuais do Apache da versão 17 e modificamo-los:

Os dois ficheiros [flask-impots-withXX.conf] são alterados apenas num único ponto:
# pasta do script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/13/apache"
# nome do site configurado por este ficheiro
# aqui, o nome será flask-impots-withmysql
# os URL serão do tipo http(s)://flask-impots-withmysql/caminho
define SITE "flask-impots-withmysql"
# introduza o endereço IP 127.0.0.1 para o site SITE no ficheiro c:/windows/system32/drivers/etc/hosts
# introduza aqui os caminhos das bibliotecas Python a utilizar — separe-as por vírgulas
# aqui, as bibliotecas de um ambiente virtual Python
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# Python Home — necessário apenas se houver várias versões do Python instaladas
# WSGIPythonHome "C:/Program Files/Python38"
# URL HTTP
<VirtualHost *:80>
# com o alias /, os URL terão o formato /{prefixe_url}/action/...
# com o alias /impostos, os URL terão o formato /impostos/{prefixe_url}/action/...
# onde [prefixe_url] está definido em parameters.py
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
# URL protegidas com HTTPS
<VirtualHost *:443>
# com o alias /, os URL terão o formato /{prefixe_url}/action/...
# com o alias /impostos, os URL terão o formato /impostos/{prefixe_url}/action/...
# onde [prefixe_url] está definido em parameters.py
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
..
</VirtualHost>
- na linha 12, agora utilizamos a pasta [impots/http-servers/13/apache].
Estamos prontos para os testes da versão 18 implementada no Apache. A configuração é a seguinte:
- o alias WSGI é /impostos nos dois ficheiros de configuração dos servidores virtuais;
- no ficheiro de configuração [configs/parameters], os parâmetros são os seguintes:
# token CSRF
"with_csrftoken": False,
# prefixo da aplicação URL
# definir a cadeia como vazia se não se pretender um prefixo ou, caso contrário, /prefixo
"prefix_url": "/do",
# URL raiz do servidor Apache — definir a cadeia como vazia para execução fora do Apache
"application_root": "/impots"
Iniciamos o servidor Apache, bem como os dois SGBD. Solicitamos o URL e o [https://flask-impots-withmysql/impots/do]. A resposta do servidor é a seguinte:

Conseguimos, de facto, aceder à página de autenticação que não tínhamos conseguido na versão anterior. O resto da aplicação funciona normalmente.
Agora, testamos o outro servidor virtual. Solicitamos o URL e o [https://flask-impots-withpgres/impots/do]. A resposta do servidor é a seguinte:

Os conceitos de alias WSGI e de prefixo URL desempenham a mesma função. Um destes dois conceitos é redundante. Assim, para prefixar os URL do servidor Apache com a cadeia [/impots/do], é possível proceder de três formas:
1 – [WGSIAlias /impots] e [prefix_url=’/do’];
2 – [WGSIAlias /] e [prefix_url=’/impots/do’];
3 – [WGSIAlias /impots/do] e [prefix_url=’’];
38.2. Testes de consola
Utilizam-se novamente os testes de consola do cliente [http-clients/09]:

- o URL do servidor deve ser alterado nas configurações [1] e [3];
- é necessário efetuar uma alteração na camada [dao] para que esta suporte o protocolo HTTPS do servidor Apache;
Nos ficheiros [config], o URL do servidor passa a ter o seguinte aspeto:
"server": {
# "urlServer": "http://127.0.0.1:5000",
# "urlServer": "http://127.0.0.1:5000/do",
"urlServer": "https://flask-impots-withmysql/impots/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
},
# modo de depuração
"debug": True,
# csrf_token
"with_csrftoken": False,
- linha 4: o novo URL do servidor. Pela primeira vez neste documento, o cliente utiliza o protocolo HTTPS;
A classe [ImpôtsDaoWihHttpSession] da camada [dao] evolui da seguinte forma:
…
# etapa de pedido/resposta
def get_response(self, method: str, url_service: str, data_value: dict = None, json_value=None):
# [method]: método HTTP, GET ou POST
# [url_service]: URL de serviço
# [data]: parâmetros do POST em x-www-form-urlencoded
# [json]: parâmetros do POST em JSON
# [cookies]: cookies a incluir na solicitação
# é necessário ter uma sessão XML ou JSON; caso contrário, não será possível processar a resposta
if self.__session_type not in ['json', 'xml']:
raise ImpôtsError(73, "il n'y a pas de session valide en cours")
# adiciona-se o token CSRF ao URL de serviço
if self.__csrf_token:
url_service = f"{url_service}/{self.__csrf_token}"
# execução do pedido
response = requests.request(method,
url_service,
data=data_value,
json=json_value,
cookies=self.__cookies,
allow_redirects=True,
# para o protocolo HTTPS
verify=False)
# modo de depuração?
if self.__debug:
# utilizador
if not self.__logger:
self.__logger = self.__config['logger']
# iniciar sessão
self.__logger.write(f"{response.text}\n")
…
# apresenta o resultado
return résultat['réponse']
- na linha 26, é adicionado o parâmetro [verify=False] devido ao protocolo HTTPS utilizado pelo servidor Apache. O módulo [requests] (linha 19) suporta nativamente o protocolo HTTPS. Por predefinição, verifica a validade do certificado de segurança que lhe é enviado pelo servidor HTTPS e lança uma exceção se o certificado recebido não for válido. É o que acontece neste caso, em que o servidor Apache do Laragon envia um certificado autoassinado. Para evitar a exceção, utiliza-se o parâmetro [verify=False] para indicar ao módulo [requests] que não deve lançar uma exceção. O [requests] limita-se então a apresentar um aviso (warning) na consola.
Depois de efetuadas estas alterações, todos os testes de consola devem funcionar.