Comment gérer les différents codes d'état HTTP dans les requêtes Python

PythonBeginner
Pratiquer maintenant

Introduction

Ce tutoriel vous guidera à travers la gestion des différents codes d'état HTTP dans les requêtes Python. Les codes d'état HTTP sont essentiels pour comprendre si une requête web a réussi ou échoué, et comment répondre correctement aux différents scénarios. À la fin de ce lab, vous apprendrez à implémenter une gestion des erreurs robuste dans vos applications Python qui interagissent avec les services web.

Vous commencerez par comprendre les codes d'état HTTP, puis vous développerez progressivement vos compétences en implémentant des techniques de gestion des erreurs de base et avancées. Ces connaissances sont cruciales pour développer des applications web Python fiables qui gèrent avec élégance les différentes réponses du serveur.

Comprendre les codes d'état HTTP et la configuration

Que sont les codes d'état HTTP ?

Les codes d'état HTTP sont des nombres à trois chiffres renvoyés par un serveur en réponse à une requête du client. Ils indiquent si une requête HTTP spécifique a été menée à bien. Ces codes sont regroupés en cinq catégories :

  • 1xx (Informationnel) : La requête a été reçue et le processus continue
  • 2xx (Succès) : La requête a été reçue, comprise et acceptée avec succès
  • 3xx (Redirection) : Une action supplémentaire doit être entreprise pour compléter la requête
  • 4xx (Erreur client) : La requête contient une mauvaise syntaxe ou ne peut pas être satisfaite
  • 5xx (Erreur serveur) : Le serveur n'a pas réussi à satisfaire une requête apparemment valide

Les codes d'état courants que vous rencontrerez incluent :

Code d'état Nom Description
200 OK Requête réussie
201 Created Requête réussie et ressource créée
400 Bad Request Le serveur ne peut pas traiter la requête
401 Unauthorized L'authentification est requise
404 Not Found La ressource demandée n'a pas été trouvée
500 Internal Server Error Le serveur a rencontré une condition inattendue

Configuration de votre environnement Python

Avant de commencer à gérer les codes d'état HTTP, installons la bibliothèque Python requests et créons un script de test simple.

  1. Ouvrez un terminal dans l'environnement LabEx et exécutez :
pip install requests
  1. Créez un nouveau répertoire pour les fichiers de notre projet :
mkdir -p ~/project/http_status_lab
cd ~/project/http_status_lab
  1. En utilisant le WebIDE, créez un nouveau fichier appelé test_request.py dans le répertoire http_status_lab avec ce code de base :
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. Exécutez le script de test pour le voir en action :
python test_request.py

Vous devriez voir une sortie similaire à celle-ci :

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

Cela confirme que vous avez configuré avec succès l'environnement Python et que vous pouvez effectuer des requêtes HTTP de base. Dans les prochaines étapes, vous apprendrez à gérer différents codes d'état HTTP et à implémenter des techniques de gestion des erreurs plus avancées.

Gestion des codes d'état HTTP de base

Maintenant que vous comprenez ce que sont les codes d'état HTTP et que vous avez configuré votre environnement, implémentons une gestion des erreurs de base pour les codes d'état courants.

Création d'un outil de test des codes d'état

Tout d'abord, créons un outil qui nous permettra de tester différents codes d'état HTTP. Le site web httpbin.org fournit des points de terminaison qui renvoient des codes d'état spécifiques, ce qui est parfait pour nos tests.

Créez un nouveau fichier appelé status_code_tester.py dans votre répertoire http_status_lab avec le code suivant :

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)

Ce script effectue une requête vers httpbin.org avec un code d'état spécifique et affiche le résultat. Vous pouvez l'exécuter avec un argument de ligne de commande pour tester différents codes d'état.

Test de différents codes d'état

Testons notre script avec différents codes d'état pour voir comment la bibliothèque requests les gère :

  1. Testez une requête réussie (200 OK) :
python status_code_tester.py 200

Sortie attendue :

Status Code: 200
Response OK: True
  1. Testez une erreur client (404 Not Found) :
python status_code_tester.py 404

Sortie attendue :

Status Code: 404
Response OK: False
  1. Testez une erreur serveur (500 Internal Server Error) :
python status_code_tester.py 500

Sortie attendue :

Status Code: 500
Response OK: False

Gestion des catégories de codes d'état de base

Maintenant, créons un script plus complet qui gère correctement différentes catégories de codes d'état. Créez un nouveau fichier appelé basic_handler.py dans votre répertoire 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")

Exécutez le script pour voir comment il gère différents codes d'état :

python basic_handler.py

Sortie attendue :

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

Ce gestionnaire de base regroupe les codes d'état en catégories et prend différentes mesures en fonction de la catégorie. Il s'agit d'un modèle courant dans les applications web Python, qui vous permet de gérer différents types de réponses de manière appropriée.

Implémentation d'une gestion des erreurs avancée

Maintenant que vous comprenez les bases de la gestion des codes d'état HTTP, implémentons des techniques de gestion des erreurs plus avancées pour vos applications Python.

Utilisation de raise_for_status()

La bibliothèque requests fournit une méthode pratique appelée raise_for_status() qui lève une exception pour les codes d'état 4xx et 5xx. C'est un moyen simple mais efficace de gérer les erreurs dans votre code.

Créez un nouveau fichier appelé raise_for_status_example.py dans votre répertoire 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}")

Exécutez le script pour voir comment raise_for_status() gère les erreurs :

python raise_for_status_example.py

La sortie attendue montrera que la bibliothèque lève automatiquement des exceptions pour les codes d'état autres que 2xx, que votre code peut ensuite intercepter et gérer de manière appropriée.

Création de gestionnaires d'exceptions personnalisés

Pour les applications plus complexes, vous souhaiterez peut-être créer des gestionnaires d'exceptions personnalisés pour fournir une gestion des erreurs plus spécifique. Créons un exemple plus avancé :

Créez un nouveau fichier appelé custom_exception_handler.py dans votre répertoire 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")

Exécutez le script pour voir comment la gestion des exceptions personnalisées fonctionne :

python custom_exception_handler.py

Cet exemple avancé démontre plusieurs concepts importants :

  1. Classes d'exceptions personnalisées qui étendent la classe de base Exception
  2. Exceptions hiérarchiques pour différents types d'erreurs
  3. Journalisation détaillée pour le débogage et la surveillance
  4. Gestion spécifique pour différents codes d'état

Cette approche vous permet de gérer différents types d'erreurs de manière plus structurée et maintenable, ce qui est essentiel pour les applications plus volumineuses.

Construction d'un client d'API Web complet

Dans cette dernière étape, nous allons rassembler tout ce que vous avez appris pour construire un client d'API web complet avec une gestion des erreurs robuste. Nous allons créer un client pour l'API JSONPlaceholder, qui est une API REST en ligne gratuite que vous pouvez utiliser pour les tests.

Création d'un client API simple

Créons un fichier appelé api_client.py dans votre répertoire 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()

Exécutez le script pour voir comment notre client API complet gère différents scénarios :

python api_client.py

Test de la gestion des erreurs

Créons un fichier séparé pour tester les capacités de gestion des erreurs de notre client API. Créez un fichier appelé test_error_handling.py dans votre répertoire 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()

Le script ci-dessus peut produire des erreurs en raison de la gestion du délai d'attente, car il tente de modifier une méthode au moment de l'exécution. Simplifions-le pour éviter ces problèmes :

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()

Exécutez le script de test des erreurs :

python test_error_handling.py

Ce script démontre comment notre client API gère différents scénarios d'erreur, fournissant une base solide pour les applications réelles.

Points clés à retenir

En construisant ce client API complet, vous avez appris plusieurs techniques importantes :

  1. Création d'une classe de client API réutilisable
  2. Implémentation d'une gestion des erreurs complète
  3. Journalisation des requêtes et des réponses pour le débogage
  4. Analyse de différents formats de réponse
  5. Gestion de différentes méthodes HTTP (GET, POST, PUT, DELETE)

Ces compétences sont essentielles pour la création d'applications Python robustes qui interagissent avec les API web, garantissant que votre code peut gérer avec élégance divers scénarios d'erreur et fournir des messages d'erreur significatifs aux utilisateurs.

Résumé

Dans ce lab, vous avez appris à gérer les codes d'état HTTP dans les requêtes Python, en progressant des techniques de base aux techniques avancées. Vous avez maintenant les compétences pour :

  • Comprendre les différentes catégories de codes d'état HTTP et leurs significations
  • Mettre en œuvre une gestion des erreurs de base avec la vérification des codes d'état
  • Utiliser raise_for_status() pour une gestion des erreurs simple
  • Créer des classes d'exceptions personnalisées pour une gestion des erreurs plus spécifique
  • Construire un client API complet avec une gestion des erreurs complète

Ces compétences sont essentielles pour développer des applications Python robustes qui interagissent avec les services web. En gérant correctement les codes d'état HTTP et les erreurs, vous pouvez créer des applications plus fiables qui offrent de meilleures expériences utilisateur et sont plus faciles à maintenir.

N'oubliez pas de toujours tenir compte de la gestion des erreurs lorsque vous travaillez avec des requêtes HTTP dans vos applications Python. C'est un aspect crucial du développement web qui distingue le code professionnel des implémentations amateurs.