Uma história sobre HTTP status code e por que você deve ler a documentação
doug-source
Posted on July 16, 2024
Nota: apenas traduzi o texto abaixo e postei aqui. As referências estão no fim deste artigo.
Desde 2020, tenho trabalhado em um Express apps (Node.js framework) para potencializar as interações e viewer events que acontecem enquanto estou streaming live coding no Twitch — meu Twitch bot. Desde que usei o Sentry para monitoramento de erros e travamentos usando o Sentry Node SDK, já eliminei alguns bugs que eram inteiramente resultado do meu próprio código terrível. Mas recentemente, disparei um preventable application error por meio de uma chamada aparentemente falhada para uma API de terceiros (o que me confundiu, pois retornou um HTTP status code de 200). Isso me lembrou da importância de enviar HTTP status codes corretos e representativos com responses — especialmente error responses — e como eu deveria realmente ler melhor a documentação.
O que disparou o error?
Ao streaming no Twitch, você pode "gritar" outras contas de streamer. Quando em live, isso envia uma notificação para o painel e fixa um CTA no topo do chat do Twitch para incentivar os espectadores a seguir o streamer. Meu Twitch bot também estende essa experiência enviando um anúncio para a janela de chat com o "stream title" mais recente e a categoria do streamer que foi anunciada, usando várias chamadas para Twitch API endpoints para construir a response.
Recentemente, ao tentar gritar com um usuário do Twitch, cometi um erro de digitação (afinal, sou apenas humano) e pedi ao meu bot Twitch bot para gritar com um usuário que não existia. O fato disso ter disparado um issue alert no Sentry (TypeError: Cannot read properties of undefined) destacou que eu não havia codificado de forma defensiva o suficiente para cometer erros de digitação - e este post irá detalhar como melhorei isso. No entanto, através de uma inspeção mais aprofundada da breadcrumb trail que levou a esse error event, percebi o que considerei ser uma falha na response da Twitch API.
A breadcrumb que precedeu a TypeError exception foi uma HTTP request para a Twitch API, requesting dados de usuário por meio de uma "login name string". Claramente, esse usuário não existia, mas com a response, a Twitch API retornou um HTTP status code de 200, indicando que estava tudo "OK". Na verdade, nem tudo estava "OK", pelo menos na minha application.
Verifiquei novamente a inexistência do login name requested (fa1kfaikfaik) navegando para twitch.tv/fa1kfaikfaik em um navegador – e isso foi confirmado na IU. Mas a aba Network também retornou um HTTP 200 com a response, quando eu esperava um HTTP 404 — not found.
Muitas vezes, ao lidar com HTTP responses, é uma boa prática verificar o response status code para proceder de acordo. Por exemplo, você só pode querer continuar enviando um welcome email a um novo cliente se a conta tiver sido criada com sucesso e a response retornada da API de criação de conta conter um HTTP status code de 201 (created). No entanto, neste caso, verificar o HTTP status code não teria resolvido meu problema.
Vamos dar uma olhada nos dados de response no código desta request específica para um usuário que não existe (observe como o Sentry adicionou o header sentry-trace
à chamada da Twitch API para conectar os pontos entre minha application e chamadas HTTP externas ao relatar problemas).
Agora, tecnicamente esta HTTP response é "OK". Temos um response object contendo uma propriedade chamada data, que é um array vazio; nada está intrinsecamente errado ou "malformed". Mas por que eu estava recebendo um HTTP 200 quando esperava um HTTP 404? A response está no design da Twitch API e está implícita na forma dos dados retornados e logados no terminal.
Eu escrevo código ruim e não li as docs, muito bem
O endpoint "/users" fornecido pela Twitch API permite que você obtenha informações sobre um ou mais usuários. O endpoint é /users
, plural, e não /user
, singular, o que está perfeitamente alinhado com o design da RESTful API. Por meio deste API endpoint, posso request dados de até 100 users. Se apenas uma em cada 100 requests contivesse um erro de digitação e a API falhasse completamente, isso seria uma experiência muito ruim para o desenvolvedor.
Apesar de inicialmente entender mal o API endpoint, isso reforça que compreender o design da API, codificar defensivamente em torno de quaisquer restrições e usar uma ferramenta de monitoramento de desempenho de applications como o Sentry pode ajudar a identificar essas lacunas no código da sua application.
Como eu corrigi meu código
Meu Twitch bot contém uma wrapper function em torno da Twitch API /users
que propositalmente só permite que minha application request dados para um único user. Aqui está o código original que causou a TypeError issue no Sentry em uma response vazia, que faz uma chamada para a Twitch API com as credenciais necessárias e executa uma verificação de idade da conta por motivos de segurança. Dado que a propriedade data
da Twitch response estava vazia, conforme mostrado no output do terminal acima, data[0]
não existia quando tentei acessar a propriedade created_at
.
async getUserByLogin(login) {
const accessTokenData = await accessTokenUtil.get();
if (accessTokenData) {
const request = await fetch(
`https://api.twitch.tv/helix/users?login=${login}`,
{
headers: {
authorization: `Bearer ${accessTokenData.accessToken}`,
"client-id": process.env.CLIENT_ID,
},
},
);
const response = await request.json();
const passesAgeCheck = accountIsOlderThanSevenDays(
response.data[0].created_at,
);
if (!passesAgeCheck) {
// ...
}
return response.data[0];
}
return undefined;
}
Para tornar meu código mais resiliente, adicionei uma verificação do length do array de dados e, se eu cometer um erro de digitação, enviarei uma mensagem para meu Twitch chat para me alertar sobre isso.
async getUserByLogin(login) {
const accessTokenData = await accessTokenUtil.get();
if (accessTokenData) {
const request = await fetch(// ...);
const response = await request.json();
+ if (response.data.length === 0) {
+ tmi.say(config.channel, "Typo: user not found");
+ return undefined;
+ }
// ...
return response.data[0];
}
return undefined;
}
Isso pode parecer óbvio agora. Mas não é incomum. Então, obrigado, Sentry, por me alertar sobre isso, me ajudar a entender melhor a API com a qual estava trabalhando e por me permitir tornar meu código mais robusto. Isso também ajudou a reduzir o ruído no Sentry causado pela minha incapacidade de digitar corretamente durante o streaming. Afinal, um erro de digitação não é um application error; é um user error.
Considerações finais sobre API design e HTTP status codes
Ao interagir com APIs, compreender os HTTP status codes enviados com as responses é uma ferramenta útil para ajudá-lo a codificar de forma defensiva contra application errors. O tratamento completo das API responses com base nos HTTP status codes retornados reduz a probabilidade de exceptions não detectadas, mas o mais importante é que, considerando os diferentes tipos de responses e HTTP codes que uma API pode retornar, leva a uma melhor compreensão do próprio API design.
E como eu realmente vi HTTP status codes de 200 retornados com error responses legítimas em minha carreira, aqui estão quatro coisas a serem consideradas ao criar APIs:
- Retorne HTTP response status codes corretos e representativos com API responses
- Retorne estruturas de dados consistentes e previsíveis
- Devolva dados organizados — não confie no API consumer para solicitar itens desnecessariamente, calcular valores arbitrariamente ou remover itens que não foram solicitados
- Decida quando um error é realmente um error; uma response vazia é um error ou é realmente "OK"?
E ao consumir APIs, não faça o que eu fiz. Leia a documentação, entenda como a API foi projetada e codifique defensivamente em torno de resultados inesperados. E se você realmente não tem tempo para isso, o Sentry e o Distributed Tracing está à sua volta.
Fonte
Artigo escrito por Salma Alam-Naylor.
Posted on July 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.