37. Exercício prático: versão 17

Esta nova versão traz as seguintes alterações:
- será portada para um servidor Apache / Windows;
- para realizar esta migração, a versão 17 contém todas as dependências de que necessita na sua pasta [impots / http-servers/ 12]. Recorde-se que as versões anteriores obtinham as suas dependências em diferentes pastas de todo o projeto [python-flask-2020];
37.1. Relocalização das dependências da aplicação

Recorde-se que a gestão das dependências da aplicação é efetuada no script [syspath]. Na versão anterior, este script era o seguinte:
def configure(config: dict) -> dict:
import os
# pasta deste ficheiro
script_dir = os.path.dirname(os.path.abspath(__file__))
# caminho raiz
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# dependências
absolute_dependencies = [
# pastas do projeto
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
f"{root_dir}/impots/v04/interfaces",
# AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
f"{root_dir}/impots/v04/services",
# ImpotsDaoWithAdminDataInDatabase
f"{root_dir}/impots/v05/services",
# AdminData, ImpôtsError, TaxPayer
f"{root_dir}/impots/v04/entities",
# Constantes, intervalos
f"{root_dir}/impots/v05/entities",
# Logger, SendAdminMail
f"{root_dir}/impots/http-servers/02/utilities",
# pasta do script principal
script_dir,
# configurações [database, layers, parameters, controllers, views]
f"{script_dir}/../configs",
# controladores
f"{script_dir}/../controllers",
# respostas HTTP
f"{script_dir}/../responses",
# modelos das vistas
f"{script_dir}/../models_for_views",
]
# definir o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# carregar a configuração
return {
"root_dir": root_dir,
"script_dir": script_dir
}
É necessário relocalizar todas as dependências cujo nome absoluto dependa da variável [root_dir] da linha 8, ou seja, as linhas 13 a 26.
O script [syspath] da nova versão será o seguinte:
def configure(config: dict) -> dict:
import os
# pasta deste ficheiro
script_dir = os.path.dirname(os.path.abspath(__file__))
# dependências
absolute_dependencies = [
# entidades da aplicação
f"{script_dir}/../entities",
# camada [dao]
f"{script_dir}/../layers/dao",
# camada [métier]
f"{script_dir}/../layers/métier",
# utilitários
f"{script_dir}/../utilities",
# pasta do script principal
script_dir,
# configurações [database, layers, parameters, controllers, views]
f"{script_dir}/../configs",
# controladores
f"{script_dir}/../controllers",
# respostas HTTP
f"{script_dir}/../responses",
# modelos das vistas
f"{script_dir}/../models_for_views",
]
# define-se o syspath
import sys
# adicionam-se as dependências absolutas do projeto
for directory in absolute_dependencies:
# verifica-se se a pasta existe
existe = os.path.exists(directory) and os.path.isdir(directory)
if not existe:
# avisa-se o programador
raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
else:
# insere-se a pasta no início do syspath
sys.path.insert(0, directory)
# é feita a configuração
return {
"script_dir": script_dir,
}
- linhas 8 a 27: todas as dependências são agora relativas à variável [script_dir] da linha 5;
- linhas 42-45: a variável [root_dir] desapareceu da configuração do syspath;
- linha 10: as entidades da aplicação encontram-se na pasta [entities] [1];
- linha 12: a camada [dao] encontra-se na pasta [layers/dao] [2];
- linha 14: a camada [métier] encontra-se na pasta [layers/métier] [2];
- linha 16: os utilitários [Logger, SendMail] encontram-se na pasta [utilities] [3];
- linhas 29-40: calcula-se o Python Path da aplicação sem importar o módulo [myutils];

37.2. Testes
Nesta fase, a versão 17 deve estar a funcionar. Verifique isso.
37.3. Portagem de uma aplicação Python/Flask para um servidor Apache/Windows
37.3.1. Fontes
Para realizar a portabilidade de uma aplicação Flask para o Apache / Windows, tive de pesquisar na Internet. Aqui está o link que me ajudou a começar: [https://medium.com/@madumalt/flask-app-deployment-in-windows-apache-server-mod-wsgi-82e1cfeeb2ed];
Utilizei as informações deste link, exceto no que diz respeito à configuração do servidor Apache. Para esta, utilizei um exemplo de configuração do servidor Apache do Laragon.
37.3.2. Instalação do módulo Python mod_wsgi
A aplicação Python/Flask que desenvolvemos utilizava o servidor WSGI (Web Server Gateway Interface) [werkzeug] fornecido com o Flask. Este servidor é descrito |aqui|. O link [https://www.fullstackpython.com/wsgi-servers.html] descreve o funcionamento dos servidores WSGI. Existem vários |servidores WSGI|. Um deles é o servidor Apache a funcionar no modo WSGI. Esta é a solução adotada aqui porque o Laragon, que instalámos, vem com um servidor Apache.
Para que o servidor Apache possa alojar uma aplicação Python, é necessário instalar o módulo Python [mod_wsgi]. A instalação deste módulo é delicada, porque durante o processo ocorre uma compilação em C++. Para que a instalação seja bem-sucedida, é necessário um compilador Microsoft C++. Uma solução simples consiste em instalar a versão atual do Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].
Se não for necessário utilizar o Visual Studio para outros fins além do [mod_wsgi], é possível limitar a instalação ao ambiente C++:

Depois de instalado o compilador C++, a instalação do módulo [mod_wsgi] é efetuada num terminal PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>SET MOD_WSGI_APACHE_ROOTDIR=C:\MyPrograms\laragon\bin\apache\httpd-2.4.35-win64-VC15
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>pip install mod_wsgi
Collecting mod_wsgi
Using cached mod_wsgi-4.7.1.tar.gz (498 kB)
Using legacy setup.py install for mod-wsgi, since package 'wheel' is not installed.
Installing collected packages: mod-wsgi
Running setup.py install for mod-wsgi ... done
Successfully installed mod-wsgi-4.7.1
- linha 1: define-se o valor da variável de ambiente [MOD_WSGI_APACHE_ROOTDIR]. Este valor corresponde à localização do servidor Apache no sistema de ficheiros. Neste caso, essa localização é [<laragon>\bin\apache\httpd-2.4.35-win64-VC15], em que <laragon> é a pasta de instalação do Laragon. Pode obter esta localização de várias formas. Aqui está uma delas, obtida a partir de uma das opções do Laragon:

Em [1-3], o ficheiro [httpd.conf] é o ficheiro de configuração principal do servidor Apache. O ficheiro em questão é então aberto num editor de texto (Notepad++, como se vê abaixo):

Em [2], a pasta de instalação do Apache é a parte que precede a sequência [conf\httpd.conf].
Voltemos à instalação do módulo [mod_wsgi]:
- linhas 3-9: instalação do módulo [mod_wsgi];
37.3.3. Configuração do servidor Apache do Laragon
Vamos configurar o servidor Apache do Laragon. Começamos pelo seu ficheiro de configuração principal [httpd.conf]:

Dirigimo-nos ao final do ficheiro [httpd.conf]:
…
IncludeOptional "C:/MyPrograms/laragon/etc/apache2/alias/*.conf"
IncludeOptional "C:/MyPrograms/laragon/etc/apache2/sites-enabled/*.conf"
Include "C:/MyPrograms/laragon/etc/apache2/httpd-ssl.conf"
Include "C:/MyPrograms/laragon/etc/apache2/mod_php.conf"
# Python mod_wsgi
LoadModule wsgi_module "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages/mod_wsgi/server/mod_wsgi.cp38-win_amd64.pyd"
- A linha 8 foi adicionada ao ficheiro existente [httpd.conf], no final do ficheiro. Ela indica ao servidor Apache onde se encontra um elemento do módulo [mod_wsgi] que acabámos de instalar;
Uma forma simples de obter o caminho da linha 8 é executar o seguinte comando num terminal PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>mod_wsgi-express module-config
LoadModule wsgi_module "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages/mod_wsgi/server/mod_wsgi.cp38-win_amd64.pyd"
WSGIPythonHome "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv"
Algumas documentações indicam que é necessário adicionar as linhas 2 e 3 ao final do ficheiro [httpd.conf]. No meu caso, a linha 3 acima provocava um erro (módulo [encodings] em falta). Por isso, não foi incluída no ficheiro [httpd.conf]. Apenas a linha 2 foi incluída. O significado dos diferentes parâmetros do módulo [mod_wsgi] que podem ser utilizados nos ficheiros de configuração do Apache está descrito |aqui|.
Em seguida, vamos ativar o protocolo HTTPS do servidor Apache:

- em [1-4], ativamos o protocolo HTTPS do servidor Apache;
A partir de agora, poderemos utilizar os protocolos URL e [https://serveur/chemin];
Para configurar o Apache de forma a servir uma aplicação Flask, o link referido |acima| utiliza servidores virtuais. O Laragon também permite gerir servidores virtuais:

- em [1-3], solicitamos ao Laragon que crie automaticamente anfitriões virtuais;
O passo seguinte é criar um projeto web com o Laragon:
![]() |
![]() | ![]() |
- em [1-3], cria-se um projeto PHP vazio;
- em [4-8], o Laragon criou um site virtual denominado [auto.projet-test.test], configurado pelo ficheiro [auto.projet-test.test.conf] [8] da pasta [sites-enabled] [7]. Esta pasta encontra-se no endereço [<laragon>\etc\apache2\sites-enabled], sendo que [laragon] é a pasta de instalação do Laragon;
Embora isso não faça parte do que estamos a fazer neste momento, talvez tenha curiosidade em ver o que é este site [projet-test] que acabámos de criar:

- em [1-5], foi criado um projeto vazio. Trata-se de um projeto PHP localizado na pasta [<laragon>/www], sendo que [laragon] é a pasta de instalação do Laragon;
Agora, vamos analisar o ficheiro [auto.projet-test.test.conf] gerado pelo Laragon na pasta [<laragon>\etc\apache2\sites-enabled]:
define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"
<VirtualHost *:80>
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:443>
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/MyPrograms/laragon/etc/ssl/laragon.key
</VirtualHost>
- linha 1: a raiz, no sistema de ficheiros, do projeto criado;
- linha 2: o nome do servidor virtual. Os ficheiros URL para este servidor terão o formato [http(s)://projet-test.test/chemin];
- linhas 4-12: configuração do site virtual para a porta 80 (linha 4) e o protocolo HTTP;
- linhas 14-27: configuração do site virtual para a porta 443 (linha 14) e o protocolo HTTPS;
Vamos ver como funciona um servidor virtual. Primeiro, inicie o servidor Apache e o PHP:
Em seguida, utilizando um navegador, acedemos ao URL [http://projet-test.test/]:

- em [1], o URL solicitado;
- no [2], foi utilizado o protocolo HTTP;
- no [3], como o projeto [projet-test] está vazio, obtém-se o índice da sua pasta (lista do seu conteúdo), índice vazio;
Agora, vamos solicitar o URL protegido pelo [https://projet-test.test/]:

- em [1-2], obtém-se a mesma resposta que anteriormente, mas com o protocolo HTTPS [1];
A criação do servidor virtual [projet-test.test] criou uma nova entrada no ficheiro [<windows>/system32/drivers/etc/hosts]:

- linha 23: o endereço IP do nome [projet-test.test] é 127.0.0.1, ou seja, o endereço de [localhost] (linha 20), a máquina local. Assim, quando se digita URL [http://projet-test.test/chemin] num navegador, o pedido é enviado para o endereço 127.0.0.1 na porta 80. É então o servidor Apache da máquina local (localhost) que responde.
Podemos questionar-nos por que razão, quando se introduz a solicitação [http://projet-test.test/], o servidor Apache utiliza a configuração do ficheiro [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf]:

Para compreender isto, é necessário ver o que o navegador envia ao servidor Apache quando se faz esta solicitação. Vamos fazê-la com o Postman:

- em [1-3], enviamos uma solicitação HTTPS [1];
- ao enviar [4], o Postman indica que não reconheceu o certificado de segurança. O protocolo HTTPS estabelece uma ligação encriptada entre o cliente web (neste caso, o Postman) e o servidor Apache. Esta ligação encriptada é estabelecida através de certificados trocados entre o cliente e o servidor. É o servidor que inicia o diálogo para o estabelecimento da ligação encriptada, enviando ao cliente um certificado de segurança. Para que este certificado seja aceite pelo cliente, tem de estar assinado, ou seja, adquirido junto de empresas autorizadas a emitir certificados de segurança. Quando ativámos o protocolo HTTPS do Laragon, o próprio Laragon criou o certificado de segurança. Diz-se então que o certificado é autoassinado. A maioria dos clientes web emite um aviso quando recebe um certificado autoassinado. É o que o Postman faz em [4]. A maioria dos clientes web sugere então desativar a verificação do certificado de segurança enviado pelo servidor. É o que o Postman sugere em [5];
Clicamos na ligação [5] para desativar a verificação SSL (Secure Sockets Layer). O SSL / TSL (Transport Layer Security) é um protocolo de segurança que cria um canal de comunicação seguro entre dois computadores na Internet. É o protocolo utilizado aqui pelo Apache. A resposta é a seguinte:

Recebemos a mesma página que com um navegador tradicional. Agora, vejamos o diálogo cliente/servidor na consola do Postman (Ctrl-Alt-C):
- linha 6: o cabeçalho HTTP [Host] especifica o nome do servidor visado pelo cliente web. Este é o princípio dos servidores virtuais. Num mesmo endereço IP (neste caso, 127.0.0.1), um servidor web pode alojar vários sites com nomes diferentes. O cabeçalho HTTP [Host] permite ao cliente indicar a que servidor (neste caso, com o endereço 127.0.0.1) se dirige;
Agora, o que faz o Apache?
Ao iniciar, o Apache lê todos os ficheiros de configuração encontrados na pasta [[<laragon>\etc\apache2\sites-enabled]:

Cada ficheiro de configuração define um servidor virtual. Por exemplo, no ficheiro [auto.projet-test.test.conf], encontra-se a seguinte linha:
define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"
…
A linha 2 define o servidor virtual [projet-test.test]. O ficheiro [auto.projet-test.test.conf] contém a configuração desse servidor virtual. Como lê, no arranque, todos os ficheiros de configuração da pasta [<laragon>\etc\apache2\sites-enabled], o servidor Apache sabe que existe um servidor virtual chamado [projet-test.test]. Assim, quando recebe do cliente Postman a solicitação HTTPS:
reconhece que a solicitação é dirigida ao servidor virtual [projet-test.test] (linha 6) e que este existe. Em seguida, utiliza a configuração do servidor virtual [projet-test.test] para responder ao cliente Postman.
37.4. Criação de um primeiro servidor virtual Apache
Agora que sabemos para que servem os servidores virtuais e como defini-los, vamos criar um. Este servirá para executar uma aplicação Python Flask instalada na pasta [Apache] da versão 17, que está a ser implementada no servidor Apache:
Colocámos na pasta [http-servers/12/apache/exemple] a aplicação desenvolvida no parágrafo |link|, um serviço web de data/hora:

O servidor [date_time_server.py] é o seguinte:
# importações
import os
import sys
import time
# temos de colocar a pasta dos módulos no Python Path
# para portabilidade para o Apache no Windows
sys.path.insert(0, "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages")
from flask import Flask, make_response, render_template
# aplicação Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
application = Flask(__name__, template_folder=f"{script_dir}")
# Página inicial URL
@application.route('/')
def index():
# envio da hora ao cliente
# time.localtime: número de milissegundos desde 01/01/1970
# time.strftime permite formatar a hora e a data
# formato de exibição da data e hora
# d: dia com 2 dígitos
# m: mês com 2 dígitos
# y: ano com 2 dígitos
# H: hora 0,23
# M: minutos
# S: segundos
# data/hora atual
time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
# gera-se o documento a enviar ao cliente
page = {"date_heure": time_of_day}
document = render_template("date_time_server.html", page=page)
print("document", type(document), document)
# resposta HTTP ao cliente
response = make_response(document)
print("response", type(response), response)
return response
# apenas a main
if __name__ == '__main__':
application.config.update(ENV="development", DEBUG=True)
application.run()
A aplicação Flask é referenciada pelo identificador [application] (linhas 14, 43, 44). Este nome é obrigatório. Se a aplicação Flask for referenciada com outro identificador, a aplicação não funcionará, apresentando uma mensagem de erro a indicar que não consegue encontrar o URL solicitado. Esta mensagem de erro não fornece qualquer indicação sobre a origem do erro. Por isso, é necessário estar atento a este ponto.
O ficheiro HTML referido na linha 34 é o seguinte:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Date et heure du moment</title>
</head>
<body>
<b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>
- [date-time-server] será o servidor virtual que irá alojar esta aplicação. Será configurado pelo ficheiro [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] (recorde-se que este nome é livre – o Apache lê todos os ficheiros presentes em [sites-enabled] para identificar os sites virtuais alojados);
Obtenemos este ficheiro, em primeiro lugar, copiando o ficheiro [auto.projet-test.test.conf] e, em seguida, modificamo-lo.

O ficheiro [date-time-server.conf] terá o seguinte conteúdo:
# pasta do script Python-Flask da aplicação
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"
# nome do site configurado por este ficheiro
# aqui, será denominado «date-time-server»
# os URL serão do tipo http(s)://date-time-server/caminho
define SITE "date-time-server"
# definir o endereço IP como 127.0.0.1 para o site SITE no ficheiro c:/windows/system32/drivers/etc/hosts
# URL HTTP
<VirtualHost *:80>
# com o alias / os URL terão o formato http(s)://servidor-de-data-e-hora/caminho/...
WSGIScriptAlias / "${ROOT}/date_time_server.py"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL seguras com HTTPS
<VirtualHost *:443>
# com o alias / os URL terão o formato http(s)://servidor-de-data-e-hora/caminho/...
WSGIScriptAlias / "${ROOT}/date_time_server.py"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/myprograms/laragon/etc/ssl/laragon.key
</VirtualHost>
- linha 7: atribui-se um nome ao servidor virtual configurado pelo ficheiro;
- linha 2: atribui-se o valor da variável [ROOT] utilizada nas linhas 14 e 27;
- linhas 14 e 27: indica-se o caminho do script Python que deve ser executado quando o servidor virtual receber um pedido. Indica-se aqui que os pedidos para o servidor [date-time-server] são processados pelo script Python [date_time_server.py]. Esta diferença em relação ao ficheiro [auto.projet-test.test.conf] deve-se ao facto de esse ficheiro configurar um servidor PHP, enquanto o ficheiro [date-time-server.conf] configura um servidor Python;
- linhas 14 e 27: o atributo [WSGIScriptAlias /] indica aqui que a raiz do servidor [date-time-server] será [/]. Assim, os URL da aplicação terão o formato [http(s)://date-time-server/chemin];
- nas linhas 14 e 27, é possível atribuir outra raiz à aplicação, por exemplo, [WSGIScriptAlias /show]. Assim, os URL da aplicação assumirão a forma [http(s)://show/date-time-server/chemin];
Temos também de adicionar uma linha ao ficheiro [<windows>/system32/drivers/etc/hosts]:
Adicionamos a linha 25, para atribuir o endereço IP [127.0.0.1] ao servidor virtual [date-time-server].
Vamos verificar tudo isto. Iniciamos o servidor Apache:

Em seguida, acedemos a URL [https://date-time-server] através de um navegador:

- em [1], a página URL solicitada;
- em [3], a resposta do servidor;
- em [2], o navegador indica que a ligação HTTPS não é segura, pois detetou que o certificado enviado pelo servidor Apache era autoassinado;
Agora, no ficheiro [date-time-server.conf], vamos inserir um alias nas linhas 14 e 27:
WSGIScriptAlias /show-date-time "${ROOT}/date_time_server.py"
A alteração não é imediatamente aplicada pelo servidor Apache. É necessário recarregá-lo:

Em seguida, solicitamos o URL [https://date-time-server/show-date-time]. A resposta do servidor é então a seguinte:

37.5. Portagem da aplicação de cálculo de impostos para Apache / Windows
A pasta [apache] [2] é obtida inicialmente através da cópia da pasta [main]. É importante que se encontrem no mesmo nível para que os caminhos do script [syspath.py], copiado de [1] para [2], continuem válidos. Para não interferir com a aplicação [impots / http-servers/ 12], que está a funcionar, colocamos no [apache] a configuração que será executada pelo servidor Apache;

- o ficheiro [config] de [2] é idêntico ao [config] de [1];
- o ficheiro [syspath] de [2] é o mesmo que o ficheiro [syspath] de [1];
- o ficheiro [main_withmysql] de [2] é o ficheiro [main] de [1] com as seguintes alterações:
O script principal [main] recebia um parâmetro [mysql / pgres] que lhe indicava qual o SGBD a utilizar. O script [main_withmysql] utiliza o SGBD MySQL:
# espera-se um parâmetro MySQL ou PostgreSQL
import os
import sys
# configurar a aplicação com MySQL
import config
config = config.configure({'sgbd': "mysql"})
# dependências
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
…
Na linha 7, define-se o SGBD como MySQL.
- O ficheiro [main_withpgres] do [2] é o ficheiro [main] do [1] com as seguintes alterações: utiliza o SGBD PostgreSQL:
# é esperado um parâmetro mysql ou pgres
import os
import sys
# a aplicação é configurada com MySQL
import config
config = config.configure({'sgbd': "pgres"})
# dependências
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
…
Na linha 7, define-se o SGBD como PostgreSQL.
Feito isto, cria-se o script [main_withmysql.wsgi] (o sufixo utilizado não tem importância) da seguinte forma:
O script [main_withmysql.wsgi] será o destino executado pelo servidor Apache no modo WSGI:
- o destino do servidor Apache poderia ter sido o script [main_withmysql.py], tal como foi feito anteriormente com o script [date_time_server.py]. No entanto, teria sido necessário alterá-lo ligeiramente:
- ao contrário do modo de execução com um script de consola, com o Apache, a pasta que contém o alvo [main_withmysql.py] não faz parte do Python Path. Por isso, a linha 6 do script [main_withmysql.py] provoca um erro;
- a segunda alteração que seria necessário fazer é que, no [main_withmysql], a aplicação Flask é referenciada pelo identificador [app]. Sabe-se que, para o Apache / WSGI, esta também deve ser referenciada por um identificador [application];
- em vez de alterar o [main_withmysql.py], alteramos o destino do Apache. Passará a ser o script [main_withmysql.wsgi] acima referido:
- linhas 1-7: adiciona-se a pasta do script ao Python Path. Assim, a linha 6 de [main_withmysql.py] já não provoca erros;
- linhas 9-10: a importação do [main_withmysql.py] faz com que este seja executado. Além disso, faz-se referência à aplicação Flask [app], encontrada em [main_withmysql.py], com o identificador [application], necessário ao Apache no modo WSGI;
Faz-se o mesmo com o script [main_withpgres.wsgi]:
# pasta deste ficheiro
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
# adiciona-se ao syspath para que a importação seguinte seja possível
import sys
sys.path.insert(0, script_dir)
# importa-se a aplicação Flask [app], atribuindo-lhe o nome [application]
from main_withpgres import app as application
Já temos os alvos executáveis para o servidor Apache. Agora, temos de criar dois servidores virtuais, um para cada alvo.
No [<laragon>\etc\apache2\sites-enabled], criamos o ficheiro [flask-impots-withmysql.conf] (o nome atribuído não tem importância):

# pasta do script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/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 / "${ROOT}/main_withmysql.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</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 / "${ROOT}/main_withmysql.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/myprograms/laragon/etc/ssl/laragon.key
</VirtualHost>
- linha 2: a raiz da aplicação, a pasta [apache] que criámos;
- linhas 23 e 38: o destino [main_witmysql.wsgi] que criámos;
- linha 7: o servidor virtual chamar-se-á [flask-impots-withmysql];
- linha 13: a diretiva [WSGIPythonPath] permite adicionar pastas ao Python Path. Aqui, o Apache não tem conhecimento de que utilizámos um ambiente virtual para desenvolver a aplicação e de que todos os módulos utilizados pela aplicação se encontram nesse ambiente virtual. Por isso, na linha 13, adicionamos a pasta que contém todos os módulos do ambiente virtual utilizado. Uma possibilidade é copiar essa pasta para outro local no sistema de ficheiros e indicar esse local. Outra possibilidade é adicionar essa pasta ao Python Path diretamente no alvo [main_witmysql.wsgi] (esta é provavelmente a melhor solução);
- linha 16: é possível indicar ao Apache a pasta de instalação do Python no sistema de ficheiros. Normalmente, esta encontra-se no PATH da máquina e, muitas vezes, esta linha é desnecessária (foi o que aconteceu neste caso). No entanto, podem existir várias instalações do Python na máquina e a desejada pode não estar no PATH da máquina. Nesse caso, esta linha resolve o problema;
Da mesma forma, cria-se um ficheiro [flask-impots-withpgres.conf]:
# pasta do script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
# nome do site configurado por este ficheiro
# aqui, será denominado flask-impots-withmysql
# os URL serão do tipo http(s)://flask-impots-withmysql/caminho
define SITE "flask-impots-withpgres"
# 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 / "${ROOT}/main_withpgres.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</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 / "${ROOT}/main_withpgres.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/myprograms/laragon/etc/ssl/laragon.key
</VirtualHost>
Guardamos todos estes ficheiros, iniciamos o servidor Apache e os ficheiros SGBD, MySQL e PostgreSQL. A aplicação está configurada com o prefixo URL, [/do] e [with_csrftoken=False] (sem token CSRF) em [configs/parameters.py]. Solicitamos o URL e o [https://flask-impots-withmysql/do]. A resposta do servidor é a seguinte:

Solicitamos agora o URL e o [https://flask-impots-pgres/do]. A resposta é a seguinte:

Ambas as aplicações funcionam normalmente.
Agora, vamos alterar o parâmetro [WSGIScriptAlias] para [flask-impots-withmysql.conf]:
# pasta do script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
…
# 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>
- nas linhas 11 e 20, o alias WSI passa a ser [/impots];
Desligamos e reiniciamos o servidor Apache e, em seguida, solicitamos o URL [https://flask-impots-withmysql/impots/do]. A resposta do servidor é a seguinte:

Ocorreu uma falha. O URL [1] indica-nos a causa. Deveria ter sido [https://flask-impots-withmysql/impots/do/afficher-vue-authentification]. Falta o alias WSGI. Trata-se de um erro da nossa aplicação. Ela sabe lidar com um prefixo de URL (/do está presente). Poder-se-ia pensar que, ao adicionar o prefixo [/impots/do] à nossa aplicação, isso resolveria o problema anterior. Mas não. Surgem então outros tipos de problemas. O alias WSGI não se comporta como um prefixo de URL.
Vamos tentar perceber o que aconteceu. Solicitámos o URL a partir do [https://flask-impots-withmysql/impots/do]. Esperávamos que fosse apresentada a página de autenticação. No [1], acima, vemos que a aplicação solicitou a sua exibição, mas não com o URL correto. Vamos examinar o percurso da solicitação [https://flask-impots-withmysql/impots/do].
Em primeiro lugar, foi executada a seguinte rota (configs/routes.py):
# as rotas da aplicação Flask
# raiz da aplicação
app.add_url_rule(f'{prefix_url}/', methods=['GET'],
view_func=routes.index)
A rota da linha 3, no nosso exemplo, é [https://flask-impots-withmysql/impots/do]. Vemos que a rota foi despojada da parte [https://flask-impots-withmysql/impots], passando a ser simplesmente [/do]. No que diz respeito à parte [https://flask-impots-withmysql], isso é normal, uma vez que o nome do servidor não é incluído na rota. Mas verifica-se que também não inclui o alias WSGI [/impots]. Este é um ponto importante. Mesmo com um alias WSGI, as nossas rotas iniciais continuam válidas.
Agora, vamos ver o que faz a função [index] da linha 4 (configs/routes_without_csrftoken):
# 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)
Na linha 4, somos redirecionados para a função URL da função [init_session]. Na função [configs/routes.py], esta função foi associada à rota [/do/init-session/html]:
# init-session
app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
view_func=routes.init_session)
Na linha 2, no nosso teste [csrftoken_param], a cadeia está vazia. A aplicação não suporta aqui o token CSRF.
A função [init_session] está definida da seguinte forma (configs/routes_without_csrftoken):
# init-session
def init_session(type_response: str) -> tuple:
# executa-se o controlador associado à ação
return front_controller()
Na linha 4, inicia-se a cadeia de processamento da ação [init-session]. Esta cadeia termina da seguinte forma em [responses/HtmlResponse]:
…
# 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']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
A ação [init-session] é uma ação ADS (Ação «Do Something») que termina com um redirecionamento para uma vista, linha 9. É aí que reside o problema. A função [redirect], na linha 9, não adiciona automaticamente o alias WSGI à função de redirecionamento URL. É isso que nos mostra a captura de ecrã acima. Falta o alias /impostos no objeto de redirecionamento URL.
A versão seguinte resolve o problema do alias WSGI.


