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; /// /// This is not real middleware it`s part of PermissionsMiddlware. /// DO NOT register it in setup.cs as middleware. /// class UserConnectionsLimitMiddlware { private readonly int parallelRequestsToController; private readonly RequestDelegate next; private readonly byte[] responseBody; private readonly ConcurrentDictionary> stat = new (); private readonly IEnumerable? controllerNames; public UserConnectionsLimitMiddlware(RequestDelegate next, IConfiguration configuration) { const int parallelRequestsToControllerDefault = 8; this.next = next; var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue("parallelRequestsToController") ?? parallelRequestsToControllerDefault; this.parallelRequestsToController = parallelRequestsToController > 0 ? parallelRequestsToController : parallelRequestsToControllerDefault; controllerNames = configuration.GetSection("userLimits")?.GetValue>("controllerNames"); var bodyText = $"Too Many Requests

Too Many Requests

I only allow {this.parallelRequestsToController} parallel requests per user. Try again soon.

"; 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); } } }