Atividade Comunicação HTTP (EstanteWeb)
Disciplina: Programação para Web 3
Assunto: Métodos HTTP, parâmetros, cabeçalhos, status codes e formulários HTML
Professor: Prof. Me. Danilo Bueno
Grupos: duplas
Duração: 4 aulas de 50 minutos (em sala)
Importante
Antes de começar, leia o material de apoio sobre comunicação HTTP. É imprescindível ler antes de codar: a atividade aplica exatamente os conceitos do material. Se tiver dúvidas, releia, pergunte ao professor ou aos colegas. Você também pode consultar os slides da aula.
Contextualização
A EstanteWeb já tem um servidor no ar, com rotas para listar o acervo e contar quantos livros existem. Mas até agora o site só fala: todo mundo recebe a mesma resposta, sempre. Um cliente que quer livros até determinado preço não tem como filtrar; a loja não tem como receber uma pergunta ou uma sugestão de um visitante. Nesta etapa, a dupla vai ensinar o servidor a receber dados do visitante, não só devolvê-los, e a responder com o status e os cabeçalhos certos para cada situação.
O que já temos
O checkpoint de partida (semana-03-inicio) traz a aplicação Spring Boot da semana passada, já rodando em http://localhost:8080, com:
PaginaController: rotas/e/sobre.Livro(modelo):titulo,autor,ano,preco.LivroRepository: guarda o acervo em memória, comlistarTodos()econtarLivros().LivroController: rotas/livros,/livros/totale/livros/html, todas GET, sem nenhum parâmetro.
Missão 1: Filtrando o acervo por parâmetro
Objetivo: aceitar um parâmetro de consulta na URL e usá-lo para filtrar o acervo.
1.1. No LivroRepository, crie o método listarAtePreco(double precoMaximo), que percorre livros e devolve apenas os que custam até precoMaximo. No LivroController, crie a rota /livros/busca, recebendo precoMaximo via @RequestParam (tipo double) e retornando a lista filtrada no mesmo formato de texto usado em /livros. Teste em http://localhost:8080/livros/busca?precoMaximo=25.
1.2. Torne precoMaximo opcional, com defaultValue alto o suficiente para trazer o acervo inteiro (ex.: "999"). Teste acessando /livros/busca sem nenhuma query string e confirme que o acervo completo aparece.
1.3. Acesse http://localhost:8080/livros/busca?precoMaximo=abc (um valor que não é número) e observe, no DevTools, o status da resposta.
Você não escreveu nenhum código para tratar
precoMaximo=abc, mas o servidor respondeu com um erro. Quem decidiu esse comportamento, e o que isso revela sobre o papel do tipo declarado no parâmetro do método?
Missão 2: Cabeçalhos e status sob controle
Objetivo: trocar o retorno simples por ResponseEntity e decidir status e cabeçalhos explicitamente.
2.1. Altere a rota /livros/busca para retornar ResponseEntity<String>: se a busca não encontrar nenhum livro dentro do preço, devolva status 404 com uma mensagem explicando; caso contrário, devolva 200 com a lista. Teste os dois casos e confira o status em cada um no DevTools.
2.2. Na resposta de sucesso (200) da mesma rota, acrescente um cabeçalho próprio X-Total-Encontrados com a quantidade de livros que a busca encontrou. Confira o cabeçalho na aba Network do DevTools (seção de Response Headers).
2.3. Crie a rota /livros/quem-acessa, que lê o cabeçalho de requisição User-Agent (via @RequestHeader) e devolve uma frase informando de onde veio o acesso. Teste no navegador e depois com uma ferramenta de linha de comando (ex.: curl http://localhost:8080/livros/quem-acessa) e compare o valor do User-Agent nos dois casos.
Por que faz sentido devolver 404 para uma busca sem resultado, em vez de devolver 200 com uma lista vazia ou uma mensagem de erro dentro do corpo?
Missão 3: Um formulário de contato
Objetivo: criar um formulário HTML e a rota que recebe seus dados por POST.
3.1. No PaginaController, crie a rota /contato (GET, produces = "text/html") retornando uma página com um <form method="post" action="/contato">, com campos de texto nome e mensagem e um botão de envio.
3.2. Crie o método correspondente com @PostMapping("/contato"), recebendo nome e mensagem via @RequestParam e retornando uma confirmação, por exemplo: "Obrigado, <nome>! Sua mensagem foi recebida.". Preencha o formulário no navegador, envie, e confira na aba Network do DevTools: o método da requisição, o cabeçalho Content-Type enviado e os dados no corpo (seção de Payload/Form Data).
3.3. Adicione uma validação simples: se mensagem vier em branco, devolva 400 (Bad Request) com uma mensagem de erro, usando ResponseEntity. Teste enviando o formulário com o campo de mensagem vazio.
Ao enviar o formulário, o navegador te levou para uma nova página com a resposta do servidor, sem nenhuma linha de JavaScript escrita por vocês. O que, na tag
<form>, é responsável por esse comportamento?
Missão 4: Sugestão de leitura (consolidação + desafio)
Objetivo: repetir o padrão formulário → POST → resposta com status, agora guardando o dado recebido.
4.1. Crie a classe Sugestao (modelo), com os atributos nomeCliente e livroSugerido, construtor e getters. Crie SugestaoRepository, com uma lista mutável (new ArrayList<>(), não List.of(...)) e um método adicionar(Sugestao s).
4.2. Crie SugestaoController, com a rota /sugestoes/nova (GET, HTML) mostrando um formulário com os campos nomeCliente e livroSugerido, method="post" e action="/sugestoes".
4.3. Crie POST /sugestoes, recebendo os dois campos via @RequestParam, guardando uma nova Sugestao no repositório e retornando ResponseEntity com status 201 (Created), um cabeçalho X-Total-Sugestoes com o total acumulado, e uma confirmação no corpo.
4.4. Desafio (opcional): crie a rota GET /sugestoes, listando (em HTML, <ul>/<li>) todas as sugestões recebidas desde que o servidor subiu. Envie duas ou três sugestões pelo formulário e confirme que todas aparecem na listagem.
Se o servidor for reiniciado agora, o que acontece com as sugestões já enviadas? Por que isso ainda é um problema em aberto para a EstanteWeb?
Entrega individual
Cada integrante da dupla deve, individualmente, no Moodle:
- Responder o questionário da semana (perguntas de reflexão sobre a atividade).
- Enviar o projeto compactado em
.zip(a pasta do projeto sem a subpastatarget/, que é gerada na compilação e pesa à toa).