Skip to content

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

Image

Esta nova versão introduz as seguintes alterações:

  • será portado para um servidor Apache/Windows;
  • Para realizar esta portabilidade, a versão 17 contém todas as dependências necessárias na pasta [impots/http-servers/12]. Note-se que as versões anteriores obtinham as suas dependências de várias pastas espalhadas por todo o projeto [python-flask-2020];

37.1. Relocalização das dependências da aplicação

Image

Note que a gestão das dependências da aplicação é tratada no script [syspath]. Na versão anterior, este script era o seguinte:

def configure(config: dict) -> dict:
    import os

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  root path
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    #  dependencies
    absolute_dependencies = [
        #  project files
        #  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",
        #  Constants, slices
        f"{root_dir}/impots/v05/entities",
        #  Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #  we return the configuration
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }

Precisamos de reposicionar todas as dependências cujo caminho absoluto depende da variável [root_dir] na linha 8, ou seja, as linhas 13–26.

O script [syspath] para a nova versão será o seguinte:

def configure(config: dict) -> dict:
    import os

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  dependencies
    absolute_dependencies = [
        #  application entities
        f"{script_dir}/../entities",
        #  layer [dao]
        f"{script_dir}/../layers/dao",
        #  business] layer
        f"{script_dir}/../layers/métier",
        #  utilities
        f"{script_dir}/../utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    import sys
    #  we add the project's absolute dependencies
    for directory in absolute_dependencies:
        #  we check the existence of the file
        existe = os.path.exists(directory) and os.path.isdir(directory)
        if not existe:
            #  the developer is notified
            raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
        else:
            #  insert the folder at the beginning of the syspath
            sys.path.insert(0, directory)

    #  we return the configuration
    return {
        "script_dir": script_dir,
    }
  • linhas 8–27: todas as dependências são agora relativas à variável [script_dir] na linha 5;
  • linhas 42–45: a variável [root_dir] foi removida 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 [business] está na pasta [layers/business] [2];
  • linha 16: os utilitários [Logger, SendMail] estão na pasta [utilities] [3];
  • linhas 29–40: o Python Path da aplicação é calculado sem importar o módulo [myutils];

Image

37.2. Testes

Nesta altura, a versão 17 deverá funcionar. Verifique-a.

37.3. Portar uma aplicação Python/Flask para um servidor Apache/Windows

37.3.1. Fontes

Para portar uma aplicação Flask para 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 isso, utilizei um exemplo de configuração de servidor Apache do Laragon.

37.3.2. Instalação do módulo mod_wsgi do Python

A aplicação Python/Flask que desenvolvemos utilizou o servidor WSGI (Web Server Gateway Interface) [werkzeug] incluído no Flask. Este servidor é descrito |aqui|. O link [https://www.fullstackpython.com/wsgi-servers.html] descreve como funcionam os 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 hospedar uma aplicação Python, precisamos de instalar o módulo Python [mod_wsgi]. A instalação deste módulo é complicada, pois envolve uma compilação em C++. Para concluir a instalação com sucesso, é necessário um compilador Microsoft C++. Uma solução simples é instalar a versão mais recente do Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].

Se não precisar do Visual Studio para nada além do [mod_wsgi], pode limitar a instalação ao ambiente C++:

Image

Assim que o compilador C++ estiver instalado, o módulo [mod_wsgi] é instalado num terminal do 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: Definimos o valor da variável de ambiente [MOD_WSGI_APACHE_ROOTDIR]. Este valor corresponde à localização do servidor Apache no sistema de ficheiros. Aqui, essa localização é [<laragon>\bin\apache\httpd-2.4.35-win64-VC15], onde <laragon> é a pasta de instalação do Laragon. Pode obter esta localização de várias formas. Aqui está um exemplo obtido utilizando 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++ 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. Configurar o servidor Apache do Laragon

Vamos configurar o servidor Apache do Laragon. Começamos pelo seu ficheiro de configuração principal [httpd.conf]:

Image

Vamos até 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 [httpd.conf] existente, no final do ficheiro. Ela indica ao servidor Apache onde encontrar um componente do módulo [mod_wsgi] que acabámos de instalar;

Uma maneira fácil de obter o caminho para a linha 8 é executar o seguinte comando num terminal do 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 as linhas 2 e 3 devem ser adicionadas ao final do ficheiro [httpd.conf]. No meu caso, a linha 3 acima causou um erro (falta o módulo [encodings]). Por isso, não foi incluída no ficheiro [httpd.conf]. Apenas a linha 2 foi adicionada. Os significados dos vários parâmetros do módulo [mod_wsgi] que podem ser utilizados nos ficheiros de configuração do Apache estão descritos |aqui|.

A seguir, vamos ativar o protocolo HTTPS no servidor Apache:

Image

  • Em [1-4], ativamos o protocolo HTTPS no servidor Apache;

A partir de agora, poderemos utilizar URLs [https://serveur/chemin];

Para configurar o Apache para servir uma aplicação Flask, o link mencionado |acima| utiliza hosts virtuais. O Laragon também oferece gestão de hosts virtuais:

Image

  • Em [1-3], solicitamos ao Laragon que crie automaticamente hosts virtuais;

O próximo passo é criar um projeto web com o Laragon:

 
  • Em [1-3], crie 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] na pasta [sites-enabled] [7]. Esta pasta encontra-se em [<laragon>\etc\apache2\sites-enabled], onde [laragon] é a pasta de instalação do Laragon;

Embora isto não faça parte do que estamos a fazer neste momento, talvez tenha curiosidade em consultar o site [test-project] 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], onde [laragon] é a pasta de instalação do Laragon;

Agora, vamos examinar 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: o diretório raiz do projeto criado no sistema de ficheiros;
  • linha 2: o nome do servidor virtual. Os URLs para este servidor terão o formato [http(s)://test-project.test/path];
  • linhas 4–12: configuração do site virtual para a porta 80 (linha 4) e o protocolo HTTP;
  • linhas 14–27: configuração do host virtual para a porta 443 (linha 14) e o protocolo HTTPS;

Vamos ver como funciona um servidor virtual. Primeiro, vamos iniciar os servidores Apache e PHP:

Image

Em seguida, utilizando um navegador, solicitamos a URL [http://projet-test.test/]:

Image

  • em [1], o URL solicitado;
  • em [2], foi utilizado o protocolo HTTP;
  • em [3], como o projeto [projet-test] está vazio, obtemos o índice da sua pasta (lista do seu conteúdo), um índice vazio;

Agora, vamos solicitar a URL segura [https://projet-test.test/]:

Image

  • Em [1-2], obtemos a mesma resposta que antes, mas utilizando o protocolo HTTPS [1];

A criação do servidor virtual [test-project.test] criou uma nova entrada no ficheiro [<windows>/system32/drivers/etc/hosts]:

Image


# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host
 
# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost
 
127.0.0.1      projet-test.test     #laragon magic!   
  • Linha 23: O endereço IP para o nome [test-project.test] é 127.0.0.1, ou seja, o endereço de [localhost] (linha 20), a máquina local. Assim, quando digita o URL [http://projet-test.test/chemin] num navegador, o pedido é enviado para o endereço 127.0.0.1 na porta 80. O servidor Apache na máquina local (localhost) responde então.

Pode perguntar-se por que razão, quando digita o pedido [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, precisamos de ver o que o navegador envia ao servidor Apache ao efetuar este pedido. Vamos fazer isto com o Postman:

Image

  • Em [1-3], enviamos uma solicitação HTTPS [1];
  • em [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 (aqui, 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 processo de estabelecimento da ligação encriptada, enviando um certificado de segurança ao cliente. Para que este certificado seja aceite pelo cliente, tem de ser 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. É isso que o Postman faz em [4]. A maioria dos clientes web oferece então a possibilidade de desativar a verificação do certificado de segurança enviado pelo servidor. É isso que o Postman oferece em [5];

Clicamos no link [5] para desativar a verificação SSL (Secure Sockets Layer). O SSL/TLS (Transport Layer Security) é um protocolo de segurança que cria um canal de comunicação seguro entre duas máquinas na Internet. Este é o protocolo utilizado aqui pelo Apache. A resposta é a seguinte:

Image

Recebemos a mesma página que com um navegador tradicional. Agora, vamos ver 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 de destino do cliente web. Este é o princípio subjacente aos servidores virtuais. Num único 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 ao cliente especificar a que servidor (aqui, o que se encontra no endereço 127.0.0.1) se está a dirigir;

Então, o que faz o Apache?

Quando é iniciado, o Apache lê todos os ficheiros de configuração encontrados no diretório [[<laragon>\etc\apache2\sites-enabled]]:

Image

Cada ficheiro de configuração define um servidor virtual. Por exemplo, no ficheiro [auto.projet-test.test.conf], encontrará a seguinte linha:


define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"

A linha 2 define o servidor virtual [test-project.test]. O ficheiro [auto.projet-test.test.conf] é a configuração deste servidor virtual. Como lê todos os ficheiros de configuração na pasta [<laragon>\etc\apache2\sites-enabled] no arranque, o servidor Apache sabe que existe um servidor virtual chamado [projet-test.test]. Assim, quando recebe o seguinte pedido HTTPS do cliente Postman:

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 o pedido é dirigido ao servidor virtual [test-project.test] (linha 6) e que este existe. Em seguida, utiliza a configuração do servidor virtual [test-project.test] para responder ao cliente Postman.

37.4. Criar o seu primeiro servidor virtual Apache

Agora que sabemos para que servem os servidores virtuais e como configurá-los, vamos criar um. Será utilizado para executar uma aplicação Python Flask instalada numa pasta [Apache] da versão 17, atualmente a ser implementada no servidor Apache:

Colocámos a aplicação desenvolvida na secção |link| — um serviço web de data/hora — na pasta [http-servers/12/apache/example]:

Image

O servidor [date_time_server.py] é o seguinte:

#  imports
import os
import sys
import time

#  we need to put the modules folder in the Python Path
#  for porting to apache 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

#  flask application
script_dir = os.path.dirname(os.path.abspath(__file__))
application = Flask(__name__, template_folder=f"{script_dir}")

#  Home URL
@application.route('/')
def index():
    #  dispatch time to customer
    #  time.localtime: number of milliseconds since 01/01/1970
    #  time.strftime formats time and date
    #  date-time display format
    #  d: 2-digit day
    #  m: 2-digit month
    #  y: 2-digit year
    #  H: hour 0.23
    #  M: minutes
    #  S: seconds

    #  current date / time
    time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
    #  generate the document to be sent to the customer
    page = {"date_heure": time_of_day}
    document = render_template("date_time_server.html", page=page)
    print("document", type(document), document)
    #  HTTP response to customer
    response = make_response(document)
    print("response", type(response), response)
    return response

#  hand only
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 referenciar a aplicação Flask com um identificador diferente, a aplicação não funcionará e exibirá uma mensagem de erro indicando 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, deve ter cuidado com isto.

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 hospeda esta aplicação. Será configurado através do ficheiro [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] (note que este nome é arbitrário — o Apache lê todos os ficheiros em [sites-enabled] para descobrir os sites virtuais hospedados);

Primeiro, obtemos este ficheiro copiando o ficheiro [auto.projet-test.test.conf] e, em seguida, modificamo-lo.

Image

O ficheiro [date-time-server.conf] terá o seguinte aspeto:

#  application python-flask script file
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"

#  name of the website configured by this file
#  here it will be called date-time-server
#  the URL will be of type http(s)://date-time-server/path
define SITE "date-time-server"

#  set address IP 127.0.0.1 for site SITE in c:/windows/system32/drivers/etc/hosts

#  URL HTTP
<VirtualHost *:80>
    #  with the alias / the URL will take the form http(s)://date-time-server/path/...
    WSGIScriptAlias / "${ROOT}/date_time_server.py"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

#  URL secured with HTTPS
<VirtualHost *:443>
    #  with the alias / the URL will take the form http(s)://date-time-server/path/...
    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: Atribua um nome ao servidor virtual configurado pelo ficheiro;
  • linha 2: define o valor da variável [ROOT] utilizada nas linhas 14 e 27;
  • Linhas 14 e 27: Especifique o caminho para o script Python que deve ser executado quando o servidor virtual receber um pedido. Aqui, especificamos que as solicitações para o servidor [date-time-server] são tratadas pelo script Python [date_time_server.py]. Esta diferença em relação ao ficheiro [auto.projet-test.test.conf] decorre do facto de o primeiro ter configurado um servidor PHP, enquanto o ficheiro [date-time-server.conf] configura um servidor Python;
  • Linhas 14 e 27: O atributo [WSGIScriptAlias /] especifica aqui que a raiz do servidor [date-time-server] será [/]. Assim, os URLs da aplicação assumirão a forma [http(s)://date-time-server/path];
  • Linhas 14 e 27: Podemos atribuir uma raiz diferente à aplicação, por exemplo [WSGIScriptAlias /show]. Neste caso, os URLs da aplicação assumirão o formato [http(s)://show/date-time-server/path];

Também precisamos de adicionar uma linha ao ficheiro [<windows>/system32/drivers/etc/hosts]:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost

127.0.0.1    flask-impots-withmysql        # cours python - appli impôts avec MySQL
127.0.0.1    flask-impots-withpgres        # cours python - appli impôts avec PostgreSQL
127.0.0.1    date-time-server            # cours python - heure et date du moment
127.0.0.1           projet-test.test                #laragon magic!

Adicionamos a linha 25 para atribuir o endereço IP [127.0.0.1] ao servidor virtual [date-time-server].

Vamos verificar tudo. Iniciamos o servidor Apache:

Image

Em seguida, acedemos à URL [https://date-time-server] utilizando um navegador:

  • em [1], a URL solicitada;
  • em [3], a resposta do servidor;
  • em [2], o navegador indica que a ligação HTTPS não é segura porque detetou que o certificado enviado pelo servidor Apache era autoassinado;

Agora, no ficheiro [date-time-server.conf], vamos adicionar um alias às linhas 14 e 27:


    WSGIScriptAlias /show-date-time "${ROOT}/date_time_server.py"

A alteração não é reconhecida imediatamente pelo servidor Apache. É necessário recarregá-lo:

Image

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

Image

37.5. Portar a aplicação de cálculo de impostos para o Apache / Windows

O diretório [apache] [2] é inicialmente criado através da cópia do diretório [main]. É importante que ambos estejam no mesmo nível, para que os caminhos no script [syspath.py] copiados de [1] para [2] permaneçam válidos. Para evitar interferir com a aplicação [impots / http-servers/ 12] em funcionamento, colocamos a configuração a ser executada pelo servidor Apache em [apache];

Image

  • o ficheiro [config] em [2] é igual ao ficheiro [config] em [1];
  • o ficheiro [syspath] em [2] é igual ao ficheiro [syspath] em [1];
  • o ficheiro [main_withmysql] em [2] é o ficheiro [main] de [1] com as seguintes modificações:

O script principal [main] recebeu um parâmetro [mysql / pgres] que lhe indicava qual o SGBD a utilizar. O script [main_withmysql] utiliza o SGBD MySQL:

#  a mysql or pgres parameter is expected
import os
import sys

#  configure the application with MySQL
import config
config = config.configure({'sgbd': "mysql"})

#  dependencies
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

A linha 7 define o SGBD como MySQL.

  • O ficheiro [main_withpgres] de [2] é o ficheiro [main] de [1] com as seguintes alterações: utiliza o SGBD PostgreSQL:
#  a mysql or pgres parameter is expected
import os
import sys

#  configure the application with MySQL
import config
config = config.configure({'sgbd': "pgres"})

#  dependencies
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

Na linha 7, definimos o SGBD como PostgreSQL.

Depois de fazer isso, criamos o seguinte script [main_withmysql.wsgi] (o sufixo utilizado não importa):

#  folder of this file
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

#  we add it to the syspath so that the following import is possible
import sys
sys.path.insert(0, script_dir)

#  we import the Flask application [app], giving it the name [application]
from main_withmysql import app as application

O script [main_withmysql.wsgi] será o alvo executado pelo servidor Apache no modo WSGI:

  • O alvo do servidor Apache poderia ter sido o script [main_withmysql.py], tal como foi feito anteriormente com o script [date_time_server.py]. Mas isso teria exigido uma ligeira modificação:
    • ao contrário do que acontece ao executar um script de consola, com o Apache, o diretório 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] causa um erro;
    • a segunda alteração que teria sido necessária é que, em [main_withmysql], a aplicação Flask é 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. Agora será o script [main_withmysql.wsgi] acima:
    • linhas 1–7: adicionamos o diretório do script ao Python Path. Como resultado, a linha 6 do [main_withmysql.py] já não causa um erro;
    • linhas 9–10: importar [main_withmysql.py] aciona a sua execução. Além disso, referenciamos a aplicação Flask [app] encontrada em [main_withmysql.py] utilizando o identificador [application] exigido pelo Apache no modo WSGI;

Fazemos o mesmo com o script [main_withpgres.wsgi]:

#  folder of this file
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

#  we add it to the syspath so that the following import is possible
import sys
sys.path.insert(0, script_dir)

#  we import the Flask application [app], giving it the name [application]
from main_withpgres import app as application

Agora temos os alvos executáveis para o servidor Apache. Precisamos agora de criar dois servidores virtuais, um para cada alvo.

Em [<laragon>\etc\apache2\sites-enabled], criamos o ficheiro [flask-imports-withmysql.conf] (o nome não importa):

Image


# dossier du script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
 
# nom du site web configuré par ce fichier
# ici il s'appellera flask-impots-withmysql
# les URL seront du type http(s)://flask-impots-withmysql/path
define SITE "flask-impots-withmysql"
 
# mettre l'adresse IP 127.0.0.1 pour site SITE dans c:/windows/system32/drivers/etc/hosts
 
# mettre ici les chemins des bibliothèques Python à utiliser - les séparer par des virgules
# ici les bibliothèques d'un environnement virtuel Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
 
# Python Home - nécessaire uniquement s'il y a plusieurs versions de Python installées
# WSGIPythonHome "C:/Program Files/Python38"
 
# URL HTTP
<VirtualHost *:80>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
# URL sécurisées avec HTTPS
<VirtualHost *:443>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans 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, 38: o destino [main_withmysql.wsgi] que criámos:
  • linha 7: o servidor virtual será denominado [flask-impots-withmysql];
  • linha 13: a diretiva [WSGIPythonPath] permite-nos 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. Portanto, na linha 13, adicionamos a pasta que contém todos os módulos do ambiente virtual utilizado. Uma opção é copiar este diretório para outro local no sistema de ficheiros e referenciar esse local. Outra opção é adicionar este diretório ao Python Path diretamente dentro do destino [main_withmysql.wsgi] (esta é provavelmente a melhor solução);
  • linha 16: podemos indicar ao Apache o diretório de instalação do Python no sistema de ficheiros. Normalmente, este diretório está incluído no PATH do computador e, muitas vezes, esta linha é desnecessária (como foi o caso aqui). No entanto, podem existir várias instalações do Python no computador e a desejada pode não estar incluída no PATH do computador. Nesse caso, esta linha resolve o problema;

Da mesma forma, crie um ficheiro [flask-impots-withpgres.conf]:


# dossier du script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
 
# nom du site web configuré par ce fichier
# ici il s'appellera flask-impots-withmysql
# les URL seront du type http(s)://flask-impots-withmysql/path
define SITE "flask-impots-withpgres"
 
# mettre l'adresse IP 127.0.0.1 pour site SITE dans c:/windows/system32/drivers/etc/hosts
 
# mettre ici les chemins des bibliothèques Python à utiliser - les séparer par des virgules
# ici les bibliothèques d'un environnement virtuel Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
 
# Python Home - nécessaire uniquement s'il y a plusieurs versions de Python installées
# WSGIPythonHome "C:/Program Files/Python38"
 
# URL HTTP
<VirtualHost *:80>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
# URL sécurisées avec HTTPS
<VirtualHost *:443>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans 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 as bases de dados 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 a URL [https://flask-impots-withmysql/do]. A resposta do servidor é a seguinte:

Image

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

Image

Ambas as aplicações estão a funcionar normalmente.

Agora vamos modificar o parâmetro [WSGIScriptAlias] no ficheiro [flask-imports-withmysql.conf]:

#  .wsgi script folder
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"



#  URL HTTP
<VirtualHost *:80>
    #  with the alias / the URL will have the form /{prefixe_url}/action/...
    #  with the alias /impots the URL will take the form /impots/{prefixe_url}/action/...
    #  where [prefixe_url] is defined in parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    
</VirtualHost>

#  URL secured with HTTPS
<VirtualHost *:443>
    #  with the alias / the URL will have the form /{prefixe_url}/action/...
    #  with the alias /impots the URL will take the form /impots/{prefixe_url}/action/...
    #  where [prefixe_url] is defined in parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    

</VirtualHost>
  • nas linhas 11 e 20, o alias WSI é agora [/impots];

Paramos e reiniciamos o servidor Apache, depois solicitamos a URL [https://flask-impots-withmysql/impots/do]. A resposta do servidor é a seguinte:

Image

Ocorre uma falha. A 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 na nossa aplicação. Ela sabe como lidar com um prefixo de URL (/do está presente). Poder-se-ia pensar que adicionar o prefixo [/imports/do] à nossa aplicação 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 compreender o que aconteceu. Solicitámos a URL [https://flask-impots-withmysql/impots/do]. Esperávamos ver a vista de autenticação. Em [1] acima, vemos que a aplicação solicitou a sua exibição, mas não com a URL correta. Vamos examinar o caminho do pedido [https://flask-impots-withmysql/impots/do].

Primeiro, foi executada a seguinte rota (configs/routes.py):


    # les routes de l'application Flask
    # racine de l'application
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

A rota na linha 3 é [https://flask-impots-withmysql/impots/do] no nosso exemplo. Podemos ver que a rota foi despojada da parte [https://flask-impots-withmysql/impots] para se tornar simplesmente [/do]. Quanto à parte [https://flask-impots-withmysql], isto é normal; o nome do servidor não está incluído na rota. Mas podemos ver que também não inclui o alias WSGI [/imports]. Este é um ponto importante. Mesmo com um alias WSGI, as nossas rotas iniciais permanecem válidas.

Agora vamos ver o que a função [index] na linha 4 (configs/routes_without_csrftoken) faz:

1
2
3
4
#  application root
def index() -> tuple:
    #  redirect to /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

Na linha 4, somos redirecionados para o URL da função [init_session]. No ficheiro [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)

Linha 2: No nosso teste, [csrftoken_param] é uma cadeia de caracteres vazia. A aplicação não processa um token CSRF aqui.

A função [init_session] é definida da seguinte forma (configs/routes_without_csrftoken):

1
2
3
4
#  init-session
def init_session(type_response: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

Linha 4: Iniciamos a cadeia de processamento da ação [init-session]. Esta cadeia termina da seguinte forma em [responses/HtmlResponse]:



# now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""
 
        # redirect response
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

A ação [init-session] é uma ação ADS (Action Do Something) que termina com um redirecionamento para uma vista, na linha 9. É aí que reside o problema. A função [redirect] na linha 9 não adiciona automaticamente o alias WSGI ao URL de redirecionamento. Isto é mostrado na captura de ecrã acima. O alias /impots está em falta no URL de redirecionamento.

A versão seguinte fornece uma solução para o problema do alias WSGI.