Skip to content

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

Image

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

Image

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];

Image

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++:

Image

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:

Image

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):

Image

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]:

Image

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:

Image

  • 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:

Image

  • 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:

Image

  • 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/]:

Image

  • 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/]:

Image

  • 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]:

Image

# Copyright (c) 1993-2009 Microsoft Corp.
#
# Este é um ficheiro de exemplo HOSTS utilizado pelo Microsoft TCP/IP para Windows.
#
# Este ficheiro contém os mapeamentos dos endereços do IP para nomes de anfitrião. Cada
# entrada deve ser colocada numa linha individual. O endereço IP deve
# ser colocado na primeira coluna, seguido do nome de anfitrião correspondente.
# O endereço IP e o nome do anfitrião devem ser separados por, pelo menos, um
# espaço.
#
# Além disso, podem ser inseridos comentários (como estes) em linhas individuais
# linhas individuais ou a seguir ao nome da máquina, indicados pelo símbolo «#».
#
# Por exemplo:
#
#       102.54.9    4.97 rhino.         acme.com # servidor de origem
#        38.25.    63.10 x             .acme.com # host do cliente x

# a resolução do nome «localhost» é tratada no próprio DNS.
#     127.0.0.1       localhost
#     ::1             localhost

127.0.0.1      projet-test.test     #A magia do Laragon!   
  • 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]:

Image

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:

Image

  • 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:

Image

Recebemos a mesma página que com um navegador tradicional. Agora, vejamos o diálogo cliente/servidor na consola do Postman (Ctrl-Alt-C):

GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: d153f711-ad99-4e1d-93c3-61c25374d1be
Host: projet-test.test
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.1 200 OK
Date: Fri, 14 Aug 2020 09:19:24 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.1b PHP/7.2.19 mod_wsgi/4.7.1 Python/3.8
Content-Length: 161
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html;charset=UTF-8
  • 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]:

Image

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:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: d153f711-ad99-4e1d-93c3-61c25374d1be
Host: projet-test.test
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

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:

Image

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.

Image

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]:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# Este é um ficheiro de exemplo HOSTS utilizado pelo Microsoft TCP/IP para Windows.
#
# Este ficheiro contém os mapeamentos dos endereços do IP para nomes de anfitrião. Cada
#deve ser mantida numa linha separada. O endereço IP deve
# ser colocado na primeira coluna, seguido do nome do anfitrião correspondente.
# O endereço IP e o nome do anfitrião devem ser separados por, pelo menos, um
# espaço.
#
# Além disso, podem ser inseridos comentários (como estes) em linhas individuais
# linhas individuais ou a seguir ao nome da máquina, indicados pelo símbolo «#».
#
# Por exemplo:
#
#       102.54.9    4.97 rhino.         acme.com # servidor de origem
#        38.25.    63.10 x             .acme.com # host do cliente x

# a resolução do nome «localhost» é tratada no próprio DNS.
#     127.0.0.1       localhost
#     ::1             localhost

127.0.0.1    flask-impots-withmysql        # curso de Python - aplicação de impostos com MySQL
127.0.0.1    flask-impots-withpgres        # curso de Python - aplicação de impostos com PostgreSQL
127.0.0.1    date-time-server            # aulas de Python - hora e data atuais
127.0.0.1           projet-test.test                #A magia do Laragon!

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:

Image

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

Image

  • 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:

Image

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

Image

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;

Image

  • 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:

# 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_withmysql import app as application

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):

Image


# 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:

Image

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

Image

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:

Image

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.