@@ -38,7 +38,7 @@ public class RateLimitConfiguration
3838 public int RetryAfterSeconds { get ; set ; } = 5 ;
3939 public RateLimitResponseWhenLimitExceeded WhenLimitExceeded { get ; set ; } = RateLimitResponseWhenLimitExceeded . Throttle ;
4040 public string CustomResponseFile { get ; set ; } = "rate-limit-response.json" ;
41- public MockResponse ? CustomResponse { get ; set ; }
41+ public MockResponseResponse ? CustomResponse { get ; set ; }
4242}
4343
4444public class RateLimitingPlugin : BaseProxyPlugin
@@ -95,55 +95,10 @@ private void UpdateProxyResponse(ProxyHttpEventArgsBase e, HttpStatusCode errorS
9595 return ;
9696 }
9797
98- // add rate limiting headers if reached the threshold percentage
99- if ( _resourcesRemaining <= _configuration . RateLimit - ( _configuration . RateLimit * _configuration . WarningThresholdPercent / 100 ) )
98+ if ( e . PluginData . TryGetValue ( Name , out var pluginData ) &&
99+ pluginData is List < HttpHeader > rateLimitingHeaders )
100100 {
101- var reset = _configuration . ResetFormat == RateLimitResetFormat . SecondsLeft ?
102- ( _resetTime - DateTime . Now ) . TotalSeconds . ToString ( "N0" ) : // drop decimals
103- new DateTimeOffset ( _resetTime ) . ToUnixTimeSeconds ( ) . ToString ( ) ;
104- headers . AddRange ( new List < HttpHeader > {
105- new HttpHeader ( _configuration . HeaderLimit , _configuration . RateLimit . ToString ( ) ) ,
106- new HttpHeader ( _configuration . HeaderRemaining , _resourcesRemaining . ToString ( ) ) ,
107- new HttpHeader ( _configuration . HeaderReset , reset )
108- } ) ;
109-
110- // make rate limiting information available for CORS requests
111- if ( request . Headers . FirstOrDefault ( ( h ) => h . Name . Equals ( "Origin" , StringComparison . OrdinalIgnoreCase ) ) is not null )
112- {
113- if ( ! response . Headers . HeaderExists ( "Access-Control-Allow-Origin" ) )
114- {
115- headers . Add ( new HttpHeader ( "Access-Control-Allow-Origin" , "*" ) ) ;
116- }
117- var exposeHeadersHeader = response . Headers . FirstOrDefault ( ( h ) => h . Name . Equals ( "Access-Control-Expose-Headers" , StringComparison . OrdinalIgnoreCase ) ) ;
118- var headerValue = "" ;
119- if ( exposeHeadersHeader is null )
120- {
121- headerValue = $ "{ _configuration . HeaderLimit } , { _configuration . HeaderRemaining } , { _configuration . HeaderReset } , { _configuration . HeaderRetryAfter } ";
122- }
123- else
124- {
125- headerValue = exposeHeadersHeader . Value ;
126- if ( ! headerValue . Contains ( _configuration . HeaderLimit ) )
127- {
128- headerValue += $ ", { _configuration . HeaderLimit } ";
129- }
130- if ( ! headerValue . Contains ( _configuration . HeaderRemaining ) )
131- {
132- headerValue += $ ", { _configuration . HeaderRemaining } ";
133- }
134- if ( ! headerValue . Contains ( _configuration . HeaderReset ) )
135- {
136- headerValue += $ ", { _configuration . HeaderReset } ";
137- }
138- if ( ! headerValue . Contains ( _configuration . HeaderRetryAfter ) )
139- {
140- headerValue += $ ", { _configuration . HeaderRetryAfter } ";
141- }
142- response . Headers . RemoveHeader ( "Access-Control-Expose-Headers" ) ;
143- }
144-
145- headers . Add ( new HttpHeader ( "Access-Control-Expose-Headers" , headerValue ) ) ;
146- }
101+ ProxyUtils . MergeHeaders ( headers , rateLimitingHeaders ) ;
147102 }
148103
149104 // add headers to the original API response, avoiding duplicates
@@ -244,12 +199,12 @@ _urlsToWatch is null ||
244199 {
245200 if ( _configuration . CustomResponse is not null )
246201 {
247- var headers = _configuration . CustomResponse . Response ? . Headers is not null ?
248- _configuration . CustomResponse . Response . Headers . Select ( h => new HttpHeader ( h . Key , h . Value ) ) :
202+ var headers = _configuration . CustomResponse . Headers is not null ?
203+ _configuration . CustomResponse . Headers . Select ( h => new HttpHeader ( h . Key , h . Value ) ) :
249204 Array . Empty < HttpHeader > ( ) ;
250205
251206 // allow custom throttling response
252- var responseCode = ( HttpStatusCode ) ( _configuration . CustomResponse . Response ? . StatusCode ?? 200 ) ;
207+ var responseCode = ( HttpStatusCode ) ( _configuration . CustomResponse . StatusCode ?? 200 ) ;
253208 if ( responseCode == HttpStatusCode . TooManyRequests )
254209 {
255210 e . ThrottledRequests . Add ( new ThrottlerInfo (
@@ -259,8 +214,8 @@ _urlsToWatch is null ||
259214 ) ) ;
260215 }
261216
262- string body = _configuration . CustomResponse . Response ? . Body is not null ?
263- JsonSerializer . Serialize ( _configuration . CustomResponse . Response . Body , new JsonSerializerOptions { WriteIndented = true } ) :
217+ string body = _configuration . CustomResponse . Body is not null ?
218+ JsonSerializer . Serialize ( _configuration . CustomResponse . Body , new JsonSerializerOptions { WriteIndented = true } ) :
264219 "" ;
265220 e . Session . GenericResponse ( body , responseCode , headers ) ;
266221 state . HasBeenSet = true ;
@@ -272,6 +227,43 @@ _urlsToWatch is null ||
272227 }
273228 }
274229
230+ StoreRateLimitingHeaders ( e ) ;
275231 return Task . CompletedTask ;
276232 }
233+
234+ private void StoreRateLimitingHeaders ( ProxyRequestArgs e )
235+ {
236+ // add rate limiting headers if reached the threshold percentage
237+ if ( _resourcesRemaining > _configuration . RateLimit - ( _configuration . RateLimit * _configuration . WarningThresholdPercent / 100 ) )
238+ {
239+ return ;
240+ }
241+
242+ var headers = new List < HttpHeader > ( ) ;
243+ var reset = _configuration . ResetFormat == RateLimitResetFormat . SecondsLeft ?
244+ ( _resetTime - DateTime . Now ) . TotalSeconds . ToString ( "N0" ) : // drop decimals
245+ new DateTimeOffset ( _resetTime ) . ToUnixTimeSeconds ( ) . ToString ( ) ;
246+ headers . AddRange ( new List < HttpHeader >
247+ {
248+ new HttpHeader ( _configuration . HeaderLimit , _configuration . RateLimit . ToString ( ) ) ,
249+ new HttpHeader ( _configuration . HeaderRemaining , _resourcesRemaining . ToString ( ) ) ,
250+ new HttpHeader ( _configuration . HeaderReset , reset )
251+ } ) ;
252+
253+ ExposeRateLimitingForCors ( headers , e ) ;
254+
255+ e . PluginData . Add ( Name , headers ) ;
256+ }
257+
258+ private void ExposeRateLimitingForCors ( IList < HttpHeader > headers , ProxyRequestArgs e )
259+ {
260+ var request = e . Session . HttpClient . Request ;
261+ if ( request . Headers . FirstOrDefault ( ( h ) => h . Name . Equals ( "Origin" , StringComparison . OrdinalIgnoreCase ) ) is null )
262+ {
263+ return ;
264+ }
265+
266+ headers . Add ( new HttpHeader ( "Access-Control-Allow-Origin" , "*" ) ) ;
267+ headers . Add ( new HttpHeader ( "Access-Control-Expose-Headers" , $ "{ _configuration . HeaderLimit } , { _configuration . HeaderRemaining } , { _configuration . HeaderReset } , { _configuration . HeaderRetryAfter } ") ) ;
268+ }
277269}
0 commit comments