Skip to content

01 - Get-it

Parte 2: Respondendo com páginas HTML

Nosso servidor já responde com Hello World, mas queremos muito mais que isso. Queremos implementar o nosso Get-it e para isso a página precisa ter muito mais conteúdo e precisa ser apresentado de maneira estruturada. Está na hora de trazermos o bom e velho HTML de volta!

Mas antes disso, nosso servidor atualmente responde uma requisição e para de rodar. Seria interessante que ele fosse capaz de responder mais do que uma requisição. Modifique seu programa da seguinte maneira:

import socket

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)

    response = 'HTTP/1.1 200 OK\n\nHello World'
    client_connection.sendall(response.encode())

    client_connection.close()

server_socket.close()

A única diferença é que colocamos a parte do código que aceita a conexão do cliente, recebe a requisição e devolve a resposta dentro de um while True. Então podemos recarregar a página no navegador quantas vezes quisermos e ela continuará a ser recebida.

Importante

Agora o servidor é um programa em loop infinito. Se quiser parar de rodar, basta encerrar o programa com Ctrl+C.

Agora sim, o HTML

Vamos devolver uma página HTML simples, apenas para relembrar as coisas:

import socket

SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8080

RESPONSE_TEMPLATE = '''HTTP/1.1 200 OK

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Get-it</title>
</head>
<body>

<h1>Get-it</h1>
<p>Como o Post-it, mas com outro verbo</p>

</body>
</html>
'''

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)

    client_connection.sendall(RESPONSE_TEMPLATE.encode())

    client_connection.close()

server_socket.close()

Testou? Funcionou? Podemos ir para o próximo passo.

Mostrando a lista de anotações

Podemos começar a pensar no conteúdo da nossa página principal. Vamos começar mostrando uma lista simples com os títulos e detalhes das anotações. Você vai precisar baixar a seguinte imagem clicando neste link. Salve essa imagem dentro de uma pasta img na mesma pasta onde está o seu código do servidor. Ou seja, o conteúdo da sua pasta será:

- DIRETORIO-DO-SEU-SERVIDOR
  |- servidor.py
  |- img
     |- logo-getit.png

Para a lista de anotações vamos utilizar as tags HTML unordered list (<ul>), list item (<li>), heading (<h3>) e paragraph (<p>). Além disso, vamos mostrar uma imagem com o logo ao invés de um texto com o título:

import socket

SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8080

RESPONSE_TEMPLATE = '''HTTP/1.1 200 OK

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Get-it</title>
</head>
<body>

<img src="img/logo-getit.png">
<p>Como o Post-it, mas com outro verbo</p>

<ul>
  <li>
    <h3>Receita de miojo</h3>
    <p>Bata com um martelo antes de abrir o pacote. Misture o tempero, coloque em uma vasilha e aproveite seu snack :)</p>
  </li>
  <li>
    <h3>Pão doce</h3>
    <p>Abra o pão e coloque o seu suco em pó favorito.</p>
  </li>
  <li>
    <h3>Sorvete com cristais de leite</h3>
    <p>Sirva o seu sorvete favorito em uma vasilha e jogue leite em cima.</p>
  </li>
  <li>
    <h3>Iogurte natural</h3>
    <p>Deixe o leite fora da geladeira (esse é mentira, não faça isso).</p>
  </li>
  <li>
    <h3>Homer Simpson</h3>
    <p>~( 8(|)</p>
  </li>
  <li>
    <h3>Numero mágico</h3>
    <p>142857</p>
  </li>
  <li>
    <h3>Série da Fundação - Isaac Asimov</h3>
    <p>É boa, leia.</p>
  </li>
</ul>

</body>
</html>
'''

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)

    client_connection.sendall(RESPONSE_TEMPLATE.encode())

    client_connection.close()

server_socket.close()

Se você rodou o código acima deve ter percebido que algo deu errado. Você não achou que seria tão simples assim, não é mesmo?

O arquivo da imagem do logo existe no seu computador (ou deveria existir - caso contrário, não se esqueça de baixar as imagens), mas o servidor precisa enviar esses arquivos como resposta quando forem solicitados.

Diferenciando rotas

Temos que implementar algumas coisas, mas vamos por partes. Verifique a saída no seu terminal. Você deve encontrar vários blocos de texto, um para cada requisição feita pelo navegador. Alguns exemplos:

GET /favicon.ico HTTP/1.1
Host: 0.0.0.0:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://0.0.0.0:8080/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,pt;q=0.8
GET /img/logo-getit.png HTTP/1.1
Host: 0.0.0.0:8080
Connection: keep-alive
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: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://0.0.0.0:8080/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,pt;q=0.8

O nosso servidor responde a mesma página HTML para todas as requisições. Não importa se o cliente está pedindo a imagem /img/logo-getit.png ou o favicon.ico. Ele sempre responde com a mesma página HTML.

Repare que a primeira linha da requisição HTTP sempre possui uma palavra (GET - voltaremos a falar sobre ele mais para a frente no curso) seguida de uma rota (por exemplo, /img/logo-getit.png). Nós podemos utilizar essa rota no servidor para decidir o que devemos responder.

Altere novamente o código do seu servidor para:

import socket
from pathlib import Path
from utils import extract_route, read_file

CUR_DIR = Path(__file__).parent
SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8080

RESPONSE_TEMPLATE = '''
HTML apagado por questão de espaço, mas você deve manter o código anterior aqui.
'''

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 = 'HTTP/1.1 200 OK\n\n'.encode() + read_file(filepath)
    else:
        response = RESPONSE_TEMPLATE.encode()
    client_connection.sendall(response)

    client_connection.close()

server_socket.close()

Crie também o arquivo utils.py na pasta do seu servidor. Você deverá implementar os métodos extract_route e read_file. Para te ajudar, baixe também o arquivo test_utils.py. Ele possui alguns testes para verificar se a sua implementação está dentro do esperado.

Para executar os testes basta rodar o arquivo no terminal (ele tem alguns testes para outras funções das próximas partes do handout - você pode ignorar os erros delas por enquanto).:

python test_utils.py

Ao rodar os teste a saída do terminal deverá ser algo parecido com as mensagens a seguir:

A função extract_route não foi implementada, portanto não é possível testá-la.
A função read_file não foi implementada, portanto não é possível testá-la.
A função load_data não foi implementada, portanto não é possível testá-la.
A função load_template não foi implementada, portanto não é possível testá-la.
A função build_response não foi implementada, portanto não é possível testá-la.

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

EXERCÍCIO

Implemente a função extract_route, que recebe uma string com a requisição e devolve a rota, excluindo o primeiro caractere (/).

Por exemplo, para a requisição:

GET /img/logo-getit.png HTTP/1.1
Host: 0.0.0.0:8080
Connection: keep-alive
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: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://0.0.0.0:8080/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,pt;q=0.8

Sua função deve devolver 'img/logo-getit.png'.

Dica

É possível verificar se a implementação da função pedida no exercício anterior está correta, basta rodar os testes. Por exemplo, se a implementação da função extract_route não estiver correta, então algumas mensagens de erro serão apresentadas no terminal ao rodar os testes.

Question

No código do servidor.py, adicionamos um import para a biblioteca pathlib. Essa biblioteca é utilizada para manipulação de caminhos de arquivos e diretórios. Qual o conteúdo da variável CUR_DIR após a execução do código a seguir?

CUR_DIR = Path(__file__).parent

Escolhe a opção correta:

  • CUR_DIR é uma string com o caminho do diretório onde o arquivo servidor.py está localizado.
  • CUR_DIR é uma string com o caminho do diretório onde o arquivo servidor.py está localizado, mas com o nome do arquivo incluso.
  • CUR_DIR é uma string com o caminho do diretório onde o arquivo servidor.py está localizado, mas com o nome do arquivo e a extensão inclusos.
  • CUR_DIR é uma string com o caminho do diretório onde o arquivo servidor.py está localizado, mas com o nome do arquivo, a extensão e o nome do diretório inclusos.

Resposta

A variável CUR_DIR é uma string com o caminho do diretório onde o arquivo servidor.py está localizado. Ou seja, será algo como '/home/usuario/tecweb/aulas/01-getit'. Para mais detalhes acesse: https://docs.python.org/3/library/pathlib.html#pathlib.Path.parent

Question

Ainda explorando o uso da biblioteca pathlib, o que será impresso no terminal ao executar o código a seguir?

Suponha que a variável CUR_DIR seja a string /home/usuario/tecweb/aulas/01-getit

CUR_DIR = Path(__file__).parent
route = 'img/logo-getit.png'
filepath = CUR_DIR / route
print(filepath)

Escolhe a opção correta:

  • img/logo-getit.png
  • /home/usuario/tecweb/aulas/01-getit
  • /home/usuario/tecweb/aulas/01-getit/img/logo-getit.png
  • Ocorrerá algum erro.

Resposta

Será impresso /home/usuario/tecweb/aulas/01-getit/img/logo-getit.png, pois a operação / entre dois objetos do tipo Path concatena os caminhos. Para mais detalhes acesse: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.truediv

Ao desenvolver aplicações que manipulam arquivos e diretórios, é comum utilizar a biblioteca pathlib para manipular caminhos de arquivos e diretórios. Não é uma boa prática utilizar caminhos absolutos.

EXERCÍCIO

Implemente a função read_file, que recebe um argumento do tipo Path e devolve o conteúdo desse arquivo. Sua função deve ler o arquivo e devolver o conteúdo como binário (bytes). Se precisar refrescar a memória, leia a documentação da função open.

Depois de implementar as duas funções acima e atualizar o código, o servidor deve funcionar corretamente, mostrando as imagens. Sim, está feio, mas nós resolvemos isso na próxima aula. Por enquanto vai ficar assim mesmo.

As páginas de detalhes ainda não estão prontas, mas antes disso precisamos refatorar o código porque ele já está acumulando muitas responsabilidades. Depois de se hidratar e fazer um alongamento, siga para a parte 3 do handout.