Skip to content

Commit a1aeea2

Browse files
committed
add info about rate-limiting request
1 parent a4d669a commit a1aeea2

File tree

4 files changed

+250
-1
lines changed

4 files changed

+250
-1
lines changed

documentation/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ description: Documentation and developer's resources for Fano Framework, web app
5353
- [Sending email](/utilities/sending-email)
5454
- [Identifying client user-agent](/utilities/identifying-client-user-agent)
5555
- [Working with regular expression](/utilities/regular-expression)
56+
- [Rate limiting request](/utilities/rate-limit)
5657

5758
## Database
5859

middlewares/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ Fano Framework provides several built-in middlewares.
308308
- `TCacheControlMiddleware`, middleware class which adds `Cache-Control` response header. For more information, read [Http cache header](/working-with-response/http-cache-header).
309309
- `TNoCacheMiddleware`, middleware class which adds `Cache-Control` response header to prevent browser from caching response.
310310
- `TStaticFilesMiddleware`, middleware class which serves static files. For more information, read [Serving static files](/working-with-response/serve-static-files).
311-
- `TThrottleMiddleware`, middleware class which limit rate of request to one ore more routes.
311+
- `TThrottleMiddleware`, middleware class which [limits rate of request](/utilities/rate-limit) to one or more routes.
312312

313313
### Group several middlewares as one
314314

utilities/index.md

+4
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ description: Tutorial on how to work with some utilities provided by Fano Framew
2424
## Regular expression
2525

2626
- [Working with regular expression](/utilities/regular-expression)
27+
28+
## Rate limiting request
29+
30+
- [Rate limiting request](/utilities/rate-limit)

utilities/rate-limit/index.md

+244
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
title: Rate limiting request
3+
description: Tutorial on how to limit rate of request with utilities provided by Fano Framework
4+
---
5+
6+
<h1 class="major">Rate limiting request</h1>
7+
8+
## Why limit request?
9+
10+
![Rate limit](https://unsplash.com/photos/CGWK6k2RduY)
11+
Rate limiting (by Ludovic Charlet[unsplash.com](https://unsplash.com/photos/CGWK6k2RduY))
12+
13+
While developing API, we want to be able to maximize our application so that it handles request as many as it can. But we also want to avoid client overusing API and cause denial of service to other client or we want to allow paid clients to have bigger number of requests quota than free clients.
14+
15+
You will define a maximum number of request in a given amount of time. When clients reached maximum value, your application answers HTTP error code `429 Too Many Requests`.
16+
17+
## Rate-limit middleware
18+
19+
Fano Framework provides `TThrottleMiddleware` to limit number of request to certain application routes or global to all routes. To use this [middleware](/middlewares), register its factory class (`TThrottleMiddlewareFactory`) to [service container](/dependency-container) as shown in following code.
20+
21+
```
22+
container.add(
23+
'throttle-one-request-per-sec',
24+
TThrottleMiddlewareFactory.create()
25+
);
26+
```
27+
and then attach middleware to one or more [routes](/working-with-router).
28+
29+
```
30+
router.get(
31+
'/',
32+
container['homeController'] as IRequestHandler
33+
).add(container['throttle-one-request-per-sec'] as IMiddleware);
34+
```
35+
36+
## Change number of requests
37+
38+
By default when you use `TThrottleMiddlewareFactory` as shown above, throttle middleware allows 1 request per second only. If you create another request before 1 second elapse, you get HTTP 429 error.
39+
40+
To change number of requests calls following factory methods:
41+
42+
- `ratePerSecond()`, set number of requests per second.
43+
- `ratePerMinute()`, set number of requests per minute.
44+
- `ratePerHour()`, set number of requests per hour.
45+
- `ratePerDay()`, set number of requests per day.
46+
- `rate()`, set number of requests of given interval in seconds.
47+
48+
Except `rate()` method, which requires two parameters, other methods require one parameter, i.e integer value of maximum number of requests. All `rate*()` methods return current factory instance so you can chain its call.
49+
50+
For example to set maximum 2 requests per second
51+
```
52+
container.add(
53+
'throttle-two-request-per-sec',
54+
TThrottleMiddlewareFactory.create()
55+
.ratePerSecond(2)
56+
);
57+
```
58+
59+
To set custom interval of 10 requests per 30 minutes
60+
```
61+
const
62+
NUM_SECONDS_IN_30_MINUTES = 30 * 60;
63+
64+
container.add(
65+
'throttle-ten-request-per-30-min',
66+
TThrottleMiddlewareFactory.create()
67+
.rate(10, NUM_SECONDS_IN_30_MINUTES)
68+
);
69+
```
70+
71+
## Change how requests are identified
72+
To be able to tell which clients exceed limit, throttle middleware need to be able to indentify requests using instance of `IRequestIdentifier` interface.
73+
74+
Currently, Fano Framework provides two implementations of this interface.
75+
76+
- `TIpAddrRequestIdentifier` which identifies request based on IP address.
77+
- `TSessionRequestIdentifier` which identifies request based on session ID.
78+
79+
By default, request is identified using its IP address. To change request identifier instance, call `requestIdentifier()` method of factory and pass new instance
80+
81+
```
82+
container.add(
83+
'throttle-one-request-per-sec',
84+
TThrottleMiddlewareFactory.create()
85+
.requestIdentifier(TSessionRequestIdentifier.create())
86+
);
87+
```
88+
`requestIdentifier()` returns current factory instance so that you can chain with other methods,
89+
90+
```
91+
container.add(
92+
'throttle-one-request-per-sec',
93+
TThrottleMiddlewareFactory.create()
94+
.requestIdentifier(TSessionRequestIdentifier.create())
95+
.ratePerSecond(1)
96+
);
97+
```
98+
99+
If you need to use different ways to identify request, for example using unique key passed as query string or POST parameter, ypu can create a class which implements `IRequestIdentifier` interface and implement its `getId()` method. For example
100+
101+
```
102+
unit MyRequestIdentifierImpl;
103+
104+
interface
105+
106+
{$MODE OBJFPC}
107+
{$H+}
108+
109+
uses
110+
111+
RequestIntf,
112+
RequestIdentifierIntf;
113+
114+
type
115+
116+
TMyRequestIdentifier = class (TInterfacedObject, IRequestIdentifier)
117+
public
118+
(*!------------------------------------------------
119+
* get identifier from request
120+
*-----------------------------------------------
121+
* @param request request object
122+
* @return identifier string
123+
*-----------------------------------------------*)
124+
function getId(const request : IRequest) : shortstring; override;
125+
end;
126+
127+
implementation
128+
129+
(*!------------------------------------------------
130+
* get identifier from request
131+
*-----------------------------------------------
132+
* @param request request object
133+
* @return identifier string
134+
*-----------------------------------------------*)
135+
function TMyRequestIdentifier.getId(
136+
const request : IRequest
137+
) : shortstring;
138+
begin
139+
result := request.getParam('accesskey');
140+
end;
141+
```
142+
You can also create class inherit from `TAbstractRequestIdentifier` and implement its abstract method `getId()`.
143+
144+
## Change rate limiter
145+
146+
Throttle middleware depends on instance of `IRateLimiter` interface to do actual test of request limitation. Currently Fano Framework provides
147+
148+
- `TMemoryRateLimiter` which tracks requests on memory. This implementation can not be used in CGI application as CGI application is created for each request.
149+
- `TDecoratorRateLimiter` which decorates other `IRateLimiter` instance.
150+
151+
Development of other type rate limiter such as rate limiter which keeps track request in Redis or MySQL is planned.
152+
153+
By default, `TMemoryRateLimiter` is used. If you need to modify rate dynamically, for example, each client type has its own maximum rate, you can create rate limiter inherit from `TDecoratorRateLimiter` and modify its `limit()` method as shown in following example.
154+
155+
```
156+
unit MyRateLimiterImpl;
157+
158+
interface
159+
160+
{$MODE OBJFPC}
161+
{$H+}
162+
163+
uses
164+
165+
RateLimiterIntf,
166+
RateTypes,
167+
DecoratorRateLimiter;
168+
169+
type
170+
171+
TMyRateLimiter = class (TDecoratorRateLimiter)
172+
public
173+
(*!------------------------------------------------
174+
* check if number of operations identified by identifier
175+
* not exceed rate configuration
176+
*-----------------------------------------------
177+
* @param identifier unique identifier
178+
* @param rate rate configuration
179+
* @return limit status
180+
*-----------------------------------------------*)
181+
function limit(
182+
const identifier : shortstring;
183+
const rate : TRate
184+
) : TLimitStatus; override;
185+
186+
end;
187+
188+
implementation
189+
190+
(*!------------------------------------------------
191+
* check if number of operations identified by identifier
192+
* not exceed rate configuration
193+
*-----------------------------------------------
194+
* @param identifier unique identifier
195+
* @param rate rate configuration
196+
* @return limit status
197+
*-----------------------------------------------*)
198+
function TMyRateLimiter.limit(
199+
const identifier : shortstring;
200+
const rate : TRate
201+
) : TLimitStatus;
202+
var customRatePerUser : TRate;
203+
begina
204+
customRatePerUser := rate;
205+
206+
//TODO: load maximum number of request from
207+
//database with identifier as primary key
208+
//customRatePerUser := getRateFromDatabase(identifier);
209+
210+
result := inherited limit(identifier, customRatePerUser);
211+
end;
212+
213+
end.
214+
```
215+
`TRate` is record declared as follows,
216+
217+
```
218+
TRate = record
219+
//number of operations allowed
220+
operations : integer;
221+
222+
//interval in seconds
223+
interval : integer;
224+
end;
225+
```
226+
227+
You can register throttle middleware with memory rate limiter but rate is load dynamically from database
228+
229+
```
230+
container.add(
231+
'throttle-one-request-per-sec',
232+
TThrottleMiddlewareFactory.create()
233+
.rateLimiter(
234+
TMyRateLimiter.create(
235+
TMemoryRateLimiter.create()
236+
)
237+
)
238+
);
239+
```
240+
241+
## Explore more
242+
243+
- [Utilities](/utilities)
244+
- [Middlewares](/middlewares)

0 commit comments

Comments
 (0)