Métodos HTTP
GET, POST, PUT, PATCH e DELETE, e quais deles podem ser repetidos com segurança.
O método é a primeira palavra de toda requisição. Ele diz ao servidor o que você quer fazer com o recurso da URL. A mesma URL, /api/Books/1entre para ver, devolve um livro com GET, altera-o com PUT ou PATCH e o remove com DELETE. A maioria das APIs usa cinco métodos para quase tudo, e mais alguns aparecem de vez em quando.
É nos métodos que a diferença entre um navegador e uma ferramenta de testes começa a importar. A barra de endereços só envia GET. Para o resto você precisa de curl, Postman ou um botão Enviar como os desta página. Os exemplos que alteram dados compartilhados usam curl, para que os botões Enviar desta página não modifiquem registros que outras pessoas estão vendo. A lição 6, Enviando requisições com curl e Postman, prepara as duas ferramentas direito. Se você ainda não tem o curl, leia os exemplos de curl aqui e rode-os depois dessa lição.
Os cinco métodos do dia a dia
| Método | O que faz | Status de sucesso típico |
|---|---|---|
GET | Lê um recurso ou uma lista. Não envia corpo. | 200 |
POST | Cria um recurso novo ou dispara uma ação. | 201, às vezes 200 ou 202 |
PUT | Substitui um recurso pelo corpo que você envia. | 200 ou 204 |
PATCH | Altera alguns campos de um recurso. | 200 ou 204 |
DELETE | Remove um recurso. | 200 ou 204 |
Cada um dos endpoints de métodos desta API aceita um verbo e descreve o que recebeu. Envie um GET:
/api/methods/get?x=1E um POST com corpo:
/api/methods/post{"hello":"world"}Esses endpoints não guardam nada. Para ver o que os métodos fazem com dados reais, você precisa de um recurso, e o resto desta lição usa livros.
POST cria
Crie um livro:
curl -i -X POST https://api.testingapis.com/api/Books \
-H "Content-Type: application/json" \
-d '{"title":"The Pragmatic Programmer","author":"David Thomas",
"isbn":"9780135957059","genre":"programming","publishedYear":2019,
"pages":352,"language":"English","price":42.5,"rating":4.6,
"available":true,"tags":["career"]}'HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"message": "Book created successfully",
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "9780135957059",
"genre": "programming",
"publishedYear": 2019,
"pages": 352,
"language": "English",
"price": 42.5,
"rating": 4.6,
"available": true,
"tags": ["career"]
}
}O servidor respondeu 201 Created, atribuiu o ID 29 e devolveu o registro novo. Você vai receber outro ID, porque outras pessoas também criam livros. Essa é a primeira coisa a testar em qualquer POST: quem escolhe o ID é o servidor, e o seu teste precisa lê-lo da resposta em vez de adivinhar.
Muitas APIs também mandam um cabeçalho Location no 201 com a URL do recurso novo, como Location: /api/Books/29entre para ver. Esta não manda. O padrão não exige, mas se a documentação prometesse, seria um bug.
Agora rode o mesmo POST uma segunda vez. Você recebe um segundo livro com um ID novo, idêntico exceto por esse número. POST não é seguro de repetir: cada chamada cria algo novo. Lembre disso para a tabela mais abaixo.
PUT substitui, PATCH altera
Os dois métodos atualizam um registro, e as pessoas os confundem o tempo todo. A diferença aparece nos campos que você deixa de fora.
PATCH
Altere só o preço do livro 29:
curl -X PATCH https://api.testingapis.com/api/Books/29 \
-H "Content-Type: application/json" \
-d '{"price":39.99}'{
"message": "Book patched successfully",
"changed": ["price"],
"ignored": [],
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "9780135957059",
"genre": "programming",
"publishedYear": 2019,
"pages": 352,
"language": "English",
"price": 39.99,
"rating": 4.6,
"available": true,
"tags": ["career"]
}
}Só o preço mudou. Tudo o que você não mencionou manteve o valor. Esta API também diz quais campos alterou, o que é incomum e muito útil para testar.
PUT
Agora envie um PUT com só três campos:
curl -X PUT https://api.testingapis.com/api/Books/29 \
-H "Content-Type: application/json" \
-d '{"title":"The Pragmatic Programmer","author":"David Thomas","price":39.99}'{
"message": "Book updated successfully",
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "",
"genre": "",
"publishedYear": 0,
"pages": 0,
"language": "",
"price": 39.99,
"rating": 0,
"available": false,
"tags": []
}
}O ISBN, o gênero, o ano, o número de páginas, o idioma, a avaliação e as tags sumiram. PUT significa "aqui está a nova versão completa deste recurso". Todo campo que você não enviou foi substituído por um valor vazio ou zero. A API fez exatamente o que o PUT define, e mesmo assim apagou quase todo um registro porque o cliente enviou um corpo parcial.
Esse é um dos bugs reais mais comuns em apps. Um desenvolvedor usa PUT para atualizar um campo, envia só esse campo, e todos os outros campos do registro são apagados sem alarde. Ao testar uma funcionalidade de atualização, envie um corpo parcial e depois faça GET do registro para ver o que sobrou.
Algumas APIs tratam isso de outro jeito. Elas tratam um PUT com campos ausentes como um PATCH, ou o recusam com 400 porque faltam campos obrigatórios. A última opção é provavelmente a mais segura. O que você testa depende da documentação, mas você sempre testa.
DELETE remove
Apague o livro 29 e depois tente apagá-lo de novo:
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"message":"Book deleted successfully"} (200 OK)
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"status":404,"error":"Not Found","message":"Book with ID 29 does not exist."} (404 Not Found)A primeira chamada devolve 200 com uma mensagem. A segunda devolve 404, porque não sobrou nada para apagar. Depois de uma exclusão, confira também se um GET do mesmo ID devolve 404. Alguns sistemas só marcam os registros como apagados e continuam devolvendo-os, o que é um bug se a documentação disser o contrário.
Métodos seguros e idempotentes
Duas propriedades do padrão HTTP definem como clientes e testes podem tratar cada método.
Um método seguro não altera nada no servidor. Você pode chamá-lo quantas vezes quiser. Um método idempotente pode alterar coisas, mas chamá-lo duas vezes tem o mesmo efeito no servidor que chamá-lo uma.
| Método | Seguro | Idempotente |
|---|---|---|
GET | Sim | Sim |
HEAD | Sim | Sim |
OPTIONS | Sim | Sim |
PUT | Não | Sim |
DELETE | Não | Sim |
POST | Não | Não |
PATCH | Não | Não garantido |
PUT é idempotente porque enviar duas vezes o mesmo registro completo deixa o mesmo registro. DELETE é idempotente mesmo com a segunda chamada recebendo 404: a resposta foi diferente, mas o estado do servidor (o livro 29 não existe) é o mesmo nos dois casos. Idempotência diz respeito ao efeito, não à resposta. POST não é idempotente, como você viu quando o segundo POST criou um segundo livro. PATCH pode ser uma coisa ou outra. Definir o preço como 39.99 é idempotente; um patch que diz "some 1 ao estoque" não é.
Isso importa porque redes falham. Se uma requisição estoura o tempo, o cliente não sabe se o servidor a recebeu. Repetir um GET ou um PUT é inofensivo. Repetir um POST pode cobrar um cartão duas vezes. Navegadores, proxies e bibliotecas HTTP usam essas regras para decidir se repetem por conta própria, e Timeouts, retries e limites de taxa mostra como as APIs tornam o POST seguro de repetir com chaves de idempotência.
Para quem testa, essas regras dão casos de teste de graça. Chame um GET e confira que nada mudou. Chame um PUT duas vezes e confira que a segunda chamada não criou nem duplicou nada. Um GET que altera dados (como /api/deleteUser?id=5entre para ver, que existe em alguns códigos por aí) é um erro de projeto, porque crawlers, prévias de link e o prefetch do navegador enviam requisições GET sem perguntar.
HEAD e OPTIONS
HEAD é um GET sem o corpo. O servidor manda o mesmo status e os mesmos cabeçalhos que mandaria para um GET e para por aí. Os clientes o usam para conferir se algo existe ou qual o tamanho de um arquivo sem baixá-lo.
HTTP/1.1 200 OK
x-message: HEAD request received. Responses to HEAD carry headers only.
x-method: HEADOPTIONS pergunta o que é permitido numa URL. Os navegadores o enviam automaticamente antes de algumas requisições entre origens (um "preflight"), e é por isso que ele aparece nos logs de rede mesmo quando nenhum código o chama. Você raramente vai testá-lo diretamente, a não ser que trabalhe com configurações de CORS.
/api/methods/optionsUsando o método errado
/api/methods/get só aceita GET. Mande um POST para ele com curl -i -X POST https://api.testingapis.com/api/methods/get:
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: GET405 Method Not Allowed significa que a URL existe mas não aceita esse método. O cabeçalho allow lista os que ela aceita. Um POST para um único livro, que não faz sentido porque livros são criados em /api/Booksentre para ver, recebe o mesmo tratamento:
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: DELETE, GET, HEAD, PATCH, PUTMandar todos os métodos para todos os endpoints é um teste barato que encontra problemas reais. Você procura métodos que deveriam ser recusados e não são (um DELETE que funciona num endpoint que deveria ser só de leitura), e 500 ou 404 onde caberia um 405. Repare que esta API devolve o 405 com corpo vazio, enquanto os outros erros dela são JSON. Clientes que esperam um corpo de erro em JSON em todo 4xx vão tropeçar nisso.
Exercício
Faça isto com o curl (ou volte aqui depois da lição de curl).
- Crie um livro com POST. Anote o ID.
- Faça PATCH do título. Faça GET e confirme que só o título mudou.
- Faça PUT com um corpo que tenha todos os campos exceto
tags. O que aconteceu com as tags? - Envie o mesmo PUT de novo. Alguma coisa mudou na segunda vez?
- Faça DELETE, GET e DELETE de novo. Anote os três códigos de status.
- Envie um DELETE para
/api/Booksentre para ver (sem ID). Antes de rodar, preveja o código de status. Depois confira o cabeçalhoallow.