¿Cómo manejar los diferentes códigos de estado HTTP en las solicitudes de Python?

PythonBeginner
Practicar Ahora

Introducción

Este tutorial te guiará a través del manejo de diferentes códigos de estado HTTP en las solicitudes de Python. Los códigos de estado HTTP son esenciales para comprender si una solicitud web tuvo éxito o falló, y cómo responder adecuadamente a diferentes escenarios. Al final de este laboratorio, aprenderás a implementar un manejo de errores robusto en tus aplicaciones Python que interactúan con servicios web.

Comenzarás comprendiendo los códigos de estado HTTP, luego desarrollarás progresivamente tus habilidades implementando técnicas básicas y avanzadas de manejo de errores. Este conocimiento es crucial para desarrollar aplicaciones web Python confiables que manejen con elegancia diversas respuestas del servidor.

Comprensión de los Códigos de Estado HTTP y Configuración

¿Qué son los Códigos de Estado HTTP?

Los códigos de estado HTTP son números de tres dígitos devueltos por un servidor en respuesta a la solicitud de un cliente. Indican si una solicitud HTTP específica se ha completado con éxito. Estos códigos se agrupan en cinco categorías:

  • 1xx (Informativo): La solicitud fue recibida y el proceso continúa
  • 2xx (Éxito): La solicitud fue recibida, entendida y aceptada con éxito
  • 3xx (Redirección): Se debe tomar una acción adicional para completar la solicitud
  • 4xx (Error del Cliente): La solicitud contiene una sintaxis incorrecta o no se puede cumplir
  • 5xx (Error del Servidor): El servidor no pudo cumplir una solicitud aparentemente válida

Los códigos de estado comunes que encontrarás incluyen:

Código de Estado Nombre Descripción
200 OK La solicitud tuvo éxito
201 Creado La solicitud tuvo éxito y el recurso fue creado
400 Bad Request El servidor no puede procesar la solicitud
401 No Autorizado Se requiere autenticación
404 No Encontrado El recurso solicitado no se pudo encontrar
500 Internal Server Error El servidor encontró una condición inesperada

Configuración de tu Entorno Python

Antes de comenzar a manejar los códigos de estado HTTP, instalemos la biblioteca requests de Python y creemos un script de prueba simple.

  1. Abre una terminal en el entorno LabEx y ejecuta:
pip install requests
  1. Crea un nuevo directorio para los archivos de nuestro proyecto:
mkdir -p ~/project/http_status_lab
cd ~/project/http_status_lab
  1. Usando el WebIDE, crea un nuevo archivo llamado test_request.py en el directorio http_status_lab con 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. Ejecuta el script de prueba para verlo en acción:
python test_request.py

Deberías ver una salida similar a esta:

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

Esto confirma que has configurado con éxito el entorno Python y puedes realizar solicitudes HTTP básicas. En los siguientes pasos, aprenderás a manejar diferentes códigos de estado HTTP e implementar técnicas de manejo de errores más avanzadas.

Manejo de Códigos de Estado HTTP Básicos

Ahora que entiendes qué son los códigos de estado HTTP y has configurado tu entorno, implementemos el manejo básico de errores para los códigos de estado comunes.

Creación de una Herramienta de Prueba de Códigos de Estado

Primero, creemos una herramienta que nos permita probar diferentes códigos de estado HTTP. El sitio web httpbin.org proporciona endpoints que devuelven códigos de estado específicos, lo cual es perfecto para nuestras pruebas.

Crea un nuevo archivo llamado status_code_tester.py en tu directorio http_status_lab con el siguiente 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 realiza una solicitud a httpbin.org con un código de estado específico e imprime el resultado. Puedes ejecutarlo con un argumento de línea de comandos para probar diferentes códigos de estado.

Prueba de Diferentes Códigos de Estado

Probemos nuestro script con diferentes códigos de estado para ver cómo la biblioteca requests los maneja:

  1. Prueba una solicitud exitosa (200 OK):
python status_code_tester.py 200

Salida esperada:

Status Code: 200
Response OK: True
  1. Prueba un error del cliente (404 Not Found):
python status_code_tester.py 404

Salida esperada:

Status Code: 404
Response OK: False
  1. Prueba un error del servidor (500 Internal Server Error):
python status_code_tester.py 500

Salida esperada:

Status Code: 500
Response OK: False

Manejo de Categorías de Códigos de Estado Básicas

Ahora, creemos un script más completo que maneje diferentes categorías de códigos de estado de manera apropiada. Crea un nuevo archivo llamado basic_handler.py en tu directorio 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")

Ejecuta el script para ver cómo maneja diferentes códigos de estado:

python basic_handler.py

Salida 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 manejador básico agrupa los códigos de estado en categorías y toma diferentes acciones basadas en la categoría. Este es un patrón común en las aplicaciones web Python, lo que te permite manejar diferentes tipos de respuestas de manera apropiada.

Implementación del Manejo de Errores Avanzado

Ahora que entiendes los conceptos básicos del manejo de códigos de estado HTTP, implementemos técnicas de manejo de errores más avanzadas para tus aplicaciones Python.

Uso de raise_for_status()

La biblioteca requests proporciona un método conveniente llamado raise_for_status() que lanza una excepción para los códigos de estado 4xx y 5xx. Esta es una forma simple pero efectiva de manejar errores en tu código.

Crea un nuevo archivo llamado raise_for_status_example.py en tu directorio 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}")

Ejecuta el script para ver cómo raise_for_status() maneja los errores:

python raise_for_status_example.py

La salida esperada mostrará que la biblioteca lanza automáticamente excepciones para los códigos de estado que no son 2xx, que tu código puede luego capturar y manejar apropiadamente.

Creación de Manejadores de Excepciones Personalizados

Para aplicaciones más complejas, es posible que desees crear manejadores de excepciones personalizados para proporcionar un manejo de errores más específico. Creemos un ejemplo más avanzado:

Crea un nuevo archivo llamado custom_exception_handler.py en tu directorio 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")

Ejecuta el script para ver cómo funciona el manejo de excepciones personalizado:

python custom_exception_handler.py

Este ejemplo avanzado demuestra varios conceptos importantes:

  1. Clases de excepción personalizadas que extienden la clase base Exception
  2. Excepciones jerárquicas para diferentes tipos de errores
  3. Registro detallado para depuración y monitoreo
  4. Manejo específico para diferentes códigos de estado

Este enfoque te permite manejar diferentes tipos de errores de una manera más estructurada y mantenible, lo cual es esencial para aplicaciones más grandes.

Construyendo un Cliente de API Web Completo

En este paso final, reuniremos todo lo que has aprendido para construir un cliente de API web completo con un manejo de errores robusto. Crearemos un cliente para la API JSONPlaceholder, que es una API REST en línea gratuita que puedes usar para realizar pruebas.

Creación de un Cliente de API Simple

Creemos un archivo llamado api_client.py en tu directorio 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()

Ejecuta el script para ver cómo nuestro cliente de API completo maneja diferentes escenarios:

python api_client.py

Prueba del Manejo de Errores

Creemos un archivo separado para probar las capacidades de manejo de errores de nuestro cliente de API. Crea un archivo llamado test_error_handling.py en tu directorio 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()

El script anterior podría producir errores debido al manejo del tiempo de espera, ya que intenta modificar un método en tiempo de ejecución. Simplifiquémoslo para evitar esos 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()

Ejecuta el script de prueba de errores:

python test_error_handling.py

Este script demuestra cómo nuestro cliente de API maneja diferentes escenarios de error, proporcionando una base sólida para aplicaciones del mundo real.

Conclusiones Clave

Al construir este cliente de API completo, has aprendido varias técnicas importantes:

  1. Creación de una clase de cliente de API reutilizable
  2. Implementación del manejo de errores integral
  3. Registro de solicitudes y respuestas para la depuración
  4. Análisis de diferentes formatos de respuesta
  5. Manejo de diferentes métodos HTTP (GET, POST, PUT, DELETE)

Estas habilidades son esenciales para construir aplicaciones Python robustas que interactúan con las API web, asegurando que tu código pueda manejar con elegancia varios escenarios de error y proporcionar mensajes de error significativos a los usuarios.

Resumen

En este laboratorio, aprendiste a manejar los códigos de estado HTTP en las solicitudes de Python, progresando de técnicas básicas a avanzadas. Ahora tienes las habilidades para:

  • Comprender las diferentes categorías de códigos de estado HTTP y sus significados
  • Implementar el manejo básico de errores con la verificación de códigos de estado
  • Usar raise_for_status() para un manejo de errores simple
  • Crear clases de excepción personalizadas para un manejo de errores más específico
  • Construir un cliente de API completo con un manejo de errores integral

Estas habilidades son esenciales para desarrollar aplicaciones Python robustas que interactúan con servicios web. Al manejar correctamente los códigos de estado HTTP y los errores, puedes crear aplicaciones más confiables que proporcionen mejores experiencias de usuario y sean más fáciles de mantener.

Recuerda siempre considerar el manejo de errores cuando trabajes con solicitudes HTTP en tus aplicaciones Python. Es un aspecto crucial del desarrollo web que distingue el código profesional de las implementaciones amateur.