22. Serviços web com o framework Flask
Por serviço web, entendemos aqui qualquer aplicativo web que forneça dados brutos consumidos por um cliente, geralmente um script de console nos exemplos a seguir. Não estamos interessados em uma tecnologia específica, como REST (REpresentational State Transfer) ou SOAP (Simple Object Access Protocol), por exemplo, que fornecem dados mais ou menos brutos em um formato bem definido. O REST fornece jSON, enquanto que, para o SOAP, o resultado é XML. Cada uma dessas tecnologias descreve com precisão a maneira como o cliente deve consultar o servidor e o formato que a resposta deste deve assumir. Neste curso, seremos muito mais flexíveis quanto à natureza da solicitação do cliente e da resposta do servidor. No entanto, os scripts escritos e as ferramentas utilizadas são semelhantes aos da tecnologia REST.
22.1. Introdução
Os scripts em Python podem ser executados por um servidor web. Um script desse tipo se torna um programa de servidor capaz de atender a vários clientes. Do ponto de vista do cliente, chamar um serviço web equivale a solicitar o URL desse serviço. O cliente pode ser escrito em qualquer linguagem, inclusive em Python. Neste último caso, utilizam-se as funções de internet que acabamos de ver. Além disso, precisamos saber “conversar” com um serviço web, ou seja, compreender o protocolo HTTP de comunicação entre um servidor web e seus clientes. Esse era o objetivo do parágrafo |o protocolo HTTP|. Os clientes web descritos nesta parte do curso nos permitiram descobrir uma parte do protocolo HTTP.

Em sua forma mais simples, as interações cliente/servidor ocorrem da seguinte maneira:
- o cliente abre uma conexão com a porta 80 do servidor web;
- ele faz uma solicitação referente a um documento;
- o servidor web envia o documento solicitado e encerra a conexão;
- o cliente, por sua vez, encerra a conexão;
O documento pode ser de diversos tipos: um texto no formato HTML, uma imagem, um vídeo, etc. Pode ser um documento existente (documento estático) ou um documento gerado dinamicamente por um script (documento dinâmico). Neste último caso, fala-se em programação web. O script de geração dinâmica de documentos pode ser escrito em diversas linguagens: PHP, Python, Perl, Java, Ruby, C#, VB.net, ...
A seguir, utilizaremos scripts em Python para gerar dinamicamente documentos de texto.

- em [1], o cliente estabelece uma conexão com o servidor, solicita um script em Python e, opcionalmente, envia parâmetros para esse script;
- em [3], o servidor web executa o script Python por meio do interpretador Python. O script gera um documento que é enviado ao cliente [2];
- o servidor encerra a conexão. O cliente faz o mesmo;
O servidor web pode atender a vários clientes ao mesmo tempo.
A seguir, utilizaremos dois servidores web:
- o servidor leve Werkzeug [https://werkzeug.palletsprojects.com/en/1.0.x/]. Esse servidor é utilizado pelo framework web Flask [https://flask.palletsprojects.com/en/1.1.x/]. Vamos chamá-lo, com mais frequência, de servidor Flask;
- o servidor Apache 2 [https://httpd.apache.org/];
O servidor Flask será utilizado em todos os exemplos. O servidor Apache será utilizado para hospedar a aplicação web que vamos desenvolver.
O framework Flask é desenvolvido em Python. Trata-se de um módulo que se instala em um terminal PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask
Collecting flask
Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
|| 94 kB 1.1 MB/s
Collecting click>=5.1
Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
|| 82 kB 5.8 MB/s
Collecting itsdangerous>=0.24
Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting Jinja2>=2.10.1
Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
|| 125 kB 6.4 MB/s
Collecting Werkzeug>=0.15
Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
|| 298 kB 6.4 MB/s
Collecting MarkupSafe>=0.23
Downloading MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl (16 kB)
Installing collected packages: click, itsdangerous, MarkupSafe, Jinja2, Werkzeug, flask
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 flask-1.1.2 itsdangerous-1.1.0
- linha 1: o comando executado;
- linha 19: os elementos que foram instalados:
- [flask-1.1.2]: é um framework de desenvolvimento web em Python;
- [Werkzeug-1.0.1]: é o servidor web que responderá às solicitações dos clientes;
- [Jinja2-2.11.2]: é uma ferramenta que permite inserir elementos dinâmicos em páginas que, de outra forma, seriam estáticas;
22.2. scripts [flask/01]: primeiros elementos de programação web

Nossos exemplos serão executados na seguinte arquitetura:

- em [1], um script em Python será executado da mesma forma que um script de console clássico;
- em [2], de forma transparente, um servidor web é instanciado e aguarda solicitações. Na verdade, ele aceitará apenas uma única URL;
- em [3], o navegador solicitará ao servidor seu único URL;
- em [4], o servidor executará o script Python indicado pela console [1];
- em [5], o script enviará seus resultados ao servidor web, um documento de texto;
- em [6], o servidor web enviará esse documento de texto ao navegador;
22.2.1. script [exemple_01]: noções básicas da linguagem HTML
Um navegador da web pode exibir diversos documentos, sendo o mais comum o documento HTML (HyperText Markup Language). Trata-se de um texto formatado com tags do tipo <balise>texte</balise>. Assim, o texto <b>important</b> exibirá o texto importante em negrito. Existem tags isoladas, como a tag <hr/>, que exibe uma linha horizontal. Não abordaremos as tags que podem ser encontradas em um texto HTML. Existem diversos softwares WYSIWYG que permitem criar uma página WEB sem escrever uma única linha de código HTML. Essas ferramentas geram automaticamente o código HTML a partir de um layout criado com o mouse e controles predefinidos. Assim, é possível inserir (com o mouse) uma tabela na página e, em seguida, consultar o código HTML gerado pelo software para descobrir as tags a serem utilizadas para definir uma tabela em uma página WEB. Não é mais complicado do que isso. Além disso, o conhecimento da linguagem HTML é indispensável, uma vez que as aplicações web dinâmicas devem gerar elas mesmas o código HTML a ser enviado aos clientes web. Esse código é gerado por programa e, obviamente, é preciso saber o que deve ser gerado para que o cliente tenha a página web que deseja.
Resumindo, não é necessário conhecer toda a linguagem HTML para começar a programar para a web. No entanto, esse conhecimento é necessário e pode ser adquirido por meio do uso de softwares WYSIWYG para a criação de páginas WEB, como o DreamWeaver e dezenas de outros. Outra maneira de descobrir as sutilezas da linguagem HTML é navegar pela web e visualizar o código-fonte das páginas que apresentam características interessantes e ainda desconhecidas para você.
Consideremos o exemplo a seguir, que apresenta alguns elementos que podem ser encontrados em um documento da web, tais como:
- uma tabela;
- uma imagem;
- um link;

Um documento HTML é delimitado pelas tags <html>…</html>. Ele é composto por duas partes:
- <head>…</head>: essa é a parte não exibível do documento. Ela fornece informações ao navegador que irá exibir o documento. Nela, costuma-se encontrar a tag <title>…</title>, que define o texto a ser exibido na barra de título do navegador. Também podem ser encontradas outras tags, notadamente aquelas que definem as palavras-chave do documento, palavras-chave posteriormente utilizadas pelos mecanismos de busca. Também é possível encontrar nessa parte scripts, geralmente escritos em JavaScript ou VBScript, que serão executados pelo navegador;
- <body atributos>…</body>: esta é a parte que será exibida pelo navegador. As tags HTML contidas nesta parte indicam ao navegador a forma visual “desejada” para o documento. Cada navegador interpretará essas tags à sua maneira. Dois navegadores podem, portanto, exibir um mesmo documento da web de maneiras diferentes. Isso costuma ser um dos desafios dos web designers;
O código HTML do nosso documento de exemplo é o seguinte:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Quelques balises HTML</title>
</head>
<body style="background-image: url(/static/images/standard.jpg)">
<h1 style="text-align: left">Quelques balises HTML</h1>
<hr />
<table border="1">
<thead>
<tr>
<th>Colonne 1</th>
<th>Colonne 2</th>
<th>Colonne 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>cellule(1,1)</td>
<td style="text-align: center;">cellule(1,2)</td>
<td>cellule(1,3)</td>
</tr>
<tr>
<td>cellule(2,1)</td>
<td>cellule(2,2)</td>
<td>cellule(2,3</td>
</tr>
</tbody>
</table>
<br /><br />
<table border="0">
<tr>
<td>Une image</td>
<td>
<img border="0" src="/static/images/cerisier.jpg" />
</td>
</tr>
<tr>
<td>Le site de Polytech'Angers</td>
<td><a href="http://www.polytech-angers.fr/fr/index.html">ici</a></td>
</tr>
</table>
</body>
</html>
Elemento | tags e exemplos HTML |
<title>Algumas tags HTML</title> (linha 5) o texto [Quelques balises HTML] aparecerá na barra de título do navegador que exibirá o documento | |
<hr />: exibe uma linha horizontal (linha 10) | |
<table atributos>….</table>: para definir a tabela (linhas 12, 32) <thead>…</thead>: para definir os cabeçalhos das colunas (linhas 13, 19) <tbody>…</tbody>: para definir o conteúdo da tabela (linhas 20, 31) <tr atributos>…</tr>: para definir uma linha (linhas 21, 25) <td atributos>…</td>: para definir uma célula (linha 22) exemplos: <table border="1">…</table>: o atributo border define a espessura da borda da tabela <td style="text-align: center;">célula(1,2)</td> (linha 23): define uma célula cujo conteúdo será célula(1,2). Esse conteúdo será centralizado horizontalmente (text-align: center). | |
<img border="0" src="/static/images/cerisier.jpg"/> (linha 38): define uma imagem sem borda (border="0") cujo arquivo de origem é [/static/images/cerisier.jpg] no servidor web (src="/static/images/cerisier.jpg"). Se esse link estiver em um documento da web gerado com o URL [http://server/chemin/balises.html], o navegador solicitará o URL [http://server/ static/images/cerisier.jpg] para obter a imagem referenciada aqui. | |
<a href="http://www.polytech-angers.fr/fr/index.html">aqui</a> (linha 43): faz com que o texto ici funcione como um link para o URL http://www.polytech-angers.fr/fr/index.html. | |
<body style="background-image: url(/static/images/standard.jpg)"> (linha 8): indica que a imagem que deve servir como plano de fundo da página está localizada no endereço URL [/static/images/standard.jpg] do servidor web. No contexto do nosso exemplo, o navegador solicitará o URL [http://server/static/images/standard.jpg] para obter essa imagem de fundo. |
Vemos neste exemplo simples que, para construir o documento na íntegra, o navegador precisa fazer três solicitações ao servidor:
- [http://server/chemin/balises.html] para obter o código-fonte HTML do documento;
- [http://server/static/images/cerisier.jpg] para obter a imagem cerisier.jpg;
- [http://server/static/images/standard.jpg] para obter a imagem de fundo standard.jpg;
O script [exemple_01] nos permitirá exibir a página estática anterior, [balises.html]:

- em [1], o script [exemple_01] que será executado;
- em [3], o documento HTML que será exibido pelo script;
- em [2], as imagens do documento HTML;
O script [exemple_01] é o seguinte:
import os
from flask import Flask, make_response, render_template
# aplicativo Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# Página inicial URL
@app.route('/')
def index():
# exibição da página
return make_response(render_template("balises.html"))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 7: instanciamos uma aplicação Flask. Uma aplicação Flask é uma aplicação web;
- o primeiro parâmetro é o nome dado à aplicação. É possível atribuir o nome que se desejar. Aqui, utilizou-se o atributo predefinido [__name__], cujo valor é [__main__] (linha 18);
- o segundo parâmetro é um parâmetro nomeado, ou seja, sua posição na ordem dos parâmetros não importa. O parâmetro nomeado [template_folder] indica a pasta onde se encontram as páginas estáticas da aplicação web. As páginas estáticas são enviadas tal como estão para o navegador. Aqui, as páginas estáticas serão encontradas na pasta [templates] da árvore de diretórios do projeto. Na linha 7, definimos um caminho relativo à pasta [script_dir], que contém o script [exemple_01] a ser executado;
- o terceiro parâmetro também é um parâmetro nomeado. [static_folder] indica a pasta onde serão encontrados os recursos do documento HTML (imagens, vídeos, etc.). Aqui também, definimos um caminho relativo à pasta [script_dir], que contém o script [exemple_01] executado;
- linhas 10-14: definimos os URL aceitos pelo aplicativo web. Cada URL está associado a uma função que é executada quando o URL é solicitado por um navegador web;
- linha 11: o único URL do aplicativo é o URL [/]. Observe que, em [@app.route('/')], [app] é a variável inicializada na linha 7. A definição das rotas (as diferentes URL gerenciadas pelo aplicativo) vem, portanto, necessariamente após a definição do aplicativo [app]. Este último nome é livre;
- linhas 12-14: a função que é executada quando se solicita a URL [/] ao aplicativo web [exemple_01];
- linha 12: a função associada a um URL pode ter qualquer nome. Às vezes, ela pode ter parâmetros para recuperar elementos do URL a ele associado. Neste caso, ela não possui parâmetros;
- linha 14:
- a função [render_template] retorna uma sequência de caracteres que corresponde ao documento de texto gerado por seu parâmetro. Neste caso, esse parâmetro é [balises.html]. Devido ao [template_folder] da linha 7, esse documento será procurado na pasta [f"{script_dir}/../templates"]. É exatamente lá que ele se encontra;
- a função [make_response] gera uma resposta HTTP para o navegador que solicitou o URL [/]. Vimos no parágrafo |o protocolo HTTP| que uma resposta HTTP possui dois elementos:
- os cabeçalhos HTTP;
- o documento solicitado pelo navegador, neste caso um documento HTML;
Na linha 14, não foi passado nenhum parâmetro à função [make_response] para gerar cabeçalhos HTTP. Ela, então, irá gerá-los por padrão. Veremos posteriormente como definir esses cabeçalhos HTTP.
- Por fim, quando o navegador solicita o URL à aplicação Flask, ele obtém a página [balises.html];
- linhas 17-20: essas linhas servem para iniciar o servidor web que executará a aplicação web [exemple_01];
- linha 18: essa condição só é verdadeira quando o script [exemple_01] é executado em um terminal;
- linha 19: a aplicação [app] da linha 7 é configurada:
- o parâmetro denominado [ENV="development"] coloca o servidor web no modo de desenvolvimento: assim que o desenvolvedor modifica um elemento da aplicação, esta é regenerada e enviada ao servidor web. O desenvolvedor não precisa solicitar uma nova execução;
- o parâmetro denominado [DEBUG=True] permitirá que o desenvolvedor insira pontos de interrupção no código da aplicação;
- linha 20: a aplicação web é iniciada: um servidor web é instanciado e a aplicação web é implantada nele para responder às solicitações dos clientes web;
Veja um exemplo de execução:

Os seguintes logs aparecem então no console de execução:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_01.py
* Serving Flask app "exemple_01" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Pressione CTRL+C para sair)
- linha 2: o servidor exibe o script executado;
- linha 3: estamos no modo de desenvolvimento;
- linhas 4-5: o servidor detecta que foi iniciado no modo [debug]. Ele então reinicia (linha 5). O modo [debug], portanto, retarda um pouco a inicialização;
- linha 8: o URL, onde a aplicação web implantada [exemple_01] está disponível;
Com um navegador da web, acessemos o URL [http://127.0.0.1:5000/]:

O documento esperado [balises.html] é exibido corretamente.
22.2.2. script [exemple_02]: gerar um documento HTML dinamicamente

O script [exemple_02] [1] irá gerar o seguinte documento [exemple_02.html] [2]:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>{{page.title}}</title>
</head>
<body>
<b>{{page.contents}}</b>
</body>
</html>
Esse documento é dinâmico porque seu conteúdo só é totalmente conhecido no momento em que o servidor web o disponibiliza. De fato, nas linhas 5 e 8, há dois elementos desconhecidos no momento da criação da página. Eles só são conhecidos no momento em que a página é enviada a um cliente. Nesse momento, são substituídos por seus valores, que são sequências de caracteres.
- linhas 5 e 8: a sintaxe {{expressão}} é uma sintaxe da linguagem de modelos Jinja2 [https://jinja.palletsprojects.com/en/2.11.x/]. Antes que a página seja enviada a um cliente, os elementos dinâmicos da página (linhas 5 e 8) são avaliados e substituídos por seus valores;
- linha 5: utilizou-se a sintaxe [page.title]. Portanto, supôs-se que, na geração da página antes de seu envio, uma variável [page] seja conhecida; veremos como. Na sintaxe {{expressão}}, é possível utilizar os nomes de variáveis que se desejar. Nas linhas 5 e 8, poderíamos ter {{title}} e {{contents}}. Poderíamos dizer, então, que [title] e [contents] são parâmetros da página. A seguir, utilizaremos sempre a mesma técnica:
- o único parâmetro da página será um dicionário [page];
- os atributos desse dicionário serão utilizados na página. Aqui, [page.title] na linha 5 e [page.contents] na linha 8;
A aplicação web [exemple_02.py] é a seguinte:
from flask import Flask, make_response, render_template
# aplicativo Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# Página inicial URL
@app.route('/')
def index():
# conteúdo da página na forma de um dicionário
page = {"title": "un titre", "contents": "un contenu"}
# exibição da página
return make_response(render_template("exemple_02.html", page=page))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- já explicamos no exemplo anterior as linhas 4-5 e 18-20. Continuaremos a utilizar esse esquema em nossos exemplos;
- linha 9: o único URL fornecido pela aplicação web é o URL /;
- linha 14: o documento servido para o URL / é o documento [exemple_02.html] que acabamos de comentar. Sabemos que ele possui um parâmetro, um dicionário chamado [page];
- linha 12: definimos o dicionário que será passado como parâmetro para a página [exemple_02.html]. Ele pode ter qualquer nome. No entanto, deve possuir os atributos [title, contents] utilizados no documento HTML;
- linha 14: a função [render_template] tem como função gerar a sequência de caracteres do documento [exemple_02.html]. Como se trata de um documento parametrizado, transmitimos à função [render_template] o(s) parâmetro(s) esperado(s). Fazemos isso aqui atribuindo um valor ao parâmetro denominado [page]. Na operação [page=page]:
- à esquerda do sinal =, temos o parâmetro [page] utilizado no documento [exemple_02.html];
- à direita do sinal =, temos o valor [page] definido na linha 12;
- De modo geral, se um documento HTML tiver os parâmetros [param1, param2, …, paramn], seus valores serão passados para a função [render_template] na forma [render_template(document, param1=valeur1, param2=valeur2, …];
Antes de executar o [exemple_02], precisamos interromper a execução do [exemple_01]:

Se, durante a execução de um script 1, você tiver a impressão de que é um script 2 que está sendo executado, isso provavelmente ocorre porque o script 1 ainda está em execução. Para retornar a um estado conhecido, você pode interromper todos os processos em execução no PyCharm (no canto superior direito da janela PyCharm):

Vamos executar o script [exemple_02]:

Os logs da console são, então, os seguintes:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_02.py
* Serving Flask app "exemple_02" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Pressione CTRL+C para sair)
A linha 8 indica a porta de implantação (5000) do aplicativo [exemple_02] (linha 1) na máquina [localhost]. Como as linhas anteriores são sempre as mesmas, não as exibiremos novamente.
Usando um navegador, acessamos o URL [http://localhost:5000/]:

- a expressão {{page.title}} gerou [1];
- a expressão {{page.contents}} gerou [2];
22.2.3. script [exemple_03]: usar fragmentos de página

- em [1], o script [exemple_03.py] irá gerar o documento dinâmico [exemple_03.html] [2]. Este será construído a partir dos fragmentos de página [fragment_01.html, fragment_02.html] e [3];
O documento [exemple_03.html] será o seguinte:
<!DOCTYPE html>
<html lang="fr">
{% include "fragments/fragment_01.html" %}
<body>
{% include "fragments/fragment_02.html" %}
</body>
</html>
- nas linhas 3 e 5, utiliza-se a diretiva [include] do Jinja2 para incluir no documento elementos externos a ele;
- a sintaxe é {% include … %}. O parâmetro da diretiva [include] é o caminho do documento a ser incorporado. Esse caminho é relativo ao parâmetro [template_folder] do aplicativo Flask:
app = Flask(__name__, template_folder="../templates", static_folder="../static")
Portanto, neste caso, os caminhos dos documentos são medidos em relação à pasta [templates].
O fragmento [fragment_01.html] (os nomes são, obviamente, livres) é o seguinte:
<meta charset="UTF-8">
<title>{{page.title}}</title>
O fragmento [fragment_02.html] é o seguinte:
<b>{{page.contents}}</b>
Se reconstituirmos o documento [exemple_03.html] com esses fragmentos, obtemos o seguinte código:
<!DOCTYPE html>
<html lang="fr">
<meta charset="UTF-8">
<title>{{page.title}}</title>
<body>
<b>{{page.contents}}</b>
</body>
</html>
Portanto, temos um documento idêntico ao [exemple_02.html], mas construído a partir de fragmentos.
O script da web [exemple_03.py] é o seguinte:
import os
from flask import Flask, make_response, render_template
# aplicativo Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# Página inicial URL
@app.route('/')
def index():
# conteúdo da página
page = {"title": "un autre titre", "contents": "un autre contenu"}
# exibição da página
return make_response(render_template("views/exemple_03.html", page=page))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
O código é semelhante ao de [exemple_02.py]. Na linha 16, mostra-se como é possível referenciar documentos presentes em subpastas de [template_folder], da linha 7.
A execução do script [exemple_03.py] produz os seguintes resultados no navegador:

22.3. scripts [flask/02]: serviço web de data e hora

O documento [date_time_server.html] é 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>
- linha 8: a página aceita o parâmetro [page.date_heure];
O serviço web [date_time_server.py] é o seguinte:
# importações
import os
import time
from flask import Flask, make_response, render_template
# aplicativo Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}")
# Página inicial URL
@app.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 manual
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 13: o aplicativo web serve apenas o URL /;
- linhas 15-24: explicam como obter a data e a hora e como exibi-las;
- linha 27: sequência de caracteres que representa a data e a hora do momento;
- linhas 28-30: geramos o documento dinâmico [date_time_server.html], passando-lhe o dicionário [page] da linha 29;
- linha 31: exibe-se o tipo de [document] e o próprio documento. O objetivo é mostrar que se trata de uma sequência de caracteres;
- linha 33: gera-se a resposta HTTP que será enviada ao cliente (ela ainda não foi enviada);
- linha 34: exibe-se seu tipo e seu valor;
- linha 35: a resposta HTTP é enviada ao cliente;
A execução do script produz o seguinte resultado em um navegador:

Os registros no console são os seguintes:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\flask\02\date_time_server.py
* Serving Flask app "date_time_server" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Pressione CTRL+C para sair)
127.0.0.1 - - [10/Jul/2020 09:32:09] "GET / HTTP/1.1" 200 -
document <class 'str'> <!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 : 10/07/20 09:42:33</b>
</body>
</html>
response <class 'flask.wrappers.Response'> <Response 195 bytes [200 OK]>
- linha 10: observa-se que o tipo do valor retornado por [render_template] é do tipo [str]. Essa sequência de caracteres nada mais é do que o documento [date_time_server.html] após ser interpretado (linhas 10-19);
- linha 20: percebe-se que o tipo do valor retornado por [make_response] é do tipo [flask.wrappers.Response]. A função [Response.__str__] foi chamada implicitamente para exibir o objeto [Response]. A string retornada por essa função fornece duas informações sobre a resposta HTTP que será gerada:
- o documento enviado tem 195 bytes;
- o status da resposta HTTP é [200 OK]. Veremos posteriormente que temos acesso a esse código de status;
22.4. scripts [flask/03]: serviços web que geram texto simples
Vimos em um exemplo anterior que o serviço web fornecia o seguinte documento:
<!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>
Um cliente da web poderia estar interessado apenas na informação [page.date_heure] da linha 8 e não na formatação HTML que a envolve. O serviço web poderia fornecer essa informação como uma simples sequência de caracteres. Apresentaremos aqui exemplos desse tipo de serviço web.
22.4.1. script [main_01]

- [main_01] é o serviço web;
- [config] é o script de configuração do aplicativo web;
- o serviço web utiliza algumas das entidades definidas em [2];
O script [config] é o seguinte:
def configure():
# caminho absoluto de referência dos caminhos relativos da configuração
rootDir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# dependências do aplicativo
absolute_dependencies = [
# Pessoas, Ferramentas, MyException
f"{rootDir}/classes/02/entities",
]
# define-se o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# carregando a configuração
return {}
A principal função dessa configuração é definir o Python Path do serviço web. É necessário que as entidades [2] (linha 8) possam ser encontradas.
O script da web [main_01] é o seguinte:
# configurando a aplicação
import config
config=config.configure()
# importações
from flask import Flask, make_response
from flask_api import status
# dependências
from Personne import Personne
# aplicativo Flask (sem documentos estáticos aqui)
app = Flask(__name__)
# Página inicial URL
@app.route('/')
def index():
# uma pessoa
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# resposta HTTP
response = make_response(str(personne))
# cabeçalhos HTTP
response.headers.set("Content-type", "application/json; charser=utf8")
# envia-se a resposta HTTP
return response, status.HTTP_200_OK
# apenas main
if __name__ == '__main__':
# inicia-se o servidor
app.config.update(ENV="development", DEBUG=True)
app.run()
- linhas 1-3: o Python Path do aplicativo é definido;
- linhas 5-10: importam-se os elementos necessários ao script;
- linha 17: o serviço web serve apenas o URL /;
- linha 20: cria-se um objeto [Personne];
- linha 22: cria-se uma resposta HTTP com a sequência de caracteres que representa a pessoa. A função [Personne.__str__] será chamada. Ela retorna a sequência jSON do dicionário [asdict] da pessoa (cf. |classe BaseEntity|). O parâmetro da função [make_response] é o documento de texto enviado ao cliente, portanto, neste caso, a sequência jSON de uma pessoa;
- linha 24: colocamos nos cabeçalhos HTTP da resposta um cabeçalho [Content-type] que indica ao cliente que tipo de documento ele receberá, neste caso um documento jSON codificado em UTF-8;
- linha 26: retorna-se uma tupla de dois elementos:
- a resposta ao cliente, os cabeçalhos HTTP e o documento;
- o código de status da resposta. Aqui, queremos definir o código de status como [200 OK]. Os diferentes códigos de status são definidos por constantes no módulo [flask_api], importado na linha 7;
O módulo [flask_api] não está disponível por padrão. É necessário instalá-lo. Isso é feito em um terminal PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask_api
Collecting flask_api
Downloading Flask_API-2.0-py3-none-any.whl (119 kB)
|| 119 kB 544 kB/s
Requirement already satisfied: Flask>=1.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from flask_api) (1.1.2)
Requirement already satisfied: Jinja2>=2.10.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (2.11.2)
Requirement already satisfied: Werkzeug>=0.15 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.0.1)
Requirement already satisfied: click>=5.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (7.1.2)
Requirement already satisfied: itsdangerous>=0.24 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.1.0)
Requirement already satisfied: MarkupSafe>=0.23 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Jinja2>=2.10.1->Flask>=1.1->flask_api) (1.1.1
)
Installing collected packages: flask-api
Successfully installed flask-api-2.0
Ao executar o script da web [main_01], obtêm-se os seguintes resultados em um navegador:

- no [2], a string jSON recebida;
- no [3-4], exibe-se o conteúdo do documento recebido. Percebe-se que não há nenhuma formatação HTML, apenas a string jSON;
Vamos agora examinar a função do cabeçalho [Content-Type] enviado ao cliente pelo serviço web. Colocamos o navegador no modo de desenvolvedor (geralmente F12) e solicitamos novamente o mesmo URL. Abaixo, uma captura de tela do navegador Chrome:

- em [1], selecione a aba [Network];
- em [2, 4]: o URL solicitado pelo navegador;
- em [3], selecione a guia [Headers] (cabeçalhos HTTP);
- em [5], o código de status da resposta HTTP recebida;
- em [6], o cabeçalho que indica ao cliente que ele receberá um texto jSON. Isso permite que o cliente se adapte à resposta. Assim, a fonte utilizada pelo Chrome para exibir uma resposta jSON ou uma resposta de texto básico não é a mesma;

- em [8], seleciona-se a aba [Response] para acessar o documento enviado pelo serviço web, neste caso, uma simples sequência de caracteres jSON;
22.4.2. Postman
[Postman] é a ferramenta que nos permitirá consultar os diferentes URL de um aplicativo web. Ela nos permite:
- utilizar qualquer URL: esses são criados manualmente;
- enviar solicitações ao servidor web por meio de um GET, POST, PUT, OPTIONS…;
- especificar os parâmetros do GET ou do POST;
- definir os cabeçalhos HTTP da consulta;
- receber uma resposta nos formatos jSON, XML, HTML,
- ter acesso aos cabeçalhos HTTP da resposta. Assim, temos acesso à resposta completa HTTP do servidor;
[Postman] é uma excelente ferramenta didática para compreender a comunicação cliente/servidor do protocolo HTTP.
O [Postman] está disponível no URL [https://www.getpostman.com/downloads/]. Proceda à instalação da sua versão do [Postman]. Durante a instalação, será solicitado que você crie uma conta: ela não será necessária neste caso. A conta [Postman] serve para sincronizar diferentes dispositivos, de modo que a configuração de um seja replicada em outro. Nada disso é necessário neste caso.
Uma vez instalado, o [Postman] apresenta a seguinte interface:

- no [2-3], tem-se acesso às configurações do produto;

- no [6], a versão utilizada neste documento;
Aqui, vamos usar o [Postman] para testar o serviço web jSON anterior:
- executamos o script [flask/03/main_01];
- em seguida, solicitamos o URL [http://localhost:5000/] com o Postman;
- no [1], criamos uma solicitação;
- no [2], será uma solicitação HTTP GET;
- em [3], o URL do serviço web consultado;
- no [4], envia-se a solicitação ao serviço web;
- em [5], seleciona-se a aba [Body], que exibe o documento recebido;
- em [6], seleciona-se a guia [Pretty], que exibe o documento recebido com a formatação adequada, neste caso, uma formatação adequada para uma sequência jSON;
- em [7], o documento jSON recebido;
- em [8-9], o documento recebido sem formatação;
- em [10], são exibidos os cabeçalhos HTTP recebidos pelo Postman;
- em [11], o status HTTP da resposta recebida;
- em [12], os cabeçalhos HTTP recebidos;
- em [13], o cabeçalho [Content-type] que permitiu ao Postman saber que receberia uma sequência jSON. O Postman utilizou essa informação para formatar, de certa forma, o documento recebido;
Há outra maneira de usar o Postman. Consiste em utilizar o console do Postman (Ctrl-Alt-C). Ele permite visualizar o diálogo cliente/servidor. Além da sequência Ctrl-Alt-C, o console do Postman está disponível por meio de um ícone no canto inferior esquerdo da janela principal do Postman:

O console do Postman armazena as interações cliente/servidor que ocorrem quando uma solicitação do Postman é executada:

- em [3], a lista de solicitações feitas pelo Postman desde que foi iniciado. As mais recentes estão na parte inferior da lista;
- em [4], a solicitação HTTP feita pelo Postman;
- em [5-6], a resposta HTTP enviada pelo servidor web;
- em [7], é possível ver os logs no modo [raw], ou seja, sem formatação;
No modo [raw], a janela do console fica assim:

- em [8], a solicitação HTTP feita pelo Postman ao servidor web;
- em [9], a resposta HTTP enviada pelo servidor web;
- em [10], é possível voltar ao modo [pretty logs];
Para facilitar as explicações, numeraremos as linhas obtidas a partir do console do Postman.
Para o cliente:
Para o servidor:
A partir de agora, utilizaremos principalmente:
- [Postman] como cliente web;
- o console [Postman] em [raw mode] para explicar a comunicação cliente/servidor;
22.4.3. script [main_02]

O script da web [main_02] é o seguinte:
# configura-se o aplicativo
import config
config=config.configure()
# importações
from flask import Flask, make_response
from flask_api import status
# dependências
from Personne import Personne
# aplicativo Flask
app = Flask(__name__)
# Página inicial URL
@app.route('/')
def index():
# uma pessoa
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# conteúdo
response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
# cabeçalhos HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# resposta HTTP
return response, status.HTTP_200_OK
# apenas principal
if __name__ == '__main__':
# inicia-se o servidor
app.config.update(ENV="development", DEBUG=True)
app.run()
- o script [main_02] é análogo ao script [main_01]. Ele difere deste em dois pontos:
- linha 22: o documento enviado ao cliente é uma sequência de caracteres bruta, e não uma sequência jSON;
- linha 24: isso se reflete no cabeçalho HTTP [Content-Type], que indica o tipo [text/plain] para o documento;
Executamos o script da web [main_02] e, em seguida, usamos [Postman] para consultá-lo:

- em [1-3], é enviada a solicitação ao serviço web;
- em [5], o status OK da resposta;
- em [4, 6], os cabeçalhos HTTP da resposta;
- em [7], o cabeçalho [Content-Type];
- em [8-10], o documento enviado pelo serviço web, uma sequência de caracteres;
O console do Postman exibe os seguintes logs:
Solicitação do cliente:
Resposta do servidor:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf8
Content-Length: 34
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:34:22 GMT
personne[Aglaë, de la Hûche, 87]
22.4.4. script [main_03]

O script da web [main_03] é o seguinte:
# configurando o aplicativo
import config
config = config.configure()
# importações
from flask import Flask, make_response
from flask_api import status
# dependências
from MyException import MyException
from Personne import Personne
# aplicativo Flask
app = Flask(__name__)
# Página inicial URL
@app.route('/')
def index():
# uma pessoa incorreta
msg_erreur = None
try:
personne = Personne().fromdict({"prénom": "", "nom": "", "âge": 87})
except MyException as erreur:
msg_erreur = f"{erreur}"
# erro?
if msg_erreur:
response = make_response(msg_erreur)
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
else:
response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
status_code = status.HTTP_200_OK
# cabeçalhos HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# resposta HTTP
return response, status_code
# apenas principal
if __name__ == '__main__':
# iniciando o servidor
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 23: ocorre um erro ao instanciar uma pessoa incorreta;
- linhas 27-29: devido ao erro:
- linha 28: prepara-se uma resposta HTTP com o conteúdo da mensagem de erro;
- linha 29: atribui-se ao código de status HTTP um valor de erro [500 Internal Server Error];
- linha 34: informa-se ao cliente que está sendo enviado um texto simples;
- linha 36: enviamos a resposta HTTP ao cliente;
Iniciamos o serviço web [main_03] e usamos o Postman para fazer a consulta:

- em [1-3], enviamos a solicitação;
- em [4], obtemos uma resposta com um código de status [500 INTERNAL SERVER ERROR];
- em [5-7]: a resposta é um texto que descreve o erro ocorrido;

- em [8-10], os cabeçalhos HTTP da resposta do serviço web;
No console do Postman, os resultados no modo [raw] são os seguintes:
Solicitação do cliente:
Resposta do servidor:
HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/plain; charset=utf8
Content-Length: 74
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:39:24 GMT
MyException[11, Le prénom doit être une chaîne de caractères non vide]
22.5. Scripts [flask/04]: informações encapsuladas na solicitação

O script [request_parameters.py] tem como objetivo demonstrar que o serviço web tem acesso a diversas informações encapsuladas na solicitação de um cliente web. O código é o seguinte:
# importação
from flask import Flask, make_response, request
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# Página inicial URL
@app.route('/', methods=['GET', 'POST'])
def index():
# parâmetros da solicitação
request_data = {}
request_data["environ"] = f"{request.environ}"
request_data["path"] = request.path
request_data["full_path"] = request.full_path
request_data["script_root"] = request.script_root
request_data["url"] = request.url
request_data["base_url"] = request.base_url
request_data["url_root"] = request.url_root
request_data["accept_charsets"] = request.accept_charsets
request_data["accept_encodings"] = request.accept_encodings
request_data["accept_languages"] = request.accept_languages
request_data["accept_mimetypes"] = request.accept_mimetypes
request_data["args"] = request.args
request_data["content_encoding"] = request.content_encoding
request_data["content_length"] = request.content_length
request_data["content_type"] = request.content_type
request_data["endpoint"] = request.endpoint
request_data["files"] = request.files
request_data["form"] = request.form
request_data["host"] = request.host
request_data["method"] = request.method
request_data["query_string"] = request.query_string.decode()
request_data["referrer"] = request.referrer
request_data["remote_addr"] = request.remote_addr
request_data["remote_user"] = request.remote_user
request_data["scheme"] = request.scheme
request_data["script_root"] = request.script_root
request_data["user_agent"] = f"{request.user_agent}"
request_data["values"] = request.values
# resposta HTTP
response = make_response(request_data)
# cabeçalhos HTTP
response.headers["Content-Type"] = "application/json; charset=utf-8"
# envio da resposta HTTP
return response, status.HTTP_200_OK
# função principal
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 9: introduzimos uma alteração. Especificamos quais são os verbos permitidos na solicitação do cliente. O Postman fornece a lista:

Os dois primeiros, [GET, POST], são os mais utilizados e também serão os únicos a serem usados neste documento. Voltando à linha 9 do código, o parâmetro [methods] contém a lista dos métodos da lista acima autorizados pelo URL. Na ausência desse parâmetro, apenas o método [GET] é autorizado. Foi isso que aconteceu até agora;
- linha 12: vamos construir o dicionário [request_data];
- linha 13: a solicitação do cliente está disponível em um objeto predefinido [request], importado na linha 2, do tipo [werkzeug.local.LocalProxy]. As linhas seguintes recuperam diversos atributos desse objeto;
- em vez de detalhar cada atributo do objeto [request], vamos executar esse código e observar os resultados. Assim, compreenderemos melhor o significado dos diferentes atributos exibidos;
- linha 42: o dicionário [request_data] será o conteúdo da resposta HTTP. Vale lembrar que este deve ser texto. O Flask transforma automaticamente os dicionários em strings jSON;
- linha 44: informamos ao cliente que ele receberá jSON;
- linha 46: enviamos a resposta ao cliente;
Com o cliente Postman, enviamos a seguinte solicitação ao serviço web anterior:

- em [1-2], a solicitação enviada;
- em [2], a solicitação está configurada. Os parâmetros são anexados ao URL na forma [ ?param1=valeur1¶m2=valeur2]. Há duas maneiras de inserir esses parâmetros no Postman:
- inseri-los diretamente no URL;
- inseri-los no [3-4];
Ambos os métodos são equivalentes;
Adicionamos outros parâmetros à solicitação:

- no [5-7], adicionamos parâmetros no corpo (=body) da solicitação. Enquanto os parâmetros do URL são visíveis para o usuário de um navegador da web, aqueles que fazem parte do corpo da solicitação não são visíveis. O navegador (ou o Postman, neste caso) os envia ao servidor após os cabeçalhos HTTP. A solicitação do cliente web passa então a ter a mesma estrutura que a resposta do servidor web: cabeçalhos HTTP seguidos por um documento. Isso fará com que dois novos cabeçalhos HTTP apareçam na solicitação do cliente:
- [Content-Type]: o cliente informa ao servidor o tipo de documento que está enviando;
- [Content-Length]: o tamanho do documento em bytes;
- em [6], a codificação a ser utilizada para os parâmetros declarados em [7]. Estes podem ser codificados de diversas maneiras. [x-www-form-urlencoded] é um método frequentemente utilizado pelos navegadores;
É possível ver a solicitação que será gerada:

A resposta a essa solicitação é a seguinte:

- em [1-5], recebemos uma sequência jSON [3];
- o que geralmente interessa ao serviço web são os parâmetros de URL [ ?param1=valeur1¶m2=valeur2] e aqueles que foram transmitidos no corpo da solicitação (documento). É assim, em geral, que o cliente transmite informações a ele. Vemos em [5] que os parâmetros de URL estão disponíveis em [request.args];
O restante da resposta é o seguinte:

- em [9], os atributos dos parâmetros inseridos no corpo da solicitação:
- [content_type] é o tipo do documento que acompanha a solicitação. Vimos que esse documento continha informações do tipo [param=valeur] codificadas na forma [x-www-form-urlencoded]. O Postman, portanto, gerou um cabeçalho HTTP [Content-Type] indicando a natureza do documento;
- [content_length] é o tamanho em bytes desse documento;
- em [10], o atributo [request.environ] contém diversas informações sobre o ambiente no qual a solicitação do cliente é processada. A maioria dessas informações se encontra nos demais atributos do objeto [request];
- em [11], os parâmetros presentes no corpo da solicitação estão disponíveis no atributo [request.form];
- em [12], o método utilizado para enviar a solicitação, neste caso o método [GET];
- em [13], o atributo [request.values] é o dicionário de todos os parâmetros, tanto os de URL quanto os do corpo do documento. Para obter os parâmetros da solicitação, utilizar-se-á o atributo:
- [request.args] para obter os presentes no URL;
- [request.form] para obter os que estão presentes no corpo do documento;
No console do Postman, os logs são os seguintes:
Solicitação do cliente:
- linha 9: o tipo do documento enviado na linha 12 ao servidor;
- linha 11: os cabeçalhos HTTP da solicitação são separados do documento enviado por uma linha em branco. É assim que o servidor identifica o fim dos cabeçalhos HTTP do cliente;
- linha 12: o documento “codificado por URL”. Todos os caracteres acentuados foram codificados;
A resposta do cliente é a seguinte:
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 2433
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:09:09 GMT
{
"accept_charsets": [],
"accept_encodings": [
[
"gzip",
1
],
[
"deflate",
1
],
[
"br",
1
]
],
"accept_languages": [],
"accept_mimetypes": [
[
"*/*",
1
]
],
"args": {
"param1": "valeur1",
"param2": "valeur2"
},
"base_url": "http://localhost:5000/",
"content_encoding": null,
"content_length": 60,
"content_type": "application/x-www-form-urlencoded",
"endpoint": "index",
"environ": "{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=908>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x00000173CA6E5160>, 'SERVER_SOFTWARE': 'Werkzeug/1.0.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': '/', 'QUERY_STRING': 'param1=valeur1¶m2=valeur2', 'REQUEST_URI': '/?param1=valeur1¶m2=valeur2', 'RAW_URI': '/?param1=valeur1¶m2=valeur2', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 50592, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_USER_AGENT': 'PostmanRuntime/7.26.1', 'HTTP_ACCEPT': '*/*', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_POSTMAN_TOKEN': 'cbfac6aa-71a0-4076-a0c3-91d36d74a4c0', 'HTTP_HOST': 'localhost:5000', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CONNECTION': 'keep-alive', 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 'CONTENT_LENGTH': '60', 'werkzeug.request': <Request 'http://localhost:5000/?param1=valeur1¶m2=valeur2' [GET]>}",
"files": {},
"form": {
"nom": "s\u00e9l\u00e9n\u00e9",
"pr\u00e9nom": "agla\u00eb",
"\u00e2ge": "77"
},
"full_path": "/?param1=valeur1¶m2=valeur2",
"host": "localhost:5000",
"method": "GET",
"path": "/",
"query_string": "param1=valeur1¶m2=valeur2",
"referrer": null,
"remote_addr": "127.0.0.1",
"remote_user": null,
"scheme": "http",
"script_root": "",
"url": "http://localhost:5000/?param1=valeur1¶m2=valeur2",
"url_root": "http://localhost:5000/",
"user_agent": "PostmanRuntime/7.26.1",
"values": {
"nom": "s\u00e9l\u00e9n\u00e9",
"param1": "valeur1",
"param2": "valeur2",
"pr\u00e9nom": "agla\u00eb",
"\u00e2ge": "77"
}
}
- linhas 1-5: os cabeçalhos HTTP da resposta, terminados por uma linha em branco;
- linhas 41-45: os caracteres acentuados foram codificados como UTF-8;
Se agora usarmos o método [POST] para enviar a mesma solicitação com os mesmos parâmetros, obteremos a mesma resposta, com a diferença de que, no [12], teremos o [‘method’ : ‘POST’].
Então, qual é a diferença entre os métodos GET e POST? A diferença é mínima e foi estabelecida pelo uso que os navegadores fizeram deles historicamente:
- os parâmetros no URL são práticos porque um URL configurado dessa forma pode servir como link em um documento HTML. O usuário também pode alterar os parâmetros por conta própria para obter respostas diferentes do servidor. Nesse caso, os navegadores costumam utilizar o método [GET] e não há corpo (content_length=0) na solicitação enviada ao servidor web (sem parâmetros ocultos);
- às vezes, não se deseja que os parâmetros sejam exibidos no URL. Esse é o caso das senhas enviadas ao servidor. Além disso, o tamanho ocupado pelos parâmetros do URL é limitado (um URL não pode ultrapassar um determinado tamanho). Os parâmetros do corpo da solicitação não têm essa limitação. Além disso, muitos parâmetros no URL tornam-no ilegível. Vamos considerar o caso comum de um formulário de cadastro em um site. Historicamente, quando as páginas ainda não incorporavam JavaScript, os navegadores enviavam as informações digitadas por meio de um POST. Na época, falava-se de valores postados;
Portanto, nos primórdios da programação web:
- os métodos GET eram mais associados à solicitação de informações fornecidas por um servidor web;
- os métodos POST eram mais associados ao envio de informações do navegador para o servidor. O servidor era então “enriquecido” por essas informações;
Desde então, o JavaScript entrou em cena. Enquanto nos exemplos anteriores o desenvolvedor não tinha controle (clicar em um link acionava inevitavelmente um GET, enviar um formulário passava inevitavelmente por um POST), o JavaScript devolveu esse controle a eles. Nesse modelo, a página HTML está associada a um código JavaScript que pode contornar o navegador. Assim, o clique em um link pode ser interceptado pelo código JavaScript, que pode então executar um código que faz uma solicitação ao servidor. Essa solicitação será transparente para o usuário. Ele não a verá. Esse código é um cliente web e, assim como fizemos com o Postman, o desenvolvedor pode criar a solicitação que desejar. Voltando ao clique em um link, ele pode executar um POST, enquanto, por padrão, o navegador teria executado um GET. Essas mudanças tornaram as diferenças entre GET e POST menos relevantes.
No entanto, os desenvolvedores costumam adotar as seguintes regras:
- um GET não deve alterar o estado do servidor. GETs sucessivos, realizados com os mesmos parâmetros no URL, devem retornar o mesmo documento. Além disso, o GET geralmente não possui corpo (nenhum documento associado), apenas parâmetros no URL;
- o POST pode alterar o estado do servidor. Os parâmetros são, na maioria das vezes, enviados no corpo da solicitação. Nesse caso, fala-se de valores postados. O exemplo do formulário é o mais esclarecedor: os valores inseridos pelo usuário serão colocados no corpo do POST e o servidor os registrará em algum lugar, geralmente em um banco de dados;
No restante deste documento, não nos comprometemos a seguir nenhuma regra específica.
22.6. scripts [flask-05]: gerenciamento da memória do usuário
22.6.1. Introdução
Nos exemplos cliente/servidor anteriores, o funcionamento era o seguinte:
- o cliente abre uma conexão com a porta 80 da máquina do serviço web;
- ele envia a sequência de texto: cabeçalhos HTTP, linha em branco, [document];
- em resposta, o servidor envia uma sequência do mesmo tipo;
- o servidor encerra a conexão com o cliente;
- o cliente encerra a conexão com o servidor;
Se o mesmo cliente fizer, pouco depois, uma nova solicitação ao servidor web, uma nova conexão é criada entre o cliente e o servidor. O servidor não tem como saber se o cliente que está se conectando já esteve lá antes ou se esta é a primeira solicitação. Entre duas conexões, o servidor “esquece” seu cliente. Por esse motivo, diz-se que o protocolo HTTP é um protocolo sem estado. No entanto, é útil que o servidor se lembre de seus clientes. Assim, se um aplicativo for seguro, o cliente enviará ao servidor um nome de usuário e uma senha para se identificar. Se o servidor “esquecer” seu cliente entre duas conexões, este terá que se identificar a cada nova conexão, o que não é viável.
Para acompanhar um cliente, o servidor pode proceder de diversas maneiras:
- ao receber uma primeira solicitação de um cliente, ele inclui em sua resposta um identificador que o cliente deve, então, reenviar a cada nova solicitação. Graças a esse identificador, diferente para cada cliente, o servidor pode reconhecer um cliente. Ele pode então gerenciar uma memória para esse cliente na forma de uma memória associada de maneira única ao identificador do cliente. É assim que funcionam, por exemplo, os serviços PHP;
- na primeira solicitação de um cliente, ele inclui em sua resposta não um identificador, mas a própria memória do usuário. Ele não armazena nada no lado do servidor. Para manter sua memória, o cliente da web deve reenviar essa memória a cada nova solicitação. Ela é modificada (ou não) a cada nova solicitação e reenviada (ou não) ao cliente. Esse é o método utilizado pelo framework Flask;
As diferenças entre os dois métodos são as seguintes:
- o método 1 consome menos largura de banda. Apenas um identificador é trocado entre o cliente e o servidor. Quando a memória do usuário aumenta, isso não tem nenhuma consequência sobre o identificador, que permanece o mesmo. Esse não é o caso do método 2, em que a memória do usuário é trocada a cada solicitação e pode aumentar à medida que as solicitações se sucedem;
- o método 1 consome mais espaço de memória. De fato, o servidor armazena a memória do usuário em seus sistemas de arquivos. Se houver um milhão de usuários, isso pode representar um problema. O método 2 não armazena nada no servidor;
Tecnicamente, é assim que funciona nos dois métodos:
- na resposta a um novo cliente, o servidor inclui o cabeçalho HTTP, [Set-Cookie : MotClé=Identifiant] ou [Set-Cookie : mémoire]. Com o método 1, isso ocorre apenas na primeira solicitação. Com o método 2, ocorre sempre que a memória do usuário muda;
- em suas solicitações, o cliente retorna sistematicamente o que recebeu, seja um identificador ou uma memória. Ele faz isso por meio do cabeçalho HTTP [Cookie : MotClé=Valeur];
Pode-se perguntar como o servidor sabe que está lidando com um novo cliente, em vez de um cliente que já esteve lá. É a presença do cabeçalho HTTP Cookie nos cabeçalhos HTTP do cliente que indica isso ao servidor. Para um novo cliente, esse cabeçalho está ausente.
O conjunto de conexões de um determinado cliente é chamado de sessão.
O servidor pode manter outros tipos de memória:

- No [1], a memória da solicitação é específica. Ela é utilizada quando a solicitação do cliente web é processada não por um único serviço (ou aplicativo), mas por vários. Para passar informações ao serviço i+1, o serviço i pode enriquecer a solicitação processada (request) com essas informações. É o que se chama de memória no nível da solicitação. Não utilizaremos esse tipo de memória neste documento;
- no [2, 4], a memória do usuário que acabamos de descrever. Ela pode ser implementada localmente ([2]) ou mantida por meio do cliente ([4]);
- em [3], a memória de nível “aplicativo” é, na maioria das vezes, uma memória somente leitura. Ela é compartilhada por todos os usuários. Nela, frequentemente, encontram-se elementos da configuração do aplicativo web, configuração compartilhada por todos os usuários do aplicativo. É preciso ter cuidado com esse tipo de memória: a gravação nela deve ser feita em um momento em que os usuários ainda não tenham enviado solicitações, geralmente no início da aplicação. Posteriormente, quando as solicitações chegam, fica difícil gravar nessa memória. Quando o servidor web atende simultaneamente a vários usuários e dois deles desejam gravar na memória de nível “aplicação”, há o risco de que essa memória seja corrompida. De fato, enquanto o usuário 1 começa a gravar na memória de nível “aplicação”, ele pode ser interrompido antes mesmo de terminar. Temos, então, uma memória de aplicação incompleta. Como ela é compartilhada, um usuário 2 pode lê-la e obter um estado incorreto;
22.6.2. script [session_scope_01]

Os scripts [session_scope_xx] ilustram o gerenciamento das memórias dos usuários.
O script [session_scope_01] é o seguinte:
# configuramos o aplicativo
import config
config = config.configure()
# dependências
import json
from flask import Flask, make_response, session
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# chave secreta da sessão
app.secret_key = config["SECRET_KEY"]
@app.route('/set-session', methods=['GET'])
def set_session():
# colocamos algo na sessão
session['nom'] = 'séléné'
# envia-se uma resposta vazia
response = make_response()
response.headers['Content-Length'] = 0
return response, status.HTTP_200_OK
@app.route('/get-session', methods=['GET'])
def get_session():
# recuperamos a sessão e enviamos a resposta
response = make_response(json.dumps({"nom": session['nom']}, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# apenas manual
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 11: uma aplicação Flask é instanciada;
- linha 14: o atributo [secret_key] dessa aplicação recebe um valor extraído do arquivo de configuração utilizado nas linhas 1 a 3. Uma sessão Flask só é possível se esse atributo for inicializado. É possível inserir qualquer coisa nele. Ele serve para criptografar uma parte da “memória do usuário” que será enviada ao cliente. Geralmente, coloca-se algo difícil de adivinhar. No arquivo [config], a chave secreta é definida da seguinte maneira:
# retornamos a configuração
config = {
# configuração do Flask
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
- pela primeira vez, definimos um aplicativo web que serve para outra finalidade que não o URL /
- linha 17: o URL [/set-session] serve para inicializar a sessão do usuário;
- linha 27: o URL [/get-session] serve para recuperar a memória do usuário (ou sessão do usuário);
- linha 20: insere-se algo na memória (= a sessão) do usuário, neste caso, um nome. A sessão funciona de maneira semelhante a um dicionário. Não é possível inserir qualquer coisa na sessão. Os valores inseridos precisam poder ser transformados em jSON. Para os tipos predefinidos do Python, isso ocorre sem intervenção do desenvolvedor. Para objetos proprietários que o Python não reconhece, é necessário realizar a conversão jSON manualmente;
- linha 22: cria-se uma resposta HTTP sem conteúdo (ausência de parâmetro em make_response);
- linha 23: informa-se ao cliente que ele receberá um documento vazio (tamanho de 0 byte);
- linha 24: envia-se a resposta HTTP ao cliente. O URL [/set-session], portanto, não faz nada além de inicializar uma sessão de usuário;
- linha 27: o URL e o [/get-session] permitem que o usuário saiba o que há em sua sessão;
- linha 30: criamos uma resposta HTTP contendo a string jSON da sessão do usuário. Aqui, criamos a string jSON nós mesmos, em vez de deixar que o Flask a gere. De fato, não queremos que os caracteres acentuados sejam escapados (ensure_ascii=False);
- linha 31: informamos ao cliente que estamos enviando jSON;
- linha 32: enviamos a resposta HTTP ao cliente;
O objetivo deste script é mostrar que a sessão do usuário permite estabelecer uma ligação entre suas solicitações sucessivas:
- a solicitação 1 solicitará o URL [/set-session];
- a solicitação 2 solicitará URL [/get-session] e recuperará o nome que a solicitação 1 tiver inicializado;
O script [config], que configura os scripts da pasta [flask/05], é o seguinte:
def configure():
# caminho absoluto de referência para os caminhos relativos da configuração
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# dependências do aplicativo
absolute_dependencies = [
# Pessoas, Ferramentas, MyException
f"{root_dir}/classes/02/entities",
]
# define-se o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# carregando a configuração
config = {
# configuração do Flask
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
Executamos o script [session_scope_01] e, em seguida, com o Postman, faremos a solicitação para o URL e o [/set-session]. Antes disso, verificaremos alguns elementos da solicitação que será feita:
![]()
- no [1], acessamos os cookies do Postman;
- em [2-4], verificamos os cookies conhecidos do Postman e os excluímos todos ([4-5]);
Agora, vamos verificar a solicitação HTTP que será gerada:

- em [9]: parte dos cabeçalhos HTTP que o Postman incluirá na solicitação com base na configuração que definimos para ela. Essa verificação permite que você confirme se não omitiu nenhum parâmetro ou, ao contrário, se não deixou parâmetros desnecessários;
Feito isso, podemos executar a consulta:
![]()
Existem várias maneiras de verificar o resultado. Já é possível observar a janela principal:

- em [1-2], a consulta enviada ao serviço web;
- em [3-6], os cabeçalhos HTTP da resposta;
- em [4], como no código não especificamos o tipo da resposta, o Flask utilizou, por padrão, o tipo [text/html];
- em [5], o cliente sabe que não há nenhum documento na resposta;
- linha 6: o cabeçalho [Set-Cookie] foi enviado pelo servidor Flask. Seu valor é chamado de cookie de sessão. Ele é composto por três elementos:
- [session=valeur]: o valor representa a memória do usuário em formato codificado. Essa memória é decodificável (consulte |https://blog.miguelgrinberg.com/post/how-secure-is-the-flask-user-session|). No entanto, devido à chave secreta utilizada pelo servidor, o usuário não pode alterar a memória recebida para, em seguida, reenviá-la ao servidor. Quando o servidor recebe uma sessão, tem assim a garantia de receber uma sessão não corrompida;
- [HttpOnly]: a presença desse elemento indica ao navegador que o recebe que o cookie não deve ser acessível ao JavaScript que a página exibida possa conter;
- [Path=/] é o caminho para o qual o cookie de sessão deve ser reenviado; portanto, neste caso, qualquer caminho da aplicação web. Sempre que o usuário, por meio do teclado, solicitar explicitamente (digitando um URL) ou implicitamente (clicando em um link) um URL desse domínio, o navegador reenviará automaticamente o cookie de sessão que recebeu;
A desvantagem da janela principal é que não temos acesso à solicitação completa que levou a essa resposta. O que é apresentado nessa janela pode causar confusão:

- nos cabeçalhos HTTP e [3-4], o [5] é apresentado como um cookie de sessão. Poderíamos então pensar que o Postman inseriu um cookie de sessão na solicitação, quando na verdade não foi o caso. Os cabeçalhos [3] representam, na verdade, os cabeçalhos HTTP que serão enviados na próxima solicitação, conforme ela está configurada atualmente. O Postman acaba de receber um cookie de sessão que ele reenviará na próxima solicitação. É por isso que temos [5];
É possível acessar a caixa de diálogo cliente/servidor no console do Postman, que é aberta com Ctrl-Alt-C:
GET /set-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 3673b73f-7600-4df4-8c4b-c37973e50df8
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Vary: Cookie
Set-Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:32:57 GMT
- linha 14: o cookie de sessão enviado pelo servidor;
Agora, vamos solicitar o URL [/get-session]:
- linha 9: o cliente Postman reenviou ao servidor o cookie de sessão que havia recebido;
- linha 18: a string jSON enviada pelo servidor;
Este exemplo nos mostra vários pontos:
- o cliente Postman reenvia o cookie de sessão que recebe do servidor Flask. Os navegadores da web sempre agem dessa forma;
- vemos que a solicitação 2, [/get-session], permitiu recuperar uma informação criada durante a solicitação 1, [/set-session]. Portanto, temos aqui um histórico do usuário;
- linhas 11-16: o servidor Flask não enviou de volta nenhum cookie de sessão. Isso não ocorre sistematicamente. O servidor Flask só envia de volta o cookie de sessão se a última solicitação tiver alterado a memória do usuário;
22.6.3. script [session_scope_02]

O script [session_02] é o seguinte:
# dependências
import os
from flask import Flask, make_response, session
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# chave secreta da sessão
app.secret_key = os.urandom(12).hex()
# Página inicial URL
@app.route('/', methods=['GET'])
def index():
# gerenciamos três contadores
if session.get('n1') is None:
session['n1'] = 0
else:
session['n1'] = session['n1'] + 1
if session.get('n2') is None:
session['n2'] = 10
else:
session['n2'] = session['n2'] + 1
if session.get('n3') is None:
session['n3'] = 100
else:
session['n3'] = session['n3'] + 1
# dicionário de contadores
compteurs = {"n1": session['n1'], "n2": session['n2'], "n3": session['n3']}
# enviando a resposta
response = make_response(compteurs)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# página inicial
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 11: aqui, a chave secreta é gerada por meio de uma função. A vantagem dessa função é que ela gera uma sequência de caracteres complexa de forma aleatória. Vale lembrar que a variável [app] é a instância da classe Flask criada na linha 8;
- linha 15: desta vez, haverá apenas uma rota, a rota /;
- linhas 17-29: gerenciamos uma sessão contendo três contadores [n1, n2, n3]. Na primeira chamada do usuário, [n1, n2, n3] = [0, 10, 100]; a partir daí, a cada chamada, esses contadores são incrementados em 1;
- linha 18: na primeira consulta, a sessão do aplicativo está vazia. A expressão [session.get(‘clé’)] retorna o valor [None]. Nas consultas seguintes, essa expressão retornará o valor associado à chave;
- linha 31: esses contadores são inseridos em um dicionário;
- linha 33: esse dicionário é o documento da resposta HTTP. Vale lembrar que o Flask transforma automaticamente os dicionários em uma string jSON;
- linha 34: informa-se ao cliente web que ele receberá jSON;
- linha 35: enviamos a resposta HTTP ao cliente;
Vamos executar esse script e consultar a aplicação web assim criada com o Postman, após ter removido todos os cookies do cliente Postman [1-3]:

Na console do Postman, as trocas entre cliente e servidor são as seguintes:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: c7db536d-9352-4aa6-9877-04560e03d935
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:50:22 GMT
{
"n1": 0,
"n2": 10,
"n3": 100
}
- em [14], o cookie de sessão enviado pelo servidor;
- em [18-22], a resposta do servidor na forma de uma string jSON;
Vamos repetir a mesma solicitação uma segunda vez. Os logs evoluem da seguinte forma:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 8205ad85-37b3-41f2-a171-70dd3b3a1679
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MSwibjIiOjExLCJuMyI6MTAxfQ.Xw6nsw.OuxIQnGhmhSsan5Qu_FL3Iyu-9k; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:52:35 GMT
{
"n1": 1,
"n2": 11,
"n3": 101
}
- linha 9: o cliente Postman reenvia o cookie de sessão que recebeu;
- linha 15: em sua resposta, o servidor envia um novo cookie de sessão, pois a solicitação do cliente alterou a memória do usuário (= a sessão);
- linhas 19-23: os novos valores dos contadores;
22.6.4. script [session_scope_03]
Este novo script tem como objetivo mostrar que é possível colocar diferentes tipos de Python em uma sessão: lista, dicionário, objeto. A única restrição é que os objetos inseridos na sessão sejam serializáveis no jSON. Se não forem por padrão (listas, dicionários), é necessário realizar a conversão manualmente no jSON.
# configura-se o aplicativo
import config
config = config.configure()
# dependências
import json
import os
from flask import Flask, make_response, session
from flask_api import status
from Personne import Personne
# aplicativo Flask
app = Flask(__name__)
# chave secreta da sessão
app.secret_key = os.urandom(12).hex()
# Página inicial URL
@app.route('/', methods=['GET'])
def index():
# gerenciamento de uma lista
liste = session.get('liste')
if liste is None:
# primeira solicitação
liste = [0, 10, 100]
else:
# solicitações seguintes
for i in range(len(liste)):
liste[i] += 1
# a lista é reinserida na sessão
session['liste'] = liste
# gerenciamento de um dicionário
dico = session.get('dico')
if not dico:
# primeira consulta
dico = {"un": 0, "deux": 10, "trois": 100}
else:
# solicitações seguintes
dico = session['dico']
for key in dico.keys():
dico[key] += 1
# o dicionário é reinserido na sessão
session['dico'] = dico
# gerenciamento de uma pessoa
personne_json = session.get('personne')
if personne_json is None:
# primeira consulta
personne = Personne().fromdict({"prénom": "aglaë", "nom": "séléné", "âge": 70})
else:
# solicitações seguintes
personne = Personne().fromjson(personne_json)
personne.âge += 1
# a pessoa é reinserida na sessão
session['personne'] = personne.asjson()
# dicionário de resultados
résultats = {"liste": liste, "dict": dico, "personne": personne.asdict()}
# envia-se uma resposta jSON
response = make_response(json.dumps(résultats, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linhas 1-3: a aplicação web é configurada;
- linhas 5-11: as dependências são importadas;
- linha 14: a aplicação Flask é instanciada;
- linha 17: o atributo [secret_key] é inicializado. É isso que permite o uso de sessões;
- linha 21: a única rota da aplicação;
- linhas 23-33: gerenciamento de uma lista na sessão. Nela, foram inseridos elementos serializáveis por padrão em jSON;
- linhas 35-46: gerenciamento de um dicionário na sessão. Nele foram inseridos elementos serializáveis por padrão em jSON;
- linhas 48-58: gerenciamento de uma pessoa. Um objeto [Personne] não é serializável por padrão em jSON. Portanto, é preciso tomar precauções;
- linha 58: utiliza-se o método [BaseEntity.asjson] para armazenar na sessão a string jSON da pessoa. Observe que poderíamos ter usado [personne.asdict], pois [personne.asdict] é um dicionário que contém valores serializáveis por padrão em jSON;
- linha 55: como armazenamos uma string jSON na sessão, recuperamos a pessoa a partir dela usando o método [BaseEntity.fromjson];
- linha 61: cria-se o dicionário [résultats], que será enviado como resposta ao cliente. Sabemos que, nesse caso, o Flask envia a string jSON do dicionário. Portanto, é necessário que este contenha apenas valores serializáveis por padrão em jSON;
- linha 64: colocamos explicitamente a string jSON do dicionário [résultats] na resposta HTTP. O Flask teria feito isso por padrão. No entanto, ainda por padrão, ele usa o parâmetro [ensure_ascii=True], o que não nos convinha;
- linha 65: informamos ao cliente que ele receberá jSON;
- linha 66: enviamos a resposta a ele;
Iniciamos o aplicativo web. Excluímos todos os cookies do cliente Postman. Em seguida, ele solicita o URL [http://localhost:5000]. O diálogo cliente/servidor no console do Postman é o seguinte:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5f8b7c63-aa8a-4429-a2fa-62141423d933
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:36:59 GMT
{"liste": [0, 10, 100], "dict": {"un": 0, "deux": 10, "trois": 100}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 70}}
Fazemos a solicitação uma segunda vez:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 40fd00ea-d45c-46b7-a51e-d4d433a37b5c
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkP2kupv9LtIW2WIBgNGqEg_nu3seQ0b2Zew-zfCa5hlvqBs5aw5-SLolGuUaETgi-7wD0sqaHPk7BJLilGXdEYW-ZqjNxjWhnuwpiWMB3Ti0Haz6MMMfz9EcM5-LrIT7zZjv4F5NYvOQ.Xw6ydQ.PMWRCqKx9HNnb_DyK-ha-9pCF7M; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:38:29 GMT
{"liste": [1, 11, 101], "dict": {"deux": 11, "trois": 101, "un": 1}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 71}}
- linha 9: o cliente retorna o cookie de sessão que recebeu;
- linha 15: o servidor retorna outro cookie, pois o conteúdo da sessão foi alterado (linha 19). Vale lembrar que esse conteúdo está presente no cookie de sessão de forma codificada;
22.7. scripts [flask/06]: informações compartilhadas por todos os usuários
22.7.1. Introdução
Esta seção tem como objetivo mostrar como gerenciar informações de escopo de aplicação, ou seja, compartilhadas por todos os usuários. Essas informações são, tipicamente, informações de configuração da aplicação. Vimos que uma aplicação web pode manter diferentes tipos de memória:

Aqui, estamos interessados na memória da aplicação [3].
22.7.2. script [application_scope_01]

O script [application_scope_01] mostra uma maneira de gerenciar dados com escopo “aplicação”:
# configura-se o aplicativo
import config
config = config.configure()
# dependências
from flask import Flask, make_response
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# Página inicial URL
@app.route('/', methods=['GET'])
def index():
# o objetivo é mostrar que o aplicativo permanece na memória entre as solicitações dos diferentes clientes
# cada cliente interage com a mesma aplicação
# app_infos representa informações no nível da aplicação e não no nível da sessão
# ou seja, ela diz respeito a todos os usuários e não a um em particular
# essa informação está armazenada aqui em [config] (não obrigatório)
# dicionário de resultados
résultats = {"config": config}
# envia-se a resposta
response = make_response(résultats)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
# verifica-se se esse código é executado várias vezes
print("application app lancée")
# inicia-se o aplicativo web
app.config.update(ENV="development", DEBUG=True)
app.run()
- linhas 1-3: recuperamos o dicionário da configuração. Vamos mostrar que o código localizado fora das funções de roteamento é executado apenas uma vez. A aplicação Flask permanece na memória. Todas as informações inicializadas fora das rotas são globais para elas e, portanto, conhecidas por elas. Assim, o dicionário [config] da linha 3 será retornado pela rota / (linha 24). Vamos demonstrar que todos os clientes da web receberão o mesmo dicionário e que este, portanto, é compartilhado por todos os clientes. Trata-se, portanto, de uma informação com escopo “aplicação”;
- linha 35: inserimos um log para verificar se o código das linhas fora da função de roteamento (linhas 1-10, 32-38) é executado várias vezes;
A configuração [config] é a seguinte:
def configure():
# retornamos a configuração
config = {
# configuração do Flask
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
Estamos iniciando este aplicativo. Os registros no console PyCharm são os seguintes:

- em [1], inicialização inicial do aplicativo;
- em [2], como solicitamos o modo [Debug], o aplicativo é reiniciado no modo [Debug];
Agora, com um navegador (Chrome, abaixo), solicitamos o URL [http://127.0.0.1:5000/]:

Agora, com o navegador Firefox:

Agora, com o cliente Postman:
Agora, voltamos ao console do Pycharm: [Run]:

- os dois registros [1, 2] ainda estão lá, mas não há outros, embora possamos ver as três solicitações recebidas pelo servidor web;
Para termos certeza absoluta de que o aplicativo não é recarregado a cada nova solicitação, podemos inserir um contador na configuração e incrementá-lo a cada nova solicitação. Veremos então que cada cliente visualiza o contador no estado em que o cliente anterior o deixou. Lembramos, no entanto, que os clientes não devem alterar dados de escopo de aplicação, pois eles são compartilhados entre todos os clientes; e, em um contexto em que o servidor atende simultaneamente a vários clientes sem garantia de que a solicitação de um cliente seja executada inteiramente sem interrupção, um cliente 1 que tenha enviado uma solicitação 1 interrompida antes de sua conclusão pode deixar os dados compartilhados em um estado corrompido para os clientes seguintes.
22.7.3. script [application_scope_02]

O script [application_scope_02] fará o que não se deve fazer: permitir que os clientes modifiquem informações compartilhadas com outros usuários. Vamos compartilhar um contador entre os usuários, que irão incrementá-lo. Veremos que cada usuário visualiza as modificações feitas pelos outros usuários no contador.
O script é o seguinte:
# dependências
from flask import Flask, make_response
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# dados do escopo da aplicação
config = {
"counter": 0
}
# Página inicial URL
@app.route('/', methods=['GET'])
def index():
# o objetivo é demonstrar que o dicionário [config] é compartilhado entre todos os clientes
# da aplicação web
# incrementa-se o contador
config["counter"] += 1
# envia-se a resposta
response = make_response(config)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linhas 10-12: o dicionário [config] compartilhado pelos usuários. Ele contém um contador;
- linha 22: sempre que um usuário solicitar o URL /, o contador da configuração será incrementado;
- linhas 23-26: a sequência jSON do dicionário é enviada a cada cliente;
Executamos esse script. Em seguida, solicitamos o URL [http://127.0.0.1:5000/] com um primeiro navegador:

Em seguida, fazemos o mesmo com um segundo navegador:

Em seguida, uma terceira vez com o Postman:

Percebemos que cada cliente recupera o contador no estado em que o cliente anterior o deixou. Portanto, todos têm acesso à mesma informação.
22.7.4. script [application_scope_03]
O script [application_scope_03] mostra por que as informações compartilhadas entre usuários devem ser somente para leitura.

O script é o seguinte:
# dependências
import threading
from time import sleep
from flask import Flask, make_response
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# dados do escopo da aplicação
config = {
"counter": 0
}
# Página inicial URL
@app.route('/', methods=['GET'])
def index():
# o objetivo é mostrar que o dicionário [config] é compartilhado entre todos os clientes
# da aplicação web e que deve ser somente para leitura
# nome do thread
thread_name = threading.current_thread().name
# lê-se o contador
counter = config["counter"]
print(f"compteur lu : {counter}, par le thread {thread_name}")
# pausa de 5 segundos — assim, outros clientes serão atendidos
sleep(5)
# incrementa-se o contador da configuração
config["counter"] = counter + 1
# registro
print(f"compteur écrit : {config['counter']}, par le thread {thread_name}")
# envia-se a resposta
response = make_response(config)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- linha 43: alteramos o modo de execução do aplicativo web. Escrevemos [threaded=True] para indicar que o aplicativo deveria atender aos usuários simultaneamente. Isso é feito por meio de threads de execução:
- pode haver várias threads de execução simultâneas, cada uma atendendo a um usuário;
- o processador da máquina é compartilhado por essas threads;
- uma thread pode ser interrompida antes de concluir seu trabalho. Ela será retomada posteriormente;
- linha 19: a função [index] pode ser executada simultaneamente por várias threads;
- linha 24: obtém-se o nome da thread que está executando a função [index];
- linha 26: lê-se o valor do contador. Para fins de nossa demonstração, decomponhamos o incremento do contador da seguinte maneira:
- etapa 1: leitura do contador (1, por exemplo) pela thread 1;
- etapa 2: pausa do thread 1 por 5 segundos (linha 29). Como o thread 1 solicitou uma pausa, o processador é cedido a outro thread, o thread 2. O objetivo é que esse novo thread leia o mesmo valor do contador (=1). Em seguida, ele também faz uma pausa de 5 segundos e perde o processador;
- etapa 3: incremento do contador, linha 31, a partir do valor lido na etapa 1 (=1). O thread 1 é o primeiro a fazer isso: ele aumenta o contador para 2 e, em seguida, encerra a execução da função [index]. Em seguida, é a vez do thread 2 acordar e também aumentar o contador para 2 a partir do valor lido na etapa 1 (=1). No final, após a execução dos dois threads, o contador está em 2, quando deveria estar em 3;
- linha 33: exibimos o valor do contador para verificação;
Executamos o script e, em seguida, acessamos a URL [http://loaclhost :5000/] com dois navegadores e, depois, com o Postman. Os logs no console PyCharm são, então, os seguintes:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/06/application_scope_03.py
* Serving Flask app "application_scope_03" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Pressione CTRL+C para sair)
compteur lu : 0, par le thread Thread-2
compteur lu : 0, par le thread Thread-4
compteur écrit : 1, par le thread Thread-2
127.0.0.1 - - [16/Jul/2020 08:55:37] "GET / HTTP/1.1" 200 -
compteur écrit : 1, par le thread Thread-4
127.0.0.1 - - [16/Jul/2020 08:55:40] "GET / HTTP/1.1" 200 -
compteur lu : 1, par le thread Thread-5
compteur écrit : 2, par le thread Thread-5
127.0.0.1 - - [16/Jul/2020 08:55:46] "GET / HTTP/1.1" 200 -
- linhas 9-10: os dois primeiros threads, 2 e 4, leem o mesmo valor 0 do contador;
- linha 11: o thread 2 aumenta o contador para 1;
- linha 13: o thread 4 altera o contador para 1. A partir deste momento, o valor do contador está incorreto;
- linhas 15-16: o thread 5 não é interrompido e lida corretamente com o valor do contador;
O que fica claro neste exemplo é que o código de um aplicativo web não deve alterar o valor das informações compartilhadas pelos usuários.
22.8. scripts [flask/07]: gestão de rotas

Aqui, estamos interessados no gerenciamento das rotas de um aplicativo, ou seja, as URL servidas pelo aplicativo web.
22.8.1. script [main_01]: rotas configuradas
O script [main_01] introduz a possibilidade de configurar as rotas:
from flask import Flask, make_response
from flask_api import status
# aplicativo Flask
app = Flask(__name__)
# envio da resposta
def send_plain_response(réponse: str):
# enviando a resposta
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# /sobrenome/nome
@app.route('/<string:nom>/<string:prenom>', methods=['GET'])
def index(nom, prenom):
# resposta
return send_plain_response(f"{prenom} {nom}")
# inicialização da sessão
@app.route('/init-session/<string:type>', methods=['GET'])
def init_session(type: str):
# resposta
return send_plain_response(f"/init-session/{type}")
# autenticar-usuário
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur():
# resposta
return send_plain_response("/authentifier-utilisateur")
# calcular-imposto
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot():
# resposta
return send_plain_response("/calculer-impot")
# listar-simulações
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations():
# resposta
return send_plain_response("/lister-simulations")
# excluir-simulação
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int):
# resposta
return send_plain_response(f"/supprimer-simulation/{numero}")
# fim-da-sessão
@app.route('/fin-session', methods=['GET'])
def fin_session():
# resposta
return send_plain_response(f"/fin-session")
# principal
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 17: especifica-se o tipo dos parâmetros do URL. Isso permite que o Flask faça verificações. Se o parâmetro não for do tipo esperado, a solicitação do cliente será rejeitada (erro 400 Bad Request). Assim, o Flask faz parte do trabalho que nós teríamos que fazer;
- linha 18: para os parâmetros, devemos usar os nomes exatos dos parâmetros da linha 17, mas não necessariamente a mesma ordem;
- linha 20: usamos a função [send_plain_response] para enviar a resposta ao cliente web;
- linha 9: a função [send_plain_response] recebe a sequência de caracteres a ser enviada ao cliente;
- linha 11: o corpo da resposta HTTP é construído;
- linha 12: informa-se ao cliente que está sendo enviado texto simples;
- linha 13: envia-se a resposta HTTP;
- linhas 23-62: outras rotas configuradas que serão utilizadas posteriormente em um exercício prático;
Executamos o script e o consultamos com o cliente Postman:

22.8.2. script [main_02]: externalização das rotas
No script [main_01] anterior, o código pode ficar extenso se houver muitas rotas. O script [main_02] mostra como externalizar as rotas.

O script [routes_02] reúne as funções associadas às rotas do script anterior:
from flask import make_response
from flask_api import status
def send_response(réponse: str):
# enviando a resposta
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# Página inicial URL
def index(nom, prenom):
# resposta
return send_response(f"{prenom} {nom}")
# inicialização da sessão
def init_session(type: str):
# resposta
return send_response(f"/init-session/{type}")
# autenticar-usuário
def authentifier_utilisateur():
# resposta
return send_response("/authentifier-utilisateur")
# calcular-imposto
def calculer_impot():
# resposta
return send_response("/calculer-impot")
# listar simulações
def lister_simulations():
# resposta
return send_response("/lister-simulations")
# excluir-simulação
def supprimer_simulation(numero: int):
# resposta
return send_response(f"/supprimer-simulation/{numero}")
# fim-da-sessão
def fin_session():
# resposta
return send_response(f"/fin-session")
Vale ressaltar que o script [routes_02] não é um script de rotas. Trata-se de uma lista de funções. É o script principal [main_02] que faz a ligação entre rotas e funções:
from flask import Flask
# as funções das rotas são transferidas para seu próprio script
import routes_02
# aplicativo Flask
app = Flask(__name__)
# associações entre rotas e funções
app.add_url_rule('/<string:nom>/<string:prenom>', methods=['GET'], view_func=routes_02.index)
app.add_url_rule('/init-session/<string:type>', methods=['GET'], view_func=routes_02.init_session)
app.add_url_rule('/authentifier-utilisateur', methods=['POST'], view_func=routes_02.authentifier_utilisateur)
app.add_url_rule('/calculer-impot', methods=['POST'], view_func=routes_02.calculer_impot)
app.add_url_rule('/lister-simulations', methods=['GET'], view_func=routes_02.lister_simulations)
app.add_url_rule('/supprimer-simulation/<int:numero>', methods=['GET'], view_func=routes_02.supprimer_simulation)
app.add_url_rule('/fin-session', methods=['GET'], view_func=routes_02.fin_session)
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- linha 4: importa-se o script das funções associadas às rotas;
- linhas 9-16: associação entre rotas e funções;
Com esse método, cada função associada a uma rota pode ser objeto de um script separado, se necessário.
Os resultados são os mesmos obtidos com o script [main_01] anterior.