1VMOD_VSTHROTTLE(3) VMOD_VSTHROTTLE(3)
2
3
4
6 vmod_vsthrottle - Throttling VMOD
7
9 import vsthrottle [as name] [from "path"]
10
11 BOOL is_denied(STRING key, INT limit, DURATION period, DURATION block)
12
13 VOID return_token(STRING key, INT limit, DURATION period, DURATION block)
14
15 INT remaining(STRING key, INT limit, DURATION period, DURATION block)
16
17 DURATION blocked(STRING key, INT limit, DURATION period, DURATION block)
18
20 A Varnish vmod for rate-limiting traffic on a single Varnish server.
21 Offers a simple interface for throttling traffic on a per-key basis to
22 a specific request rate.
23
24 Keys can be specified from any VCL string, e.g. based on client.ip, a
25 specific cookie value, an API token, etc.
26
27 The request rate is specified as the number of requests permitted over
28 a period. To keep things simple, this is passed as two separate parame‐
29 ters, 'limit' and 'period'.
30
31 If an optional duration 'block' is specified, then access is denied
32 altogether for that period of time after the rate limit is reached.
33 This is a way to entirely turn away a particularly troublesome source
34 of traffic for a while, rather than let them back in as soon as the
35 rate slips back under the threshold.
36
37 This VMOD implements a token bucket algorithm. State associated with
38 the token bucket for each key is stored in-memory using BSD's red-black
39 tree implementation.
40
41 Memory usage is around 100 bytes per key tracked.
42
43 Example:
44
45 vcl 4.0;
46 import vsthrottle;
47
48 backend default { .host = "192.0.2.11"; .port = "8080"; }
49
50 sub vcl_recv {
51 # Varnish will set client.identity for you based on client IP.
52
53 if (vsthrottle.is_denied(client.identity, 15, 10s, 30s)) {
54 # Client has exceeded 15 reqs per 10s.
55 # When this happens, block altogether for the next 30s.
56 return (synth(429, "Too Many Requests"));
57 }
58
59 # There is a quota per API key that must be fulfilled.
60 if (vsthrottle.is_denied("apikey:" + req.http.Key, 30, 60s)) {
61 return (synth(429, "Too Many Requests"));
62 }
63
64 # Only allow a few POST/PUTs per client.
65 if (req.method == "POST" || req.method == "PUT") {
66 if (vsthrottle.is_denied("rw" + client.identity, 2, 10s)) {
67 return (synth(429, "Too Many Requests"));
68 }
69 }
70 }
71
72 BOOL is_denied(STRING key, INT limit, DURATION period, DURATION block)
73 BOOL is_denied(
74 STRING key,
75 INT limit,
76 DURATION period,
77 DURATION block=0
78 )
79
80 Arguments:
81
82 · key: A unique identifier to define what is being throttled - more
83 examples below
84
85 · limit: How many requests in the specified period
86
87 · period: The time period
88
89 · block: a period to deny all access after hitting the threshold.
90 Default is 0s
91
92 Description
93 Can be used to rate limit the traffic for a specific key to a
94 maximum of 'limit' requests per 'period' time. If 'block' is >
95 0s, (0s by default), then always deny for 'key' for that length
96 of time after hitting the threshold.
97
98 Note: A token bucket is uniquely identified by the 4-tuple of
99 its key, limit, period and block, so using the same key multiple
100 places with different rules will create multiple token buckets.
101
102 Example
103
104 sub vcl_recv {
105 if (vsthrottle.is_denied(client.identity, 15, 10s)) {
106 # Client has exceeded 15 reqs per 10s
107 return (synth(429, "Too Many Requests"));
108 }
109
110 # ...
111 }
112
113 VOID return_token(STRING key, INT limit, DURATION period, DURATION block)
114 VOID return_token(
115 STRING key,
116 INT limit,
117 DURATION period,
118 DURATION block=0
119 )
120
121 Arguments:
122
123 · Same arguments as is_denied()
124
125 Description
126 Increment (by one) the number of tokens in the specified bucket.
127 is_denied() decrements the bucket by one token and
128 return_token() adds it back. Using these two, you can effec‐
129 tively make a token bucket act like a limit on concurrent
130 requests instead of requests / time.
131
132 Note: This function doesn't enforce anything, it merely credits
133 a token to appropriate bucket.
134
135 Warning: If streaming is enabled (beresp.do_stream = true) as it
136 is by default now, vcl_deliver() is called before the response
137 is sent to the client (who may download it slowly). Thus you may
138 credit the token back too early if you use return_token() in
139 vcl_backend_response().
140
141 Example
142
143 sub vcl_recv {
144 if (vsthrottle.is_denied(client.identity, 20, 20s)) {
145 # Client has more than 20 concurrent requests
146 return (synth(429, "Too Many Requests In Flight"));
147 }
148
149 # ...
150 }
151
152 sub vcl_deliver {
153 vsthrottle.return_token(client.identity, 10, 10s);
154 }
155
156 INT remaining(STRING key, INT limit, DURATION period, DURATION block)
157 INT remaining(
158 STRING key,
159 INT limit,
160 DURATION period,
161 DURATION block=0
162 )
163
164 Arguments:
165
166 · Same arguments as is_denied()
167
168 Description
169 Get the current number of tokens for a given token bucket. This can
170 be used to create a response header to inform clients of their cur‐
171 rent quota.
172
173 Example
174
175 sub vcl_deliver {
176 set resp.http.X-RateLimit-Remaining = vsthrottle.remaining(client.identity, 15, 10s);
177 }
178
179 DURATION blocked(STRING key, INT limit, DURATION period, DURATION block)
180 DURATION blocked(
181 STRING key,
182 INT limit,
183 DURATION period,
184 DURATION block
185 )
186
187 Arguments:
188
189 · Same arguments as is_denied()
190
191 Description
192 If the token bucket identified by the four parameters has been
193 blocked by use of the 'block' parameter in 'is_denied()', then
194 return the time remaining in the block. If it is not blocked, return
195 0s. This can be used to inform clients how long they will be locked
196 out.
197
198 Example
199
200 sub vcl_deliver {
201 set resp.http.Retry-After
202 = vsthrottle.blocked(client.identity, 15, 10s, 30s);
203 }
204
205
206
207
208 VMOD_VSTHROTTLE(3)