Merge pull request #537 from iPromKnight/feature/rd_rate_limit
Rate limit RD requests through the http client
This commit is contained in:
commit
617ae547cf
4 changed files with 72 additions and 4 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using RdtClient.Service.BackgroundServices;
|
using RdtClient.Service.BackgroundServices;
|
||||||
|
using RdtClient.Service.Handlers;
|
||||||
using RdtClient.Service.Middleware;
|
using RdtClient.Service.Middleware;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using RdtClient.Service.Services.TorrentClients;
|
using RdtClient.Service.Services.TorrentClients;
|
||||||
|
|
@ -9,7 +11,9 @@ namespace RdtClient.Service;
|
||||||
|
|
||||||
public static class DiConfig
|
public static class DiConfig
|
||||||
{
|
{
|
||||||
public static void Config(IServiceCollection services)
|
public const String RD_CLIENT = "RdClient";
|
||||||
|
|
||||||
|
public static void RegisterRdtServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<AllDebridTorrentClient>();
|
services.AddScoped<AllDebridTorrentClient>();
|
||||||
services.AddScoped<Authentication>();
|
services.AddScoped<Authentication>();
|
||||||
|
|
@ -30,4 +34,13 @@ public static class DiConfig
|
||||||
services.AddHostedService<UpdateChecker>();
|
services.AddHostedService<UpdateChecker>();
|
||||||
services.AddHostedService<WatchFolderChecker>();
|
services.AddHostedService<WatchFolderChecker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void RegisterHttpClients(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHttpClient();
|
||||||
|
|
||||||
|
services.AddHttpClient(RD_CLIENT)
|
||||||
|
.AddHttpMessageHandler(sp => new RateLimitingHandler(sp.GetRequiredService<ILogger<RateLimitingHandler>>(), 60, TimeSpan.FromSeconds(60)))
|
||||||
|
.SetHandlerLifetime(Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
54
server/RdtClient.Service/Handlers/RateLimitingHandler.cs
Normal file
54
server/RdtClient.Service/Handlers/RateLimitingHandler.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Handlers;
|
||||||
|
|
||||||
|
public class RateLimitingHandler : DelegatingHandler
|
||||||
|
{
|
||||||
|
private readonly ConcurrentQueue<DateTimeOffset> _callQueue = new();
|
||||||
|
private readonly TimeSpan _requestWindow;
|
||||||
|
private readonly ILogger<RateLimitingHandler> _logger;
|
||||||
|
private readonly Int32 _requestsAllowedPerWindow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RateLimitingHandler"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="requestsAllowedPerWindow">The number of requests allowed per window.</param>
|
||||||
|
/// <param name="requestWindow">The time to delay when the maximum number of requests has been reached (The window).</param>
|
||||||
|
public RateLimitingHandler(ILogger<RateLimitingHandler> logger, Int32 requestsAllowedPerWindow, TimeSpan requestWindow)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(requestsAllowedPerWindow);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(requestWindow, TimeSpan.Zero);
|
||||||
|
_logger = logger;
|
||||||
|
_requestsAllowedPerWindow = requestsAllowedPerWindow;
|
||||||
|
_requestWindow = requestWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
_callQueue.Enqueue(now);
|
||||||
|
while (_callQueue.TryPeek(out var oldCall) && now - oldCall > _requestWindow)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Removing oldest request from queue");
|
||||||
|
_callQueue.TryDequeue(out _);
|
||||||
|
}
|
||||||
|
if (_callQueue.Count > _requestsAllowedPerWindow)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Delaying request");
|
||||||
|
var earliestAllowedRequestTime = now.Add(-_requestWindow);
|
||||||
|
var delayDuration = _callQueue.ElementAtOrDefault(_requestsAllowedPerWindow - 1) -
|
||||||
|
earliestAllowedRequestTime;
|
||||||
|
if (delayDuration > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Delaying request for {DelayDuration}", delayDuration);
|
||||||
|
await Task.Delay(delayDuration, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.LogDebug("Sending request");
|
||||||
|
return await base.SendAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
throw new("Real-Debrid API Key not set in the settings");
|
throw new("Real-Debrid API Key not set in the settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
||||||
|
|
||||||
var rdtNetClient = new RdNetClient(null, httpClient, 5);
|
var rdtNetClient = new RdNetClient(null, httpClient, 5);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||||
using RdtClient.Data.Data;
|
using RdtClient.Data.Data;
|
||||||
using RdtClient.Data.Models.Internal;
|
using RdtClient.Data.Models.Internal;
|
||||||
|
using RdtClient.Service;
|
||||||
using RdtClient.Service.Middleware;
|
using RdtClient.Service.Middleware;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -116,7 +117,7 @@ builder.Services.AddCors(options =>
|
||||||
builder.Services.AddResponseCaching();
|
builder.Services.AddResponseCaching();
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
builder.Services.AddDistributedMemoryCache();
|
builder.Services.AddDistributedMemoryCache();
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.RegisterHttpClients();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddSession();
|
builder.Services.AddSession();
|
||||||
|
|
||||||
|
|
@ -133,7 +134,7 @@ builder.Services.AddSignalR(hubOptions =>
|
||||||
builder.Host.UseWindowsService();
|
builder.Host.UseWindowsService();
|
||||||
|
|
||||||
RdtClient.Data.DiConfig.Config(builder.Services, appSettings);
|
RdtClient.Data.DiConfig.Config(builder.Services, appSettings);
|
||||||
RdtClient.Service.DiConfig.Config(builder.Services);
|
builder.Services.RegisterRdtServices();
|
||||||
|
|
||||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue