Définir le nombre maximum de requêtes faite à votre serveur par plage de temps UPDATED

La consommation de services web est quelque chose de très aisé dans une application Windows (#UWP ou inférieur). Il arrive que certains services grand public limitent le nombre de requêtes possibles par IP sur une tranche de temps donnée ( 30 requêtes maximum sur 10 secondes par exemple).

Il est alors possible de mettre en place la logique soi-même à la main mais aujourd’hui j’aimerais vous présenter une solution que je trouve assez élégante et réutilisable dans tous vos projets : implémenter un IHttpFilter personnalisé !

 

Mise en place de l’IHttpFilter

Pour rappel, un IHttpFilter est une boîte noire qui prend en entrée une requête HTTP (HttpRequestMessage) et retourne une réponse HTTP (HttpResponseMessage). Il existe un filtre par défaut HttpBaseProtocolFilter qui fait réellement la logique d’envoi au serveur et que vous pouvez notamment utiliser pour activer la compression gzip des réponses retournées par votre serveur.

Pour mettre en place notre HttpFilter, il va falloir implémenter l’interface IHttpFilter qui définit une méthode de traitement “SendRequestAsync” et une méthode de nettoyage “Dispose”.

La bonne pratique consiste à passer en paramètre du constructeur de notre filtre un autre filter à appeler dans notre implémentation de SendRequestAsync. Dans notre cas de figure on passera ainsi une instance d’HttpBaseProtocolFilter. On passera aussi en paramètre notre configuration : le nombre maximum de requête et la durée en seconde de la plage de temps.

 

Le code de base de notre filtre va donc être :

internal class ThrottleRequestFilter
    : Windows.Web.Http.Filters.IHttpFilter
{
    private IHttpFilter _innerFilter;
    private int _throttleTime;

    public ThrottleRequestFilter(IHttpFilter innerFilter,
                                   int throttleTime, int howMuchAllowed)
    {
        _innerFilter = innerFilter;
        _throttleTime = throttleTime;
    }

    public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> 
                     SendRequestAsync(HttpRequestMessage request)
    {
        return _innerFilter.SendRequestAsync(request);
    }

    public void Dispose()
    {
        _innerFilter.Dispose();
        GC.SuppressFinalize(this);
    }
}

 

Dans la méthode Dispose, nous avons bien pris soin d’appeler le Dispose du filtre “interne”.

Limitation du nombre d’appels

Pour mettre en place l’algorithme, nous allons tout simplement utiliser une SemaphoreSlim et Task.Delay !

 

La logique sera alors la suivante :

  1. Initialisation du sémaphore avec le nombre de requêtes maximum.
  2. Attente d’un jeton sur le sémaphore,
  3. Déclenchement de la requête sans attendre son résultat en utilisant le filtre interne.
  4. Attente utiliser un Task.Delay du nombre de secondes correspondant à la plage de temps et libérer le jeton du sémaphore à sa fin.

 

EDIT : après une discussion avec Gaëtan qui a le même problème sur des appels à des APIs Sharepoint, il m’a fait remarquer que je ne retournais la réponse qu’après l’attente ce qui n’est pas terrible. Le code mis à jour est ci-dessous.

 

return AsyncInfo.Run(
    async (cancellationToken, progress) =>
    {
        IAsyncOperationWithProgress innerRequest = null;

        try
        {
            // wait for a token from the semaphore
            await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

            //Launch the request first but do not await it
            innerRequest = _innerFilter.SendRequestAsync(request);
        }
        catch (Exception)
        {
            // release the semaphore in case of exception
            _semaphore.Release();

            throw;
        }

        // After the throttle time : release the _semaphore
        // do not wait to give back the http answe first
        Task.Delay(1000 * _throttleTime).ContinueWith((_) => _semaphore.Release());

        //attente et retour de la requête
        return await innerRequest;
    });

 

Vous noterez l’utilisation de l’utilitaire AsyncInfo.Run qui permet de retourner le bon type de retour.

 

Utilisation du filtre

L’utilisation de ce filtre est on ne peut plus aisée : il suffit de créer un HttpClient en lui donnant une instance du filtre. Il sera important de partager le filtre ou l’HttpClient entre tous vos appels aux serveurs.

//création d'un filtre de base
var baseFilter = new HttpBaseProtocolFilter { AutomaticDecompression = true };

// création et configuration de notre filtre
var ourFilter = new ThrottleRequestFilter(baseFilter, 10, 30);
var httpClient = new HttpClient(ourFilter);

 

Bon code !

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus