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

Esta nova versão traz as seguintes atualizações:
- ela será portada para um servidor Apache/Windows;
- para realizar essa migração, a versão 17 contém todas as dependências necessárias em sua pasta [impots / http-servers/ 12]. Vale lembrar que as versões anteriores buscavam suas dependências em diferentes pastas de todo o projeto [python-flask-2020];
37.1. Relocalização das dependências do aplicativo

Vale lembrar que o gerenciamento das dependências do aplicativo é feito no script [syspath]. Na versão anterior, esse script era o seguinte:
def configure(config: dict) -> dict:
import os
# pasta deste arquivo
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, faixas
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 de visualizações
f"{script_dir}/../models_for_views",
]
# definimos o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# fazemos a configuração
return {
"root_dir": root_dir,
"script_dir": script_dir
}
É necessário realocar 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 arquivo
script_dir = os.path.dirname(os.path.abspath(__file__))
# dependências
absolute_dependencies = [
# entidades do aplicativo
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 de visualizações
f"{script_dir}/../models_for_views",
]
# definimos o syspath
import sys
# adiciona-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 desenvolvedor
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)
# a configuração é restaurada
return {
"script_dir": script_dir,
}
- linhas 8 a 27: todas as dependências agora são relativas à variável [script_dir] da linha 5;
- linhas 42-45: a variável [root_dir] foi removida da configuração do syspath;
- linha 10: as entidades do aplicativo estão na pasta [entities] [1];
- linha 12: a camada [dao] está na pasta [layers/dao] [2];
- linha 14: a camada [métier] está na pasta [layers/métier] [2];
- linha 16: os utilitários [Logger, SendMail] estão na pasta [utilities] [3];
- linhas 29-40: calcula-se o Python Path do aplicativo sem importar o módulo [myutils];

37.2. Testes
Nesta fase, a versão 17 deve estar funcionando. Verifique isso.
37.3. Portagem de um aplicativo Python/Flask para um servidor Apache/Windows
37.3.1. Fontes
Para realizar a portabilidade de um aplicativo Flask para Apache/Windows, precisei 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 desse link, exceto para a configuração do servidor Apache. Para isso, usei 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. Esse servidor é descrito |aqui|. O link [https://www.fullstackpython.com/wsgi-servers.html] descreve o funcionamento dos servidores WSGI. Existem diferentes |servidores WSGI|. Um deles é o servidor Apache operando no modo WSGI. Essa é a solução adotada aqui porque o Laragon, que instalamos, vem com um servidor Apache.
Para que o servidor Apache possa hospedar uma aplicação em Python, precisamos instalar o módulo Python [mod_wsgi]. A instalação desse módulo é delicada, pois, 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 é instalar a versão atual do Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].
Se o Visual Studio for necessário apenas para o [mod_wsgi], é possível limitar a instalação ao ambiente C++:

Depois que o compilador C++ estiver instalado, a instalação do módulo [mod_wsgi] é feita em um 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]. Esse valor corresponde ao caminho do servidor Apache no sistema de arquivos. Aqui, esse caminho é [<laragon>\bin\apache\httpd-2.4.35-win64-VC15], onde <laragon> é a pasta de instalação do Laragon. É possível obter esse caminho de várias maneiras. Aqui está um exemplo obtido a partir de uma das opções do Laragon:

Em [1-3], o arquivo [httpd.conf] é o arquivo de configuração principal do servidor Apache. O arquivo em questão é então aberto em um editor de texto (Notepad++, conforme mostrado 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 arquivo de configuração principal [httpd.conf]:

Vamos para o final do arquivo [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 arquivo [httpd.conf], no final do arquivo. Ela indica ao servidor Apache onde se encontra um elemento do módulo [mod_wsgi] que acabamos de instalar;
Uma maneira simples de obter o caminho da linha 8 é executar o seguinte comando em um 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 arquivo [httpd.conf]. No meu caso, a linha 3 acima causava um erro (módulo [encodings] ausente). Por isso, ela não foi incluída no arquivo [httpd.conf]. Apenas a linha 2 foi inserida nele. O significado dos diferentes parâmetros do módulo [mod_wsgi] que podem ser usados nos arquivos 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 usar URL e [https://serveur/chemin];
Para configurar o Apache para hospedar uma aplicação Flask, o link mencionado |acima| utiliza servidores virtuais. O Laragon também oferece a gestão de servidores virtuais:

- em [1-3], solicitamos ao Laragon que crie automaticamente hosts virtuais;
O próximo passo é criar um projeto web com o Laragon:
![]() |
![]() | ![]() |
- no [1-3], cria-se um projeto PHP vazio;
- em [4-8], o Laragon criou um site virtual chamado [auto.projet-test.test], configurado pelo arquivo [auto.projet-test.test.conf] [8] da pasta [sites-enabled] [7]. Essa pasta está localizada no endereço [<laragon>\etc\apache2\sites-enabled], onde [laragon] é a pasta de instalação do Laragon;
Embora isso não faça parte do que estamos fazendo no momento, talvez você tenha curiosidade de dar uma olhada nesse site [projet-test] que acabamos de criar:

- em [1-5], foi criado um projeto vazio. Trata-se de um projeto PHP localizado na pasta [<laragon>/www], onde [laragon] é a pasta de instalação do Laragon;
Agora, vamos examinar o arquivo [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 arquivos, do projeto criado;
- linha 2: o nome do servidor virtual. Os arquivos URL para esse 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, vamos iniciar o servidor Apache e o PHP:
Em seguida, usando um navegador, acessamos o 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 de sua pasta (lista de seu conteúdo), índice vazio;
Agora, vamos solicitar o URL protegido por [https://projet-test.test/]:

- em [1-2], obtemos a mesma resposta que anteriormente, mas com o protocolo HTTPS [1];
A criação do servidor virtual [projet-test.test] gerou uma nova entrada no arquivo [<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] em um navegador, a solicitação é enviada para o endereço 127.0.0.1 na porta 80. É então o servidor Apache da máquina local (localhost) que responde.
Pode-se questionar por que, ao digitar a solicitação [http://projet-test.test/], o servidor Apache utiliza a configuração do arquivo [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf]:

Para entender isso, é preciso observar o que o navegador envia ao servidor Apache ao fazer essa solicitação. Vamos testá-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 conexão criptografada entre o cliente web (neste caso, o Postman) e o servidor Apache. Essa conexão criptografada é realizada por meio de certificados trocados entre o cliente e o servidor. É o servidor que inicia o diálogo para o estabelecimento da conexão criptografada, enviando ao cliente um certificado de segurança. Para que esse certificado seja aceito pelo cliente, ele precisa estar assinado, ou seja, adquirido de empresas autorizadas a emitir certificados de segurança. Quando ativamos 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 exibe um aviso ao receber um certificado autoassinado. É o que o Postman faz no [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 no [5];
Clicamos no link [5] para desativar a verificação SSL (Secure Sockets Layer). 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 veríamos em um navegador tradicional. Agora, vamos observar o diálogo cliente/servidor no console do Postman (Ctrl-Alt-C):
- linha 6: o cabeçalho HTTP [Host] especifica o nome do servidor de destino do cliente web. Esse é o princípio dos servidores virtuais. Em um mesmo endereço IP (aqui, 127.0.0.1), um servidor web pode hospedar vários sites com nomes diferentes. O cabeçalho HTTP [Host] permite que o cliente indique a qual servidor (neste caso, com o endereço 127.0.0.1) ele está se conectando;
E o que o Apache faz agora?
Ao iniciar, o Apache lê todos os arquivos de configuração encontrados na pasta [[<laragon>\etc\apache2\sites-enabled]:

Cada arquivo de configuração define um servidor virtual. Por exemplo, no arquivo [auto.projet-test.test.conf], encontramos 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 arquivo [auto.projet-test.test.conf] contém a configuração desse servidor virtual. Como ele lê, ao iniciar, todos os arquivos 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:
ele reconhece que a solicitação é direcionada ao servidor virtual [projet-test.test] (linha 6) e que este existe. Em seguida, ele 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. Ele servirá para executar um aplicativo Python Flask instalado na pasta [Apache] da versão 17, que está sendo implantada no servidor Apache:
Colocamos na pasta [http-servers/12/apache/exemple] o aplicativo desenvolvido 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
# precisamos 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
# aplicativo 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 de 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())
# geramos o documento a ser enviado 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 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). Esse nome é obrigatório. Se a aplicação Flask for referenciada com outro identificador, ela não funcionará, exibindo uma mensagem de erro indicando que não encontrou o URL solicitado. Essa mensagem de erro não fornece nenhuma indicação sobre a origem do erro. Portanto, é preciso estar atento a esse ponto.
O arquivo HTML referenciado 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 hospedará essa aplicação. Ele será configurado pelo arquivo [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] (lembre-se de que esse nome é livre – o Apache lê todos os arquivos presentes em [sites-enabled] para identificar os sites virtuais hospedados);
Primeiramente, obtemos esse arquivo copiando o arquivo [auto.projet-test.test.conf] e, em seguida, o modificamos.

O arquivo [date-time-server.conf] ficará da seguinte forma:
# pasta do script Python-Flask do aplicativo
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 arquivo
# aqui ele se chamará date-time-server
# os URL serão do tipo http(s)://date-time-server/caminho
define SITE "date-time-server"
# insira o endereço IP 127.0.0.1 para o site SITE no arquivo 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 protegidas 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: atribuímos um nome ao servidor virtual configurado pelo arquivo;
- linha 2: definimos 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 uma solicitação. Indica-se aqui que as solicitações para o servidor [date-time-server] são processadas pelo script Python [date_time_server.py]. Essa diferença em relação ao arquivo [auto.projet-test.test.conf] se deve ao fato de que esse arquivo configurava um servidor PHP, enquanto o arquivo [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 do aplicativo terão o formato [http(s)://date-time-server/chemin];
- nas linhas 14 e 27, é possível definir outra raiz para o aplicativo, por exemplo, [WSGIScriptAlias /show]. Assim, os URL do aplicativo terão o formato [http(s)://show/date-time-server/chemin];
Também precisamos adicionar uma linha ao arquivo [<windows>/system32/drivers/etc/hosts]:
Adicionamos a linha 25 para atribuir os endereços IP e [127.0.0.1] ao servidor virtual [date-time-server].
Vamos verificar tudo isso. Iniciamos o servidor Apache:

Em seguida, acessamos URL [https://date-time-server] com um navegador:

- em [1], a página URL solicitada;
- em [3], a resposta do servidor;
- em [2], o navegador indica que a conexão HTTPS não é segura, pois detectou que o certificado enviado pelo servidor Apache era autoassinado;
Agora, no arquivo [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 é aplicada imediatamente pelo servidor Apache. É preciso recarregá-lo:

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

37.5. Portação do aplicativo de cálculo de impostos para Apache / Windows
A pasta [apache] [2] é obtida inicialmente por meio da cópia da pasta [main]. É importante que elas estejam no mesmo nível para que os caminhos do script [syspath.py], copiado de [1] para [2], permaneçam válidos. Para não interferir no funcionamento da aplicação [impots / http-servers/ 12], colocamos no [apache] a configuração que será executada pelo servidor Apache;

- o arquivo [config] de [2] é o mesmo que [config] de [1];
- o arquivo [syspath] de [2] é o mesmo que [syspath] de [1];
- o arquivo [main_withmysql], derivado de [2], é o arquivo [main], derivado de [1], com as seguintes alterações:
O script principal [main] recebia um parâmetro [mysql / pgres] que indicava qual SGBD deveria ser utilizado. O script [main_withmysql] utiliza o SGBD e o MySQL:
# espera-se um parâmetro mysql ou pgres
import os
import sys
# configurando o aplicativo 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 arquivo [main_withpgres], derivado de [2], é o arquivo [main], derivado de [1], com as seguintes alterações: ele utiliza o SGBD e o PostgreSQL:
# espera-se 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 isso, crie o script [main_withmysql.wsgi] (o sufixo utilizado não importa) 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], como foi feito anteriormente com o script [date_time_server.py]. Mas seria necessário modificá-lo um pouco:
- ao contrário do modo de execução com um script de console, no Apache, a pasta que contém o alvo [main_withmysql.py] não faz parte do Python Path. Portanto, a linha 6 do script [main_withmysql.py] gera um erro;
- a segunda modificação que seria necessário fazer é que, no [main_withmysql], a aplicação Flask seja referenciada pelo identificador [app]. Sabemos que, para o Apache / WSGI, ela também deve ser referenciada por um identificador [application];
- em vez de modificar [main_withmysql.py], alteramos o destino do Apache. A partir de agora, será o script [main_withmysql.wsgi] acima:
- linhas 1-7: colocamos a pasta do script no Python Path. Assim, a linha 6 do [main_withmysql.py] não gera mais erro;
- linhas 9-10: a importação do [main_withmysql.py] faz com que ele seja executado. Além disso, faz-se referência à aplicação Flask [app], encontrada em [main_withmysql.py], com o identificador [application], necessário para o Apache no modo WSGI;
Faz-se o mesmo com o script [main_withpgres.wsgi]:
# pasta deste arquivo
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
# adiciona-se ao syspath para que a importação a seguir seja possível
import sys
sys.path.insert(0, script_dir)
# importamos o aplicativo Flask [app], atribuindo-lhe o nome [application]
from main_withpgres import app as application
Agora já temos os alvos executáveis para o servidor Apache. Precisamos agora criar dois servidores virtuais, um para cada alvo.
No [<laragon>\etc\apache2\sites-enabled], criamos o arquivo [flask-impots-withmysql.conf] (o nome dado não importa):

# 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 arquivo
# aqui ele se chamará flask-impots-withmysql
# os URL serão do tipo http(s)://flask-impots-withmysql/caminho
define SITE "flask-impots-withmysql"
# insira o endereço IP 127.0.0.1 para o site SITE no arquivo c:/windows/system32/drivers/etc/hosts
# insira aqui os caminhos das bibliotecas Python a serem utilizadas — separe-as por vírgulas
# aqui estão as bibliotecas de um ambiente virtual do 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:/Arquivos de Programas/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 do aplicativo, a pasta [apache] que criamos;
- linhas 23, 38: o destino [main_witmysql.wsgi] que criamos:
- linha 7: o servidor virtual se chamará [flask-impots-withmysql];
- linha 13: a diretiva [WSGIPythonPath] permite adicionar pastas ao Python Path. Aqui, o Apache não tem conhecimento de que utilizamos um ambiente virtual para desenvolver a aplicação e de que todos os módulos utilizados pela aplicação estão nesse ambiente virtual. Portanto, na linha 13, adicionamos a pasta que contém todos os módulos do ambiente virtual utilizado. Uma opção é copiar essa pasta para outro local no sistema de arquivos e indicar esse local. Outra opção é adicionar essa pasta ao Python Path diretamente no destino [main_witmysql.wsgi] (essa provavelmente é a melhor solução);
- linha 16: é possível indicar ao Apache a pasta de instalação do Python no sistema de arquivos. Normalmente, ela está no PATH da máquina e, muitas vezes, essa linha é desnecessária (foi o caso aqui). No entanto, pode haver várias instalações do Python na máquina e a desejada não estar no PATH da máquina. Nesse caso, essa linha resolve o problema;
Da mesma forma, cria-se um arquivo [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 arquivo
# aqui, ele se chamará flask-impots-withmysql
# os URL serão do tipo http(s)://flask-impots-withmysql/caminho
define SITE "flask-impots-withpgres"
# insira o endereço IP 127.0.0.1 para o site SITE no arquivo c:/windows/system32/drivers/etc/hosts
# insira aqui os caminhos das bibliotecas Python a serem utilizadas — separe-as por vírgulas
# aqui estão as bibliotecas de um ambiente virtual do 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:/Arquivos de Programas/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/...
#, em que [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>
Salvamos todos esses arquivos, iniciamos o servidor Apache e os arquivos SGBD, MySQL e PostgreSQL. A aplicação está configurada com o prefixo de 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:

Agora solicitamos 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/...
#, em que [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] nos indica a causa. Deveria ter sido [https://flask-impots-withmysql/impots/do/afficher-vue-authentification]. O alias WSGI está faltando. Trata-se de um erro da nossa aplicação. Ela sabe lidar com um prefixo de URL (/do está presente). Poderíamos pensar que, ao adicionar o prefixo [/impots/do] ao nosso aplicativo, isso resolveria o problema anterior. Mas não. Encontramos então outros tipos de problemas. O alias WSGI não se comporta como um prefixo de URL.
Vamos tentar entender o que aconteceu. Solicitamos o URL a partir do [https://flask-impots-withmysql/impots/do]. Esperávamos que a tela de autenticação fosse exibida. No [1], acima, vemos que o aplicativo solicitou sua exibição, mas não com o URL correto. Vamos examinar o caminho da solicitação [https://flask-impots-withmysql/impots/do].
Primeiramente, a seguinte rota (configs/routes.py) foi executada:
# as rotas do aplicativo Flask
# diretório 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 teve a parte [https://flask-impots-withmysql/impots] removida, passando a ser simplesmente [/do]. Quanto à parte [https://flask-impots-withmysql], isso é normal, pois o nome do servidor não é incluído na rota. Mas percebe-se que ele também não inclui o alias WSGI [/impots]. Esse é um ponto importante. Mesmo com um alias WSGI, nossas rotas iniciais permanecem 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], essa 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, em nosso teste [csrftoken_param], a cadeia está vazia. O aplicativo não processa 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]. Essa cadeia termina da seguinte forma em [responses/HtmlResponse]:
…
# 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
A ação [init-session] é uma ação ADS (Ação “Do Something”) que termina com um redirecionamento para uma visualização, 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. É o que mostra a captura de tela acima. Falta o alias /impostos no objeto de redirecionamento URL.
A versão a seguir oferece uma solução para o problema do alias WSGI.


