Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V tomto článku se dozvíte, jak vytvořit obslužnou rutinu HTTP na straně klienta, která omezuje počet požadavků, které odesílá. Zobrazí se vám HttpClient, který má přístup k "www.example.com" prostředku. Prostředky využívají aplikace, které na ně spoléhají, a když aplikace pro jeden prostředek vytváří příliš mnoho požadavků, může vést k kolizí prostředků. Kolize prostředků nastane, když prostředek spotřebuje příliš mnoho aplikací a prostředek nemůže obsluhovat všechny aplikace, které ho požadují. To může vést k špatnému uživatelskému prostředí a v některých případech může dokonce vést k útoku do odepření služby (DoS). Další informace o službě DoS najdete v tématu OWASP: Odepření služby.
Co je omezování rychlosti?
Omezování rychlosti je koncept omezení počtu prostředků, ke které je možné přistupovat. Můžete například vědět, že databáze, ke které vaše aplikace přistupuje, může bezpečně zpracovávat 1 000 požadavků za minutu, ale nemusí zpracovat mnohem více než to. Do aplikace můžete umístit omezovač rychlosti, který umožňuje 1 000 požadavků každou minutu a odmítne všechny další žádosti, než budou mít přístup k databázi. Proto omezte rychlost databáze a povolte aplikaci, aby zpracovávala bezpečný počet požadavků. Jedná se o běžný vzor v distribuovaných systémech, kde můžete mít spuštěných více instancí aplikace a chcete zajistit, aby se všechny nepokoušaly o přístup k databázi najednou. Existuje několik různých algoritmů omezování rychlosti pro řízení toku požadavků.
Pokud chcete použít omezení rychlosti v .NET, budete odkazovat na balíček NuGet System.Threading.RateLimiting .
Implementace podtřídy DelegatingHandler
Pokud chcete řídit tok požadavků, implementujete vlastní DelegatingHandler podtřídu. Jedná se o typ HttpMessageHandler , který umožňuje zachytávat a zpracovávat požadavky před jejich odesláním na server. Odpovědi můžete také zachytit a zpracovat předtím, než se vrátí volajícímu. V tomto příkladu implementujete vlastní DelegatingHandler podtřídu, která omezuje počet požadavků, které se dají odeslat na jeden prostředek. Vezměte v úvahu následující vlastní ClientSideRateLimitedHandler třídu:
internal sealed class ClientSideRateLimitedHandler(
RateLimiter limiter)
: DelegatingHandler(new HttpClientHandler()), IAsyncDisposable
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
using RateLimitLease lease = await limiter.AcquireAsync(
permitCount: 1, cancellationToken);
if (lease.IsAcquired)
{
return await base.SendAsync(request, cancellationToken);
}
var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests);
if (lease.TryGetMetadata(
MetadataName.RetryAfter, out TimeSpan retryAfter))
{
response.Headers.Add(
"Retry-After",
((int)retryAfter.TotalSeconds).ToString(
NumberFormatInfo.InvariantInfo));
}
return response;
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
await limiter.DisposeAsync().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
limiter.Dispose();
}
}
}
Předchozí kód jazyka C#:
- Dědí
DelegatingHandlertyp. - Implementuje rozhraní IAsyncDisposable.
-
RateLimiterDefinuje pole, které je přiřazeno z konstruktoru. - Přepíše metodu
SendAsyncpro zachycení a zpracování požadavků před jejich odesláním na server. - Přepíše metodu DisposeAsync() pro odstranění
RateLimiterinstance.
Při bližším pohledu na metodu SendAsync uvidíte, že:
- Spoléhá na
RateLimiterinstanci k získáníRateLimitLeasez objektuAcquireAsync. - Pokud je vlastnost
lease.IsAcquired, požadavek se odešle na server. - V opačném případě je vrácena HttpResponseMessage se stavovým kódem
429a pokudleaseobsahujeRetryAfterhodnotu, hlavičkaRetry-Afterje nastavena na tuto hodnotu.
Emulace mnoha souběžných požadavků
Pokud chcete tuto vlastní DelegatingHandler podtřídu vložit do testu, vytvoříte konzolovou aplikaci, která emuluje mnoho souběžných požadavků. Tato Program třída vytvoří HttpClient s vlastním ClientSideRateLimitedHandler.
var options = new TokenBucketRateLimiterOptions
{
TokenLimit = 8,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = true
};
// Create an HTTP client with the client-side rate limited handler.
using HttpClient client = new(
handler: new ClientSideRateLimitedHandler(
limiter: new TokenBucketRateLimiter(options)));
// Create 100 urls with a unique query string.
var oneHundredUrls = Enumerable.Range(0, 100).Select(
i => $"https://example.com?iteration={i:0#}");
// Flood the HTTP client with requests.
var floodOneThroughFortyNineTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(0..49),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(^50..),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
await Task.WhenAll(
floodOneThroughFortyNineTask,
floodFiftyThroughOneHundredTask);
static async ValueTask GetAsync(
HttpClient client, string url, CancellationToken cancellationToken)
{
using var response =
await client.GetAsync(url, cancellationToken);
Console.WriteLine(
$"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})");
}
V předchozí aplikaci konzoly:
- Jsou
TokenBucketRateLimiterOptionskonfigurovány s limitem tokenů8, a pořadím zpracování frontyOldestFirst, limitem fronty3, opakováním každé1milisekundy, počtem tokenů na období2, a automatické doplňování hodnotoutrue. -
HttpClientje vytvořen sClientSideRateLimitedHandler, který je nakonfigurován sTokenBucketRateLimiter. - Pro emulaci 100 požadavků Enumerable.Range vytvoří 100 adres URL, z nichž každý má jedinečný parametr řetězce dotazu.
- Dva Task objekty jsou přiřazeny ze Parallel.ForEachAsync metody, které rozdělují adresy URL do dvou skupin.
- Slouží
HttpClientk odesláníGETpožadavku na každou adresu URL a odpověď se zapíše do konzoly. - Task.WhenAll čeká na dokončení obou úloh.
Jelikož je HttpClient nakonfigurován s ClientSideRateLimitedHandler, ne všechny požadavky se dostanou k prostředkům serveru. Toto tvrzení můžete otestovat spuštěním konzolové aplikace. Uvidíte, že na server se odešle jenom zlomek celkového počtu požadavků a zbytek se odmítne se stavovým kódem 429HTTP . Zkuste změnit options objekt použitý k vytvoření TokenBucketRateLimiter , abyste viděli, jak se počet požadavků odesílaných na server mění.
Představte si následující příklad výstupu:
URL: https://example.com?iteration=06, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=60, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=55, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=59, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=57, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=11, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=63, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=13, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=62, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=65, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=64, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=67, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=14, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=68, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=16, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=69, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=70, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=71, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=17, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=18, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=72, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=73, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=74, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=19, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=75, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=76, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=79, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=77, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=21, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=78, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=81, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=22, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=80, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=20, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=82, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=83, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=23, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=84, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=24, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=85, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=86, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=25, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=87, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=26, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=88, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=89, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=27, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=90, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=28, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=91, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=94, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=29, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=93, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=96, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=92, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=95, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=31, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=30, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=97, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=98, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=99, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=32, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=33, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=34, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=35, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=36, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=37, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=38, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=39, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=40, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=41, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=42, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=43, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=44, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=45, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=46, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=47, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=48, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=15, HTTP status code: OK (200)
URL: https://example.com?iteration=04, HTTP status code: OK (200)
URL: https://example.com?iteration=54, HTTP status code: OK (200)
URL: https://example.com?iteration=08, HTTP status code: OK (200)
URL: https://example.com?iteration=00, HTTP status code: OK (200)
URL: https://example.com?iteration=51, HTTP status code: OK (200)
URL: https://example.com?iteration=10, HTTP status code: OK (200)
URL: https://example.com?iteration=66, HTTP status code: OK (200)
URL: https://example.com?iteration=56, HTTP status code: OK (200)
URL: https://example.com?iteration=52, HTTP status code: OK (200)
URL: https://example.com?iteration=12, HTTP status code: OK (200)
URL: https://example.com?iteration=53, HTTP status code: OK (200)
URL: https://example.com?iteration=07, HTTP status code: OK (200)
URL: https://example.com?iteration=02, HTTP status code: OK (200)
URL: https://example.com?iteration=01, HTTP status code: OK (200)
URL: https://example.com?iteration=61, HTTP status code: OK (200)
URL: https://example.com?iteration=05, HTTP status code: OK (200)
URL: https://example.com?iteration=09, HTTP status code: OK (200)
URL: https://example.com?iteration=03, HTTP status code: OK (200)
URL: https://example.com?iteration=58, HTTP status code: OK (200)
URL: https://example.com?iteration=50, HTTP status code: OK (200)
Všimněte si, že první protokolované položky jsou vždy odpovědi s kódem 429, které jsou okamžitě vráceny, a poslední položky jsou vždy odpovědi s kódem 200. Důvodem je to, že je na straně klienta zjištěn limit rychlosti a zabraňuje volání PROTOKOLU HTTP na server. To je dobré, protože to znamená, že server není přetížený žádostmi. Také to znamená, že se limit rychlosti vynucuje konzistentně napříč všemi klienty.
Všimněte si také, že řetězec dotazu každé adresy URL je jedinečný: prozkoumejte iteration parametr a zjistěte, že se pro každý požadavek zvýší o jeden. Tento parametr pomáhá ilustrovat, že odpovědi 429 nejsou z prvních požadavků, ale z požadavků provedených po dosažení limitu rychlosti. 200 odpovědí dorazí později, ale tyto žádosti byly provedeny dříve – před dosažením limitu.
Pokud chcete lépe porozumět různým algoritmům omezování rychlosti, zkuste tento kód přepsat a přijmout jinou RateLimiter implementaci. Kromě TokenBucketRateLimiter můžete také vyzkoušet:
Shrnutí
V tomto článku jste se dozvěděli, jak implementovat vlastní ClientSideRateLimitedHandler. Tento vzor lze použít k implementaci HTTP klienta s omezením přenosové rychlosti pro prostředky, o nichž víte, že mají omezení rozhraní API. Tímto způsobem bráníte klientské aplikaci v provádění nepotřebných požadavků na server a zároveň bráníte zablokování aplikace serverem. Kromě toho s použitím metadat k ukládání hodnot časování opakování můžete také implementovat logiku automatického opakování.