"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > La limitation expliquée : un guide pour gérer les limites de requêtes d'API

La limitation expliquée : un guide pour gérer les limites de requêtes d'API

Publié le 2024-12-22
Parcourir:334

Quand devriez-vous implémenter la limitation dans votre code ?

Pour les grands projets, il est généralement préférable d'utiliser des outils tels que Cloudflare Rate Limiting ou HAProxy. Ils sont puissants, fiables et s'occupent du gros du travail à votre place.

Mais pour les petits projets (ou si vous souhaitez apprendre comment les choses fonctionnent), vous pouvez créer votre propre limiteur de débit directement dans votre code. Pourquoi?

  • C'est simple : vous construirez quelque chose de simple et facile à comprendre.
  • C'est économique : aucun coût supplémentaire au-delà de l'hébergement de votre serveur.
  • Cela fonctionne pour les petits projets : tant que le trafic est faible, cela permet de garder les choses rapides et efficaces.
  • Il est réutilisable : vous pouvez le copier dans d'autres projets sans configurer de nouveaux outils ou services.

Ce que vous apprendrez

À la fin de ce guide, vous saurez comment créer un régulateur de base dans TypeScript pour protéger vos API contre la surcharge. Voici ce que nous allons aborder :

  • Limites de temps configurables : chaque tentative bloquée augmente la durée du verrouillage pour éviter les abus.
  • Request Caps : définissez un nombre maximum de requêtes autorisées. Ceci est particulièrement utile pour les API qui impliquent des services payants, comme OpenAI.
  • Stockage en mémoire : une solution simple qui fonctionne sans outils externes comme Redis, idéale pour les petits projets ou prototypes.
  • Limites par utilisateur : suivez les demandes par utilisateur à l'aide de leur adresse IPv4. Nous utiliserons SvelteKit pour récupérer facilement l'adresse IP du client grâce à sa méthode intégrée.

Ce guide est conçu pour être un point de départ pratique, parfait pour les développeurs qui souhaitent apprendre les bases sans complexité inutile. Mais il n'est pas prêt pour la production.

Avant de commencer, je souhaite attribuer les bons crédits à la section Rate Limiting de Lucia.


Implémentation du régulateur

Définissons la classe Throttler :

export class Throttler {
    private storage = new Map();

    constructor(private timeoutSeconds: number[]) {}
}

Le constructeur Throttler accepte une liste de durées d'expiration (timeoutSeconds). Chaque fois qu'un utilisateur est bloqué, la durée augmente progressivement en fonction de cette liste. Finalement, lorsque le délai d'attente final est atteint, vous pouvez même déclencher un rappel pour interdire définitivement l'adresse IP de l'utilisateur, bien que cela dépasse le cadre de ce guide.

Voici un exemple de création d'une instance de régulateur qui bloque les utilisateurs à intervalles croissants :

const throttler = new Throttler([1, 2, 4, 8, 16]);

Cette instance bloquera les utilisateurs la première fois pendant une seconde. La deuxième fois pour deux, et ainsi de suite.

Nous utilisons une carte pour stocker les adresses IP et leurs données correspondantes. Une carte est idéale car elle gère efficacement les ajouts et suppressions fréquents.

Conseil de pro : utilisez une carte pour les données dynamiques qui changent fréquemment. Pour les données statiques et immuables, un objet est préférable. (Trou de lapin 1)


Lorsque votre point de terminaison reçoit une demande, il extrait l'adresse IP de l'utilisateur et consulte le Throttler pour déterminer si la demande doit être autorisée.

Comment ça marche

  • Cas A : utilisateur nouveau ou inactif

    Si l’adresse IP n’est pas trouvée dans le Throttler, c’est soit la première demande de l’utilisateur, soit il est inactif depuis assez longtemps. Dans ce cas:

    • Autoriser l'action.
    • Suivez l'utilisateur en stockant son adresse IP avec un délai d'attente initial.
  • Cas B : Utilisateur actif

    Si l'adresse IP est trouvée, cela signifie que l'utilisateur a effectué des demandes précédentes. Ici:

    • Vérifiez si le temps d'attente requis (basé sur le tableau timeoutSeconds) s'est écoulé depuis leur dernier bloc.
    • Si suffisamment de temps s'est écoulé :
    • Mettre à jour l'horodatage.
    • Incrémentez l'index de délai d'attente (plafonné au dernier index pour éviter tout débordement).
    • Sinon, refusez la demande.

Dans ce dernier cas, nous devons vérifier si suffisamment de temps s'est écoulé depuis le dernier bloc. Nous savons à quels timeoutSeconds nous devons faire référence grâce à un index. Sinon, rebondissez simplement. Sinon, mettez à jour l'horodatage.

export class Throttler {
    // ...

    public consume(key: string): boolean {
        const counter = this.storage.get(key) ?? null;
        const now = Date.now();

        // Case A
        if (counter === null) {
            // At next request, will be found.
            // The index 0 of [1, 2, 4, 8, 16] returns 1.
            // That's the amount of seconds it will have to wait.
            this.storage.set(key, {
                index: 0,
                updatedAt: now
            });
            return true; // allowed
        }

        // Case B
        const timeoutMs = this.timeoutSeconds[counter.index] * 1000;
        const allowed = now - counter.updatedAt >= timeoutMs;
        if (!allowed) {
            return false; // denied
        }

        // Allow the call, but increment timeout for following requests.
        counter.updatedAt = now;
        counter.index = Math.min(counter.index   1, this.timeoutSeconds.length - 1);
        this.storage.set(key, counter);

        return true; // allowed
    }
}

Lors de la mise à jour de l'index, il se limite au dernier index de timeoutSeconds. Sans cela, counter.index 1 le déborderait et le prochain accès this.timeoutSeconds[counter.index] entraînerait une erreur d'exécution.

Exemple de point de terminaison

Cet exemple montre comment utiliser le Throttler pour limiter la fréquence à laquelle un utilisateur peut appeler votre API. Si l'utilisateur fait trop de requêtes, il obtiendra une erreur au lieu d'exécuter la logique principale.

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]);

export async function GET({ getClientAddress }) {
    const IP = getClientAddress();

    if (!throttler.consume(IP)) {
        throw error(429, { message: 'Too Many Requests' });
    }

    // Read from DB, call OpenAI - do the thing.

    return new Response(null, { status: 200 });
}

Throttling Explained: A Guide to Managing API Request Limits

Remarque pour l'authentification

Lorsque vous utilisez la limitation de débit avec les systèmes de connexion, vous pouvez être confronté à ce problème :

  1. Un utilisateur se connecte, ce qui déclenche l'association par le Throttler d'un délai d'attente à son adresse IP.
  2. L'utilisateur se déconnecte ou sa session se termine (par exemple, se déconnecte immédiatement, le cookie expire avec des plantages de session et de navigateur, etc.).
  3. Lorsqu'ils tentent de se reconnecter peu de temps après, le Throttler peut toujours les bloquer, renvoyant une erreur 429 Too Many Requests.

Pour éviter cela, utilisez l'ID utilisateur unique de l'utilisateur au lieu de son adresse IP pour limiter le débit. En outre, vous devez réinitialiser l'état du régulateur après une connexion réussie pour éviter les blocages inutiles.

Ajouter une méthode de réinitialisation à la classe Throttler :

export class Throttler {
    // ...

    public reset(key: string): void {
        this.storage.delete(key);
    }
}

Et utilisez-le après une connexion réussie :

const user = db.get(email);

if (!throttler.consume(user.ID)) {
    throw error(429);
}

const validPassword = verifyPassword(user.password, providedPassword);
if (!validPassword) {
    throw error(401);
}

throttler.reset(user.id); // Clear throttling for the user

Gestion des enregistrements IP périmés avec un nettoyage périodique

Puisque votre régulateur suit les adresses IP et les limites de débit, il est important de réfléchir à comment et quand supprimer les enregistrements IP qui ne sont plus nécessaires. Sans mécanisme de nettoyage, votre régulateur continuera à stocker les enregistrements en mémoire, ce qui pourrait entraîner des problèmes de performances au fil du temps à mesure que les données augmentent.

Pour éviter cela, vous pouvez implémenter une fonction de nettoyage qui supprime périodiquement les anciens enregistrements après une certaine période d'inactivité. Voici un exemple de la façon d'ajouter une méthode de nettoyage simple pour supprimer les entrées obsolètes du régulateur.

export class Throttler {
    // ...

    public cleanup(): void {
        const now = Date.now()

        // Capture the keys first to avoid issues during iteration (we use .delete)
        const keys = Array.from(this.storage.keys())

        for (const key of keys) {
            const counter = this.storage.get(key)
            if (!counter) {
                // Skip if the counter is already deleted (handles concurrency issues)
                return
            }

            // If the IP is at the first timeout, remove it from storage
            if (counter.index == 0) {
                this.storage.delete(key)
                continue
            }

            // Otherwise, reduce the timeout index and update the timestamp
            counter.index -= 1
            counter.updatedAt = now
            this.storage.set(key, counter)
        }
    }
}

Un moyen très simple (mais probablement pas le meilleur) de planifier le nettoyage consiste à utiliser setInterval :

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300])
const oneMinute = 60_000
setInterval(() => throttler.cleanup(), oneMinute)

Ce mécanisme de nettoyage permet de garantir que votre régulateur ne conserve pas indéfiniment les anciens enregistrements, garantissant ainsi l'efficacité de votre application. Bien que cette approche soit simple et facile à mettre en œuvre, elle peut nécessiter des affinements supplémentaires pour des cas d'utilisation plus complexes (par exemple, utilisation d'une planification plus avancée ou gestion d'une simultanéité élevée).

Grâce à un nettoyage périodique, vous évitez l'engorgement de la mémoire et garantissez que les utilisateurs qui n'ont pas tenté de faire des requêtes depuis un certain temps ne sont plus suivis. Il s'agit d'une première étape pour rendre votre système de limitation de débit à la fois évolutif et économe en ressources.


  1. Si vous vous sentez aventureux, vous pourriez être intéressé de savoir comment les propriétés sont attribuées et comment elles changent. Et pourquoi pas, sur les optimisations des VM comme les caches en ligne, particulièrement favorisées par le monomorphisme. Apprécier. ↩

Déclaration de sortie Cet article est reproduit sur : https://dev.to/didof/throttling-explained-a-guide-to-managing-api-request-limits-102a?1 En cas de violation, veuillez contacter [email protected] pour le supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3