@@ -38,7 +38,7 @@ public class RateLimitConfiguration
38
38
public int RetryAfterSeconds { get ; set ; } = 5 ;
39
39
public RateLimitResponseWhenLimitExceeded WhenLimitExceeded { get ; set ; } = RateLimitResponseWhenLimitExceeded . Throttle ;
40
40
public string CustomResponseFile { get ; set ; } = "rate-limit-response.json" ;
41
- public MockResponse ? CustomResponse { get ; set ; }
41
+ public MockResponseResponse ? CustomResponse { get ; set ; }
42
42
}
43
43
44
44
public class RateLimitingPlugin : BaseProxyPlugin
@@ -95,55 +95,10 @@ private void UpdateProxyResponse(ProxyHttpEventArgsBase e, HttpStatusCode errorS
95
95
return ;
96
96
}
97
97
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 )
100
100
{
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 ) ;
147
102
}
148
103
149
104
// add headers to the original API response, avoiding duplicates
@@ -244,12 +199,12 @@ _urlsToWatch is null ||
244
199
{
245
200
if ( _configuration . CustomResponse is not null )
246
201
{
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 ) ) :
249
204
Array . Empty < HttpHeader > ( ) ;
250
205
251
206
// allow custom throttling response
252
- var responseCode = ( HttpStatusCode ) ( _configuration . CustomResponse . Response ? . StatusCode ?? 200 ) ;
207
+ var responseCode = ( HttpStatusCode ) ( _configuration . CustomResponse . StatusCode ?? 200 ) ;
253
208
if ( responseCode == HttpStatusCode . TooManyRequests )
254
209
{
255
210
e . ThrottledRequests . Add ( new ThrottlerInfo (
@@ -259,8 +214,8 @@ _urlsToWatch is null ||
259
214
) ) ;
260
215
}
261
216
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 } ) :
264
219
"" ;
265
220
e . Session . GenericResponse ( body , responseCode , headers ) ;
266
221
state . HasBeenSet = true ;
@@ -272,6 +227,43 @@ _urlsToWatch is null ||
272
227
}
273
228
}
274
229
230
+ StoreRateLimitingHeaders ( e ) ;
275
231
return Task . CompletedTask ;
276
232
}
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
+ }
277
269
}
0 commit comments