33
44using Microsoft . Extensions . Configuration ;
55using Microsoft365 . DeveloperProxy . Abstractions ;
6+ using Microsoft365 . DeveloperProxy . Plugins . MocksResponses ;
67using System . Net ;
78using System . Text . Json ;
89using System . Text . RegularExpressions ;
1112
1213namespace Microsoft365 . DeveloperProxy . Plugins . Behavior ;
1314
15+ public enum RateLimitResponseWhenLimitExceeded {
16+ Throttle ,
17+ Custom
18+ }
19+
1420public class RateLimitConfiguration {
1521 public string HeaderLimit { get ; set ; } = "RateLimit-Limit" ;
1622 public string HeaderRemaining { get ; set ; } = "RateLimit-Remaining" ;
@@ -21,6 +27,9 @@ public class RateLimitConfiguration {
2127 public int WarningThresholdPercent { get ; set ; } = 80 ;
2228 public int RateLimit { get ; set ; } = 120 ;
2329 public int RetryAfterSeconds { get ; set ; } = 5 ;
30+ public RateLimitResponseWhenLimitExceeded WhenLimitExceeded { get ; set ; } = RateLimitResponseWhenLimitExceeded . Throttle ;
31+ public string CustomResponseFile { get ; set ; } = "rate-limit-response.json" ;
32+ public MockResponse ? CustomResponse { get ; set ; }
2433}
2534
2635public class RateLimitingPlugin : BaseProxyPlugin {
@@ -30,6 +39,7 @@ public class RateLimitingPlugin : BaseProxyPlugin {
3039 // first request and can set the initial values
3140 private int _resourcesRemaining = - 1 ;
3241 private DateTime _resetTime = DateTime . MinValue ;
42+ private RateLimitingCustomResponseLoader ? _loader = null ;
3343
3444 private ThrottlingInfo ShouldThrottle ( Request request , string throttlingKey ) {
3545 var throttleKeyForRequest = BuildThrottleKey ( request ) ;
@@ -129,6 +139,10 @@ public override void Register(IPluginEvents pluginEvents,
129139 base . Register ( pluginEvents , context , urlsToWatch , configSection ) ;
130140
131141 configSection ? . Bind ( _configuration ) ;
142+ _loader = new RateLimitingCustomResponseLoader ( _logger ! , _configuration ) ;
143+ // load the responses from the configured mocks file
144+ _loader . InitResponsesWatcher ( ) ;
145+
132146 pluginEvents . BeforeRequest += OnRequest ;
133147 pluginEvents . BeforeResponse += OnResponse ;
134148 }
@@ -169,17 +183,34 @@ _urlsToWatch is null ||
169183 // subtract the cost of the request
170184 _resourcesRemaining -= _configuration . CostPerRequest ;
171185 if ( _resourcesRemaining < 0 ) {
186+ _resourcesRemaining = 0 ;
172187 var request = e . Session . HttpClient . Request ;
173188
174189 _logger ? . LogRequest ( new [ ] { $ "Exceeded resource limit when calling { request . Url } .", "Request will be throttled" } , MessageType . Failed , new LoggingContext ( e . Session ) ) ;
175- e . ThrottledRequests . Add ( new ThrottlerInfo (
176- BuildThrottleKey ( request ) ,
177- ShouldThrottle ,
178- DateTime . Now . AddSeconds ( _configuration . RetryAfterSeconds )
179- ) ) ;
180-
181- ThrottleResponse ( e ) ;
182- state . HasBeenSet = true ;
190+ if ( _configuration . WhenLimitExceeded == RateLimitResponseWhenLimitExceeded . Throttle ) {
191+ e . ThrottledRequests . Add ( new ThrottlerInfo (
192+ BuildThrottleKey ( request ) ,
193+ ShouldThrottle ,
194+ DateTime . Now . AddSeconds ( _configuration . RetryAfterSeconds )
195+ ) ) ;
196+ ThrottleResponse ( e ) ;
197+ state . HasBeenSet = true ;
198+ }
199+ else {
200+ if ( _configuration . CustomResponse is not null ) {
201+ var headers = _configuration . CustomResponse . ResponseHeaders is not null ?
202+ _configuration . CustomResponse . ResponseHeaders . Select ( h => new HttpHeader ( h . Key , h . Value ) ) :
203+ Array . Empty < HttpHeader > ( ) ;
204+ string body = _configuration . CustomResponse . ResponseBody is not null ?
205+ JsonSerializer . Serialize ( _configuration . CustomResponse . ResponseBody , new JsonSerializerOptions { WriteIndented = true } ) :
206+ "" ;
207+ e . Session . GenericResponse ( body , ( HttpStatusCode ) ( _configuration . CustomResponse . ResponseCode ?? 200 ) , headers ) ;
208+ state . HasBeenSet = true ;
209+ }
210+ else {
211+ _logger ? . LogRequest ( new [ ] { $ "Custom behavior not set { _configuration . CustomResponseFile } not found." } , MessageType . Failed , new LoggingContext ( e . Session ) ) ;
212+ }
213+ }
183214 }
184215 }
185216}
0 commit comments