Como lidar com diferentes códigos de status HTTP em requisições Python

PythonBeginner
Pratique Agora

Introdução

Este tutorial irá guiá-lo através do tratamento de diferentes códigos de status HTTP em requisições Python. Os códigos de status HTTP são essenciais para entender se uma requisição web foi bem-sucedida ou falhou, e como responder adequadamente a diferentes cenários. Ao final deste laboratório, você aprenderá como implementar um tratamento de erros robusto em suas aplicações Python que interagem com serviços web.

Você começará entendendo os códigos de status HTTP, e então construirá progressivamente suas habilidades implementando técnicas básicas e avançadas de tratamento de erros. Este conhecimento é crucial para o desenvolvimento de aplicações web Python confiáveis que lidam graciosamente com várias respostas do servidor.

Entendendo os Códigos de Status HTTP e Configuração

O que são Códigos de Status HTTP?

Os códigos de status HTTP são números de três dígitos retornados por um servidor em resposta à requisição de um cliente. Eles indicam se uma requisição HTTP específica foi concluída com sucesso. Esses códigos são agrupados em cinco categorias:

  • 1xx (Informativo): A requisição foi recebida e o processo está continuando
  • 2xx (Sucesso): A requisição foi recebida, entendida e aceita com sucesso
  • 3xx (Redirecionamento): Ações adicionais devem ser tomadas para completar a requisição
  • 4xx (Erro do Cliente): A requisição contém sintaxe incorreta ou não pode ser atendida
  • 5xx (Erro do Servidor): O servidor falhou ao atender uma requisição aparentemente válida

Códigos de status comuns que você encontrará incluem:

Código de Status Nome Descrição
200 OK Requisição bem-sucedida
201 Created Requisição bem-sucedida e recurso criado
400 Bad Request Servidor não pode processar a requisição
401 Unauthorized Autenticação é necessária
404 Not Found O recurso solicitado não foi encontrado
500 Internal Server Error O servidor encontrou uma condição inesperada

Configurando Seu Ambiente Python

Antes de começarmos a lidar com códigos de status HTTP, vamos instalar a biblioteca requests do Python e criar um script de teste simples.

  1. Abra um terminal no ambiente LabEx e execute:
pip install requests
  1. Crie um novo diretório para os arquivos do nosso projeto:
mkdir -p ~/project/http_status_lab
cd ~/project/http_status_lab
  1. Usando o WebIDE, crie um novo arquivo chamado test_request.py no diretório http_status_lab com este código básico:
import requests

def make_request(url):
    """Make a basic GET request to the specified URL."""
    response = requests.get(url)
    print(f"Status Code: {response.status_code}")
    print(f"Response: {response.text[:100]}...")  ## Print first 100 characters
    return response

## Test with a working URL
response = make_request("https://httpbin.org/status/200")
print(f"Request was successful: {response.ok}")
  1. Execute o script de teste para vê-lo em ação:
python test_request.py

Você deve ver uma saída semelhante a esta:

Status Code: 200
Response: ...
Request was successful: True

Isso confirma que você configurou com sucesso o ambiente Python e pode fazer requisições HTTP básicas. Nos próximos passos, você aprenderá como lidar com diferentes códigos de status HTTP e implementar técnicas de tratamento de erros mais avançadas.

Lidando com Códigos de Status HTTP Básicos

Agora que você entende o que são códigos de status HTTP e configurou seu ambiente, vamos implementar o tratamento de erros básico para códigos de status comuns.

Criando uma Ferramenta de Teste de Códigos de Status

Primeiro, vamos criar uma ferramenta que nos permitirá testar diferentes códigos de status HTTP. O site httpbin.org fornece endpoints que retornam códigos de status específicos, o que é perfeito para nossos testes.

Crie um novo arquivo chamado status_code_tester.py no seu diretório http_status_lab com o seguinte código:

import requests
import sys

def test_status_code(status_code):
    """Test a specific HTTP status code using httpbin.org."""
    url = f"https://httpbin.org/status/{status_code}"
    try:
        response = requests.get(url)
        print(f"Status Code: {response.status_code}")
        print(f"Response OK: {response.ok}")
        return response
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return None

if __name__ == "__main__":
    ## Get status code from command line or use default
    status_code = sys.argv[1] if len(sys.argv) > 1 else "200"
    test_status_code(status_code)

Este script faz uma requisição para httpbin.org com um código de status específico e imprime o resultado. Você pode executá-lo com um argumento de linha de comando para testar diferentes códigos de status.

Testando Diferentes Códigos de Status

Vamos testar nosso script com diferentes códigos de status para ver como a biblioteca requests os lida:

  1. Teste uma requisição bem-sucedida (200 OK):
python status_code_tester.py 200

Saída esperada:

Status Code: 200
Response OK: True
  1. Teste um erro do cliente (404 Not Found):
python status_code_tester.py 404

Saída esperada:

Status Code: 404
Response OK: False
  1. Teste um erro do servidor (500 Internal Server Error):
python status_code_tester.py 500

Saída esperada:

Status Code: 500
Response OK: False

Lidando com Categorias de Códigos de Status Básicas

Agora, vamos criar um script mais abrangente que lida com diferentes categorias de códigos de status de forma apropriada. Crie um novo arquivo chamado basic_handler.py no seu diretório http_status_lab:

import requests

def handle_response(url):
    """Handle different HTTP status codes with basic error handling."""
    try:
        response = requests.get(url)

        ## Check status code category
        if 200 <= response.status_code < 300:
            print(f"Success! Status code: {response.status_code}")
            return response
        elif 300 <= response.status_code < 400:
            print(f"Redirection! Status code: {response.status_code}")
            ## For redirection, you might want to follow the redirect
            return response
        elif 400 <= response.status_code < 500:
            print(f"Client error! Status code: {response.status_code}")
            ## Handle client errors
            return response
        elif 500 <= response.status_code < 600:
            print(f"Server error! Status code: {response.status_code}")
            ## Handle server errors
            return response
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return None

## Test with different status codes
print("Testing 200 OK:")
handle_response("https://httpbin.org/status/200")

print("\nTesting 404 Not Found:")
handle_response("https://httpbin.org/status/404")

print("\nTesting 500 Internal Server Error:")
handle_response("https://httpbin.org/status/500")

Execute o script para ver como ele lida com diferentes códigos de status:

python basic_handler.py

Saída esperada:

Testing 200 OK:
Success! Status code: 200

Testing 404 Not Found:
Client error! Status code: 404

Testing 500 Internal Server Error:
Server error! Status code: 500

Este manipulador básico agrupa os códigos de status em categorias e toma diferentes ações com base na categoria. Este é um padrão comum em aplicações web Python, permitindo que você lide com diferentes tipos de respostas de forma apropriada.

Implementando Tratamento de Erros Avançado

Agora que você entende os conceitos básicos de tratamento de códigos de status HTTP, vamos implementar técnicas de tratamento de erros mais avançadas para suas aplicações Python.

Usando raise_for_status()

A biblioteca requests fornece um método conveniente chamado raise_for_status() que levanta uma exceção para códigos de status 4xx e 5xx. Esta é uma maneira simples, mas eficaz, de lidar com erros em seu código.

Crie um novo arquivo chamado raise_for_status_example.py no seu diretório http_status_lab:

import requests

def fetch_data(url):
    """Fetch data from a URL with error handling using raise_for_status()."""
    try:
        response = requests.get(url)
        ## This will raise an HTTPError if the HTTP request returned an unsuccessful status code
        response.raise_for_status()
        ## If we get here, the request was successful
        print(f"Success! Status code: {response.status_code}")
        return response.json() if 'application/json' in response.headers.get('Content-Type', '') else response.text
    except requests.exceptions.HTTPError as e:
        print(f"HTTP Error: {e}")
        return None
    except requests.exceptions.ConnectionError as e:
        print(f"Connection Error: {e}")
        return None
    except requests.exceptions.Timeout as e:
        print(f"Timeout Error: {e}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"Request Exception: {e}")
        return None

## Test with different URLs
print("Testing successful request:")
data = fetch_data("https://httpbin.org/json")
print(f"Received data type: {type(data)}")

print("\nTesting 404 error:")
data = fetch_data("https://httpbin.org/status/404")
print(f"Received data: {data}")

print("\nTesting 500 error:")
data = fetch_data("https://httpbin.org/status/500")
print(f"Received data: {data}")

Execute o script para ver como raise_for_status() lida com erros:

python raise_for_status_example.py

A saída esperada mostrará que a biblioteca automaticamente levanta exceções para códigos de status que não são 2xx, que seu código pode então capturar e tratar apropriadamente.

Criando Manipuladores de Exceção Personalizados

Para aplicações mais complexas, você pode querer criar manipuladores de exceção personalizados para fornecer um tratamento de erros mais específico. Vamos criar um exemplo mais avançado:

Crie um novo arquivo chamado custom_exception_handler.py no seu diretório http_status_lab:

import requests
import logging

## Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

## Custom exceptions
class APIError(Exception):
    """Base class for API request exceptions."""
    pass

class ClientError(APIError):
    """Exception raised for client errors (4xx)."""
    pass

class ServerError(APIError):
    """Exception raised for server errors (5xx)."""
    pass

class NotFoundError(ClientError):
    """Exception raised for 404 Not Found errors."""
    pass

class UnauthorizedError(ClientError):
    """Exception raised for 401 Unauthorized errors."""
    pass

def api_request(url, method="get", **kwargs):
    """Make an API request with comprehensive error handling."""
    try:
        ## Make the request using the specified HTTP method
        response = getattr(requests, method.lower())(url, **kwargs)

        ## Log the response status
        logger.info(f"Request to {url} returned status code {response.status_code}")

        ## Handle different status codes
        if 200 <= response.status_code < 300:
            return response
        elif response.status_code == 401:
            raise UnauthorizedError(f"Authentication required: {response.text}")
        elif response.status_code == 404:
            raise NotFoundError(f"Resource not found: {url}")
        elif 400 <= response.status_code < 500:
            raise ClientError(f"Client error {response.status_code}: {response.text}")
        elif 500 <= response.status_code < 600:
            raise ServerError(f"Server error {response.status_code}: {response.text}")
        else:
            raise APIError(f"Unexpected status code {response.status_code}: {response.text}")

    except requests.exceptions.RequestException as e:
        logger.error(f"Request failed: {e}")
        raise APIError(f"Request failed: {e}")

## Test the custom exception handler
def test_api_request(url):
    """Test the api_request function with error handling."""
    try:
        response = api_request(url)
        print(f"Success! Status code: {response.status_code}")
        print(f"Response: {response.text[:100]}...")  ## Print first 100 characters
        return response
    except UnauthorizedError as e:
        print(f"Authentication Error: {e}")
    except NotFoundError as e:
        print(f"Not Found Error: {e}")
    except ClientError as e:
        print(f"Client Error: {e}")
    except ServerError as e:
        print(f"Server Error: {e}")
    except APIError as e:
        print(f"API Error: {e}")
    return None

## Test with different status codes
print("Testing 200 OK:")
test_api_request("https://httpbin.org/status/200")

print("\nTesting 404 Not Found:")
test_api_request("https://httpbin.org/status/404")

print("\nTesting 401 Unauthorized:")
test_api_request("https://httpbin.org/status/401")

print("\nTesting 500 Internal Server Error:")
test_api_request("https://httpbin.org/status/500")

Execute o script para ver como o tratamento de exceção personalizado funciona:

python custom_exception_handler.py

Este exemplo avançado demonstra vários conceitos importantes:

  1. Classes de exceção personalizadas que estendem a classe base Exception
  2. Exceções hierárquicas para diferentes tipos de erros
  3. Log detalhado para depuração e monitoramento
  4. Tratamento específico para diferentes códigos de status

Esta abordagem permite que você lide com diferentes tipos de erros de uma maneira mais estruturada e sustentável, o que é essencial para aplicações maiores.

Construindo um Cliente de API Web Completo

Nesta etapa final, reuniremos tudo o que você aprendeu para construir um cliente de API web completo com tratamento de erros robusto. Criaremos um cliente para a API JSONPlaceholder, que é uma API REST online gratuita que você pode usar para testes.

Criando um Cliente de API Simples

Vamos criar um arquivo chamado api_client.py no seu diretório http_status_lab:

import requests
import json
import logging

## Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class APIClient:
    """A simple API client with robust error handling."""

    def __init__(self, base_url):
        """Initialize the API client with a base URL."""
        self.base_url = base_url
        self.session = requests.Session()

    def request(self, endpoint, method="get", params=None, data=None, headers=None):
        """Make a request to the API with error handling."""
        url = f"{self.base_url}{endpoint}"

        ## Default headers for JSON APIs
        if headers is None:
            headers = {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            }

        ## Convert data to JSON if it's a dictionary
        json_data = None
        if data and isinstance(data, dict):
            json_data = data
            data = None

        try:
            ## Log the request
            logger.info(f"Making {method.upper()} request to {url}")

            ## Make the request
            response = self.session.request(
                method=method,
                url=url,
                params=params,
                data=data,
                json=json_data,
                headers=headers
            )

            ## Log the response status
            logger.info(f"Received response with status code {response.status_code}")

            ## Check for HTTP errors
            response.raise_for_status()

            ## Parse JSON response if applicable
            try:
                return response.json()
            except json.JSONDecodeError:
                return response.text

        except requests.exceptions.HTTPError as e:
            status_code = e.response.status_code
            error_message = f"HTTP Error: {status_code}"

            try:
                ## Try to get more details from the response
                error_data = e.response.json()
                error_message = f"{error_message} - {error_data.get('message', str(error_data))}"
            except (json.JSONDecodeError, AttributeError):
                error_message = f"{error_message} - {e.response.text if hasattr(e, 'response') else str(e)}"

            logger.error(error_message)

            ## Re-raise with more context
            if 400 <= status_code < 500:
                logger.error(f"Client error: {error_message}")
                raise Exception(f"Client error (HTTP {status_code}): {error_message}")
            elif 500 <= status_code < 600:
                logger.error(f"Server error: {error_message}")
                raise Exception(f"Server error (HTTP {status_code}): {error_message}")
            else:
                raise

        except requests.exceptions.ConnectionError as e:
            logger.error(f"Connection Error: {e}")
            raise Exception(f"Connection Error: Could not connect to {url}")

        except requests.exceptions.Timeout as e:
            logger.error(f"Timeout Error: {e}")
            raise Exception(f"Timeout Error: Request to {url} timed out")

        except requests.exceptions.RequestException as e:
            logger.error(f"Request Exception: {e}")
            raise Exception(f"Request Error: {str(e)}")

    def get(self, endpoint, params=None, headers=None):
        """Make a GET request to the API."""
        return self.request(endpoint, method="get", params=params, headers=headers)

    def post(self, endpoint, data=None, headers=None):
        """Make a POST request to the API."""
        return self.request(endpoint, method="post", data=data, headers=headers)

    def put(self, endpoint, data=None, headers=None):
        """Make a PUT request to the API."""
        return self.request(endpoint, method="put", data=data, headers=headers)

    def delete(self, endpoint, headers=None):
        """Make a DELETE request to the API."""
        return self.request(endpoint, method="delete", headers=headers)

## Test the API client with JSONPlaceholder
def test_api_client():
    """Test the API client with JSONPlaceholder."""
    client = APIClient("https://jsonplaceholder.typicode.com")

    try:
        ## Get a list of posts
        print("\nGetting posts:")
        posts = client.get("/posts")
        print(f"Retrieved {len(posts)} posts")
        print(f"First post: {posts[0]}")

        ## Get a single post
        print("\nGetting a single post:")
        post = client.get("/posts/1")
        print(f"Retrieved post: {post}")

        ## Get a non-existent post (404)
        print("\nTrying to get a non-existent post:")
        try:
            client.get("/posts/999999")
        except Exception as e:
            print(f"Expected error: {e}")

        ## Create a new post
        print("\nCreating a new post:")
        new_post = client.post("/posts", {
            "title": "My New Post",
            "body": "This is the content of my new post.",
            "userId": 1
        })
        print(f"Created post: {new_post}")

        ## Update a post
        print("\nUpdating a post:")
        updated_post = client.put("/posts/1", {
            "id": 1,
            "title": "Updated Title",
            "body": "Updated content.",
            "userId": 1
        })
        print(f"Updated post: {updated_post}")

        ## Delete a post
        print("\nDeleting a post:")
        delete_response = client.delete("/posts/1")
        print(f"Delete response: {delete_response}")

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    test_api_client()

Execute o script para ver como nosso cliente de API completo lida com diferentes cenários:

python api_client.py

Testando o Tratamento de Erros

Vamos criar um arquivo separado para testar as capacidades de tratamento de erros do nosso cliente de API. Crie um arquivo chamado test_error_handling.py no seu diretório http_status_lab:

from api_client import APIClient
import time

def test_different_errors():
    """Test different error scenarios with our API client."""

    ## Test with a valid API
    print("\n1. Testing with a valid API:")
    valid_client = APIClient("https://jsonplaceholder.typicode.com")
    try:
        data = valid_client.get("/posts/1")
        print(f"Success! Retrieved data: {data}")
    except Exception as e:
        print(f"Unexpected error: {e}")

    ## Test with a 404 error
    print("\n2. Testing with a non-existent endpoint (404):")
    try:
        valid_client.get("/non_existent_endpoint")
    except Exception as e:
        print(f"Expected error: {e}")

    ## Test with an invalid host
    print("\n3. Testing with an invalid host (Connection Error):")
    invalid_client = APIClient("https://this-does-not-exist-123456789.com")
    try:
        invalid_client.get("/anything")
    except Exception as e:
        print(f"Expected error: {e}")

    ## Test with a timeout
    print("\n4. Testing with a timeout:")
    timeout_client = APIClient("https://httpbin.org")
    try:
        ## httpbin.org/delay/5 will delay the response for 5 seconds
        ## but we'll set the timeout to 2 seconds
        timeout_client.session.request = lambda **kwargs: timeout_client.session.request_original(
            **{**kwargs, 'timeout': 2}
        )
        timeout_client.session.request_original = timeout_client.session.request
        timeout_client.get("/delay/5")
    except Exception as e:
        print(f"Expected error: {e}")

if __name__ == "__main__":
    test_different_errors()

O script acima pode produzir erros devido ao tratamento de timeout, pois ele tenta modificar um método em tempo de execução. Vamos simplificá-lo para evitar esses problemas:

from api_client import APIClient

def test_different_errors():
    """Test different error scenarios with our API client."""

    ## Test with a valid API
    print("\n1. Testing with a valid API:")
    valid_client = APIClient("https://jsonplaceholder.typicode.com")
    try:
        data = valid_client.get("/posts/1")
        print(f"Success! Retrieved data: {data}")
    except Exception as e:
        print(f"Unexpected error: {e}")

    ## Test with a 404 error
    print("\n2. Testing with a non-existent endpoint (404):")
    try:
        valid_client.get("/non_existent_endpoint")
    except Exception as e:
        print(f"Expected error: {e}")

    ## Test with a server error
    print("\n3. Testing with a server error (500):")
    error_client = APIClient("https://httpbin.org")
    try:
        error_client.get("/status/500")
    except Exception as e:
        print(f"Expected error: {e}")

if __name__ == "__main__":
    test_different_errors()

Execute o script de teste de erros:

python test_error_handling.py

Este script demonstra como nosso cliente de API lida com diferentes cenários de erro, fornecendo uma base robusta para aplicações do mundo real.

Principais Conclusões

Ao construir este cliente de API completo, você aprendeu várias técnicas importantes:

  1. Criar uma classe de cliente de API reutilizável
  2. Implementar tratamento de erros abrangente
  3. Registrar requisições e respostas para depuração
  4. Analisar diferentes formatos de resposta
  5. Lidar com diferentes métodos HTTP (GET, POST, PUT, DELETE)

Essas habilidades são essenciais para construir aplicações Python robustas que interagem com APIs web, garantindo que seu código possa lidar graciosamente com vários cenários de erro e fornecer mensagens de erro significativas aos usuários.

Resumo

Neste laboratório, você aprendeu como lidar com códigos de status HTTP em requisições Python, progredindo de técnicas básicas para avançadas. Agora você tem as habilidades para:

  • Entender diferentes categorias de códigos de status HTTP e seus significados
  • Implementar tratamento de erros básico com verificação de código de status
  • Usar raise_for_status() para tratamento de erros simples
  • Criar classes de exceção personalizadas para tratamento de erros mais específico
  • Construir um cliente de API completo com tratamento de erros abrangente

Essas habilidades são essenciais para desenvolver aplicações Python robustas que interagem com serviços web. Ao lidar adequadamente com códigos de status HTTP e erros, você pode criar aplicações mais confiáveis que proporcionam melhores experiências ao usuário e são mais fáceis de manter.

Lembre-se de sempre considerar o tratamento de erros ao trabalhar com requisições HTTP em suas aplicações Python. É um aspecto crucial do desenvolvimento web que distingue o código profissional das implementações amadoras.