using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace AsbCloudWebApi.Middlewares { /// <summary> /// This is not real middleware it`s part of PermissionsMiddlware. /// DO NOT register it in setup.cs as middleware. /// </summary> class UserConnectionsLimitMiddlware { private readonly int parallelRequestsToController; private readonly RequestDelegate next; private readonly byte[] responseBody; private readonly ConcurrentDictionary<int, ConcurrentDictionary<string, int>> stat = new (); private readonly IEnumerable<string>? controllerNames; public UserConnectionsLimitMiddlware(RequestDelegate next, IConfiguration configuration) { const int parallelRequestsToControllerDefault = 8; this.next = next; var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue<int>("parallelRequestsToController") ?? parallelRequestsToControllerDefault; this.parallelRequestsToController = parallelRequestsToController > 0 ? parallelRequestsToController : parallelRequestsToControllerDefault; controllerNames = configuration.GetSection("userLimits")?.GetValue<IEnumerable<string>>("controllerNames"); var bodyText = $"<html><head><title>Too Many Requests</title></head><body><h1>Too Many Requests</h1><p>I only allow {this.parallelRequestsToController} parallel requests per user. Try again soon.</p></body></html>"; responseBody = System.Text.Encoding.UTF8.GetBytes(bodyText); } public async Task InvokeAsync(HttpContext context, int idUser, string controllerName) { if(controllerNames?.Any(n => controllerName.StartsWith(n)) == false) { await next(context); return; } var userStat = stat.GetOrAdd(idUser, idUser => new()); var count = userStat.AddOrUpdate(controllerName, 0, (k, v) => v); if(count + 1 < parallelRequestsToController) { try { userStat[controllerName]++; await next(context); } finally { userStat[controllerName]--; } } else { context.Response.Clear(); context.Response.StatusCode = (int)System.Net.HttpStatusCode.TooManyRequests; context.Response.Headers.RetryAfter = "1000"; context.Response.Headers.ContentType = "text/html"; await context.Response.BodyWriter.WriteAsync(responseBody); } } } }