11using System . Collections . Frozen ;
22using System . IO . Compression ;
3+ using System . Threading . RateLimiting ;
34using Microsoft . AspNetCore . Authentication . BearerToken ;
45using Microsoft . AspNetCore . Authentication . Cookies ;
56using Microsoft . AspNetCore . DataProtection ;
67using Microsoft . AspNetCore . DataProtection . AuthenticatedEncryption ;
78using Microsoft . AspNetCore . DataProtection . AuthenticatedEncryption . ConfigurationModel ;
89using Microsoft . AspNetCore . DataProtection . KeyManagement ;
10+ using Microsoft . AspNetCore . RateLimiting ;
911using Microsoft . AspNetCore . ResponseCompression ;
1012using Microsoft . Extensions . Primitives ;
1113using Microsoft . Net . Http . Headers ;
1416using Serilog ;
1517using Serilog . Extensions . Logging ;
1618using Serilog . Sinks . OpenTelemetry ;
19+ using RateLimiterOptions = NpgsqlRest . RateLimiterOptions ;
1720
1821namespace NpgsqlRestClient ;
1922
@@ -927,4 +930,114 @@ public CacheOptions BuildCacheOptions(WebApplication app)
927930
928931 return options ;
929932 }
933+
934+ public enum RateLimiterType { FixedWindow , SlidingWindow , TokenBucket , Concurrency }
935+
936+ public RateLimiterOptions ? BuildRateLimiter ( )
937+ {
938+ var rateLimiterCfg = _config . Cfg . GetSection ( "RateLimiterOptions" ) ;
939+ if ( _config . Exists ( rateLimiterCfg ) is false || _config . GetConfigBool ( "Enabled" , rateLimiterCfg ) is false )
940+ {
941+ return null ;
942+ }
943+ var policiesCfg = rateLimiterCfg . GetSection ( "Policies" ) ;
944+ if ( _config . Exists ( policiesCfg ) is false )
945+ {
946+ return null ;
947+ }
948+
949+ var result = new RateLimiterOptions
950+ {
951+ Enabled = true ,
952+ DefaultPolicy = _config . GetConfigStr ( "DefaultPolicy" , rateLimiterCfg ) ,
953+ StatusCode = _config . GetConfigInt ( "StatusCode" , rateLimiterCfg ) ?? 429 ,
954+ Message = _config . GetConfigStr ( "Message" , rateLimiterCfg ) ?? "Too many requests. Please try again later."
955+ } ;
956+
957+ Instance . Services . AddRateLimiter ( options =>
958+ {
959+ foreach ( var sectionCfg in policiesCfg . GetChildren ( ) )
960+ {
961+ var type = _config . GetConfigEnum < RateLimiterType ? > ( "Type" , sectionCfg ) ;
962+ if ( type is null )
963+ {
964+ continue ;
965+ }
966+ if ( _config . GetConfigBool ( "Enabled" , sectionCfg ) is false )
967+ {
968+ continue ;
969+ }
970+ var name = _config . GetConfigStr ( "Name" , sectionCfg ) ?? type . ToString ( ) ! ;
971+
972+ if ( type == RateLimiterType . FixedWindow )
973+ {
974+ options . AddFixedWindowLimiter ( name , config =>
975+ {
976+ config . PermitLimit = _config . GetConfigInt ( "PermitLimit" , sectionCfg ) ?? 100 ;
977+ config . Window = TimeSpan . FromSeconds ( _config . GetConfigInt ( "WindowSeconds" , sectionCfg ) ?? 60 ) ;
978+ config . QueueLimit = _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ;
979+ config . AutoReplenishment = _config . GetConfigBool ( "AutoReplenishment" , sectionCfg , true ) ;
980+ } ) ;
981+ Logger ? . LogDebug ( "Using Fixed Window rate limiter with name {Name}: PermitLimit={PermitLimit}, WindowSeconds={WindowSeconds}, QueueLimit={QueueLimit}, AutoReplenishment={AutoReplenishment}" ,
982+ name ,
983+ _config . GetConfigInt ( "PermitLimit" , sectionCfg ) ?? 100 ,
984+ _config . GetConfigInt ( "WindowSeconds" , sectionCfg ) ?? 60 ,
985+ _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ,
986+ _config . GetConfigBool ( "AutoReplenishment" , sectionCfg , true ) ) ;
987+ }
988+ else if ( type == RateLimiterType . SlidingWindow )
989+ {
990+ options . AddSlidingWindowLimiter ( name , config =>
991+ {
992+ config . PermitLimit = _config . GetConfigInt ( "PermitLimit" , sectionCfg ) ?? 100 ;
993+ config . Window = TimeSpan . FromSeconds ( _config . GetConfigInt ( "WindowSeconds" , sectionCfg ) ?? 60 ) ;
994+ config . SegmentsPerWindow = _config . GetConfigInt ( "SegmentsPerWindow" , sectionCfg ) ?? 6 ;
995+ config . QueueLimit = _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ;
996+ config . AutoReplenishment = _config . GetConfigBool ( "AutoReplenishment" , sectionCfg , true ) ;
997+ } ) ;
998+ Logger ? . LogDebug ( "Using Sliding Window rate limiter with name {Name}: PermitLimit={PermitLimit}, WindowSeconds={WindowSeconds}, SegmentsPerWindow={SegmentsPerWindow}, QueueLimit={QueueLimit}, AutoReplenishment={AutoReplenishment}" ,
999+ name ,
1000+ _config . GetConfigInt ( "PermitLimit" , sectionCfg ) ?? 100 ,
1001+ _config . GetConfigInt ( "WindowSeconds" , sectionCfg ) ?? 60 ,
1002+ _config . GetConfigInt ( "SegmentsPerWindow" , sectionCfg ) ?? 6 ,
1003+ _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ,
1004+ _config . GetConfigBool ( "AutoReplenishment" , sectionCfg , true ) ) ;
1005+ }
1006+ else if ( type == RateLimiterType . TokenBucket )
1007+ {
1008+ options . AddTokenBucketLimiter ( name , config =>
1009+ {
1010+ config . TokenLimit = _config . GetConfigInt ( "TokenLimit" , sectionCfg ) ?? 100 ;
1011+ config . TokensPerPeriod = _config . GetConfigInt ( "TokensPerPeriod" , sectionCfg ) ?? 10 ;
1012+ config . ReplenishmentPeriod = TimeSpan . FromSeconds ( _config . GetConfigInt ( "ReplenishmentPeriodSeconds" , sectionCfg ) ?? 10 ) ;
1013+ config . QueueLimit = _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ;
1014+ config . AutoReplenishment = _config . GetConfigBool ( "AutoReplenishment" , sectionCfg , true ) ;
1015+ } ) ;
1016+ Logger ? . LogDebug ( "Using Token Bucket rate limiter with name {Name}: TokenLimit={TokenLimit}, TokensPerPeriod={TokensPerPeriod}, ReplenishmentPeriodSeconds={ReplenishmentPeriodSeconds}, QueueLimit={QueueLimit}, AutoReplenishment={AutoReplenishment}" ,
1017+ name ,
1018+ _config . GetConfigInt ( "TokenLimit" , sectionCfg ) ?? 100 ,
1019+ _config . GetConfigInt ( "TokensPerPeriod" , sectionCfg ) ?? 10 ,
1020+ _config . GetConfigInt ( "ReplenishmentPeriodSeconds" , sectionCfg ) ?? 1 ,
1021+ _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ,
1022+ _config . GetConfigBool ( "AutoReplenishment" , sectionCfg , true ) ) ;
1023+ }
1024+ else if ( type == RateLimiterType . Concurrency )
1025+ {
1026+ options . AddConcurrencyLimiter ( name , config =>
1027+ {
1028+ config . PermitLimit = _config . GetConfigInt ( "PermitLimit" , sectionCfg ) ?? 100 ;
1029+ config . QueueLimit = _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ;
1030+ config . QueueProcessingOrder = _config . GetConfigBool ( "OldestFirst" , sectionCfg , true ) is true ? QueueProcessingOrder . OldestFirst : QueueProcessingOrder . NewestFirst ;
1031+ } ) ;
1032+ Logger ? . LogDebug ( "Using Concurrency rate limiter with name {Name}: PermitLimit={PermitLimit}, QueueLimit={QueueLimit}, OldestFirst={OldestFirst}" ,
1033+ name ,
1034+ _config . GetConfigInt ( "PermitLimit" , sectionCfg ) ?? 100 ,
1035+ _config . GetConfigInt ( "QueueLimit" , sectionCfg ) ?? 10 ,
1036+ _config . GetConfigBool ( "OldestFirst" , sectionCfg , true ) ) ;
1037+ }
1038+ }
1039+ } ) ;
1040+
1041+ return result ;
1042+ }
9301043}
0 commit comments