01 - Get-it
Parte 4: Fazendo um formulário de criação de anotação
Vamos implementar agora a funcionalidade de adicionar anotações. O objetivo é que você aprenda como receber dados no servidor enviados pelo navegador.
Atenção
É possível que nas próximas etapas o servidor apresente erros inesperados. Tente acessar o servidor de um navegador com aba anônima.
Para começar, modifique o template index.html
para adicionar o <form>
:
<!-- DOCTYPE, HTML, HEAD DEVEM CONTINUAR AQUI -->
<body>
<img src="img/logo-getit.png">
<p>Como o Post-it, mas com outro verbo</p>
<form method="post">
<label for="titulo">Título</label>
<input id="titulo" type="text" name="titulo" />
<label for="detalhes">Detalhes</label>
<input id="detalhes" name="detalhes" />
<input type="submit" />
</form>
<!-- O RESTO DO HTML A PARTIR DAQUI -->
Seu código do programa principal não precisa ser modificado ainda.
EXERCÍCIO
Execute o servidor e teste a página.
Importante
Quando você for testar a página, ao clicar em submit
a página simplesmente vai recarregar. É isso mesmo que deve ocorrer. Nos próximos passos nós vamos usar essa informação.
Usando os dados recebidos do formulário
Talvez você tenha notado que no formulário (<form>
) existe um atributo method="post"
. Isso quer dizer que os dados do formulário serão enviados utilizando o método HTTP POST (veremos mais detalhes sobre ele no futuro). O que você precisa saber por enquanto é que até o momento nós sempre enviamos requisições do tipo GET para o servidor. Para entender melhor o que está acontecendo, observe a saída do seu terminal. Deve haver uma requisição parecida com essa:
POST / HTTP/1.1
Host: 0.0.0.0:8080
Connection: keep-alive
Content-Length: 25
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://0.0.0.0:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://0.0.0.0:8080/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,pt;q=0.8
titulo=Sorvete+de+banana&detalhes=Coloque+uma+banana+no+congelador+e+espere.+Pronto%21+1%2B1%3D2.
Há dois pontos importantes:
- A primeira linha da requisição está diferente. Existe um
POST
ao invés deGET
, mas a rota é a mesma (/
). - O corpo da requisição (última linha) possui os dados que o usuário digitou no formulário html.
Queremos pegar o corpo da requisição, titulo=Sorvete+de+banana&detalhes=Coloque+uma+banana+no+congelador+e+espere.+Pronto%21+1%2B1%3D2.
e extrair o título e os detalhes da anotação.
Note que o que vem após titulo=
é o que o usuário digitou no campo referente ao título. Porém o texto está codificado. O texto Sorvete+de+banana
deveria ser Sorvete de banana
.
Além disso, há o símbolo &
indicando que um outro valor será apresentado em seguido.
Em seguida, nos deparamos com o texto detalhes=
que representa o texto digitado pelo usuário no campo input
referente aos detalhes da anotação.
O mesmo ocorre com o campo detalhes
. O texto Coloque+uma+banana+no+congelador+e+espere.+Pronto%21+1%2B1%3D2.
deveria ser Coloque uma banana no congelador e espere. Pronto!
.
Para remover esses caracteres especiais, vamos utilizar a função urllib.parse.unquote_plus
(veja a documentação aqui).
EXERCÍCIO
Vamos utilizar o começo da string de requisição para saber o seu tipo (GET
ou POST
).
Modifique a sua função index
no arquivo views.py
para conter o seguinte conteúdo:
def index(request):
# A string de request sempre começa com o tipo da requisição (ex: GET, POST)
if request.startswith('POST'):
request = request.replace('\r', '') # Remove caracteres indesejados
# Cabeçalho e corpo estão sempre separados por duas quebras de linha
partes = request.split('\n\n')
corpo = partes[1]
params = {}
# Preencha o dicionário params com as informações do corpo da requisição
# O dicionário conterá dois valores, o título e a descrição.
# Posteriormente pode ser interessante criar uma função que recebe a
# requisição e devolve os parâmetros para desacoplar esta lógica.
# Dica: use o método split da string e a função unquote_plus
for chave_valor in corpo.split('&'):
# AQUI É COM VOCÊ
# O RESTO DO CÓDIGO DA FUNÇÃO index CONTINUA DAQUI PARA BAIXO...
Você deverá escrever algum código, onde está escrito # AQUI É COM VOCÊ
.
Seu objetivo é preencher o dicionário params
com os valores recebidos do formulário html.
Por exemplo, se o corpo da requisição for titulo=Sorvete+de+banana&detalhes=Coloque+uma+banana+no+congelador+e+espere.+Pronto%21+1%2B1%3D2.
, o dicionário params
deve ser preenchido com {'titulo': 'Sorvete de banana', 'detalhes': 'Coloque uma banana no congelador e espere. Pronto! 1+1=2.'}
.
IMPORTANTE
Note que a função index
agora está recebendo o request
como argumento (index(request)
).
Será necessário alterar o arquivo servidor.py
para passar o request
.
EXERCÍCIO
Ainda na função index(request)
do arquivo views.py
, adicione a nova anotação (que deverá estar armazenada em params['titulo']
e params['detalhes']
) ao arquivo notes.json
.
Dica: crie uma função no arquivo utils.py
que recebe a nova anotação e a adiciona à lista do arquivo notes.json
.
Última refatoração
Envie novamente uma nova anotação e recarregue a página. O navegador deve perguntar se você quer reenviar o formulário. Se você confirmar, a anotação deve ser adicionada mais uma vez, ficando duplicada no arquivo notes.json
. Por que isso acontece? Como podemos corrigir? Para isso vamos precisar entender um pouco sobre códigos de status de respostas HTTP.
Desde a parte 1 deste handout, nós começamos o cabeçalho das nossas respostas com 'HTTP/1.1 200 OK'
. O código 200
é um dos possíveis códigos de status resposta. Ele diz para o navegador que a requisição foi processada com sucesso. Para avisarmos para o navegador que depois de enviar a anotação ele deve recarregar a lista de anotações, vamos precisar do código 303
, que pede para o navegador se redirecionar para outra página após receber a resposta. No nosso caso, vamos redirecionar para a mesma página, mas a nova requisição será novamente do tipo GET (para saber um pouco mais leia este artigo).
Até o momento o nosso servidor sempre utiliza o cabeçalho fixo com código 200. Precisamos refatorá-lo para que possamos responder com códigos diferentes.
EXERCÍCIO
Implemente a função build_response
no arquivo utils.py
. Ele deve receber os seguintes argumentos: build_response(body='', code=200, reason='OK', headers='')
(talvez você queira ler isso: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values).
Acesse o link a seguir para ver um exemplo de response
: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview#responses
Lembre-se de testar a sua função com python test_utils.py
.
Agora que você implementou a função build_response
, vamos refatorar o código para utilizá-la.
Modifique seu programa principal da seguinte maneira:
Atenção
O código abaixo funcionará somente após finalizar o exercício abaixo.
import socket
from pathlib import Path
from utils import extract_route, read_file, build_response
from views import index
CUR_DIR = Path(__file__).parent
SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8080
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((SERVER_HOST, SERVER_PORT))
server_socket.listen()
print(f'Servidor escutando em (ctrl+click): http://{SERVER_HOST}:{SERVER_PORT}')
while True:
client_connection, client_address = server_socket.accept()
request = client_connection.recv(1024).decode()
print('*'*100)
print(request)
route = extract_route(request)
filepath = CUR_DIR / route
if filepath.is_file():
response = build_response() + read_file(filepath)
elif route == '':
response = index(request)
else:
response = build_response()
client_connection.sendall(response)
client_connection.close()
server_socket.close()
EXERCÍCIO
Modifique a função index
no arquivo views.py
para que ela utilize a função build_response
.
Será necessário fazer duas alterações na função index
:
- No retorno principal, será necessário alterar a última linha
return load_template('index.html').format(notes=notes).encode()
para utilizar a funçãobuild_response
. Certifique-se de não estar chamando a funçãoencode
duas vezes. - No caso de requisição com o método
POST
, você deve devolver o resultado debuild_response(code=303, reason='See Other', headers='Location: /')
.
Se tudo estiver correto você pode preencher o formulário e enviar. A lista de anotações deve ser atualizada e, ao recarregar a página, o navegador não deve perguntar novamente se você quer reenviar o formulário.
Desafio
O handout acabou, mas se quiser praticar um pouco mais você pode fazer o servidor devolver uma resposta com o código 404 quando a requisição é feita a uma página/recurso que não existe (dica: você só vai precisar modificar uma linha).
Ufa, cansei
Parabéns! Agora você pode tentar fazer alguma das receitas da nossa lista de anotações. Depois disso, se ainda tiver pique, é um bom momento para dar aquela relembrada em CSS para a próxima aula com essa lista de jogos:
- Flexbox Defense
- Flexbox Froggy
- Grid Garden
- CSS Diner
- Se você tem interesse por CSS, você vai gostar disso: https://rupl.github.io/unfold/