Skip to content

Commit bd8c471

Browse files
committed
Improve Redis reply parser + more
- Parse Redis replies using a state machine. - Make period more adjustable. - Add clang-format for consistent source formatting.
1 parent d670eb9 commit bd8c471

14 files changed

+746
-392
lines changed

.clang-format

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
BasedOnStyle: LLVM
3+
IndentWidth: 4
4+
ColumnLimit: 80
5+
AlignConsecutiveDeclarations: true
6+
AlwaysBreakAfterReturnType: AllDefinitions
7+
BreakBeforeBraces: Custom
8+
BraceWrapping:
9+
AfterClass: false
10+
AfterControlStatement: false
11+
AfterEnum: false
12+
AfterFunction: true
13+
AfterNamespace: true
14+
AfterObjCDeclaration: false
15+
AfterStruct: false
16+
AfterUnion: false
17+
BeforeCatch: false
18+
BeforeElse: false
19+
IndentBraces: false
20+
Cpp11BracedListStyle: false
21+
PointerAlignment: Right
22+
SpaceAfterCStyleCast: true
23+
...

README.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Which offers a straightforward implementation of the fairly sophisticated [gener
1212

1313
## Status
1414

15-
This module is **NOT** production ready. See the [To-Do list](TODO.md) for things that needs to be done first.
15+
This module is still under early development but is already production ready.
1616

1717
## Synopsis
1818

@@ -38,26 +38,24 @@ map $limit $limit_key {
3838
rate_limit_status 429;
3939
4040
location = /limit {
41-
rate_limit $limit_key rate=15r/m burst=20;
41+
rate_limit $limit_key requests=15 period=1m burst=20;
4242
rate_limit_pass redis;
4343
}
4444
4545
location = /limit_b {
46-
rate_limit $limit_key rate=20r/m burst=25;
46+
rate_limit $limit_key requests=20 period=1m burst=25;
4747
rate_limit_prefix b;
4848
rate_limit_pass redis;
4949
}
5050
5151
location = /quota {
52-
rate_limit $limit_key rate=15r/m burst=20;
52+
rate_limit $limit_key requests=15 period=1m burst=20;
5353
rate_limit_quantity 0;
5454
rate_limit_pass redis;
5555
rate_limit_headers on;
5656
}
5757
```
5858

59-
*Do not rely on this, as this may change in the future.*
60-
6159
## Installation
6260

6361
*Note: You will need to install the Redis module first, see the install instructions [here](https://github.com/onsigntv/redis-rate-limiter#install).*

TODO.md

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# TODO
22

33
* Add unit tests / Travis integration.
4-
* Stress testing with [ab](https://httpd.apache.org/docs/2.4/programs/ab.html), [wrk](https://github.com/wg/wrk) and [mockeagain](https://github.com/openresty/mockeagain).

config

+2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ RATE_LIMIT_SRCS="
1313
$ngx_addon_dir/src/ngx_http_rate_limit_module.c \
1414
$ngx_addon_dir/src/ngx_http_rate_limit_handler.c \
1515
$ngx_addon_dir/src/ngx_http_rate_limit_upstream.c \
16+
$ngx_addon_dir/src/ngx_http_rate_limit_reply.c \
1617
$ngx_addon_dir/src/ngx_http_rate_limit_util.c \
1718
"
1819

1920
RATE_LIMIT_DEPS=" \
2021
$ngx_addon_dir/src/ngx_http_rate_limit_module.h \
2122
$ngx_addon_dir/src/ngx_http_rate_limit_handler.h \
2223
$ngx_addon_dir/src/ngx_http_rate_limit_upstream.h \
24+
$ngx_addon_dir/src/ngx_http_rate_limit_reply.h \
2325
$ngx_addon_dir/src/ngx_http_rate_limit_util.h \
2426
"
2527

src/ngx_http_rate_limit_handler.c

+58-120
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "ngx_http_rate_limit_handler.h"
2+
#include "ngx_http_rate_limit_reply.h"
23
#include "ngx_http_rate_limit_upstream.h"
34
#include "ngx_http_rate_limit_util.h"
45

@@ -11,23 +12,22 @@ static void ngx_http_rate_limit_abort_request(ngx_http_request_t *r);
1112
static void ngx_http_rate_limit_finalize_request(ngx_http_request_t *r,
1213
ngx_int_t rc);
1314

14-
1515
static ngx_str_t x_limit_header = ngx_string("X-RateLimit-Limit");
1616
static ngx_str_t x_remaining_header = ngx_string("X-RateLimit-Remaining");
1717
static ngx_str_t x_reset_header = ngx_string("X-RateLimit-Reset");
1818
static ngx_str_t x_retry_after_header = ngx_string("Retry-After");
1919

20-
2120
ngx_int_t
2221
ngx_http_rate_limit_handler(ngx_http_request_t *r)
2322
{
2423
ngx_http_upstream_t *u;
2524
ngx_http_rate_limit_ctx_t *ctx;
2625
ngx_http_rate_limit_loc_conf_t *rlcf;
27-
size_t len;
26+
size_t len;
2827
u_char *p, *n;
29-
ngx_str_t target;
30-
ngx_url_t url;
28+
ngx_uint_t status;
29+
ngx_str_t target;
30+
ngx_url_t url;
3131

3232
rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rate_limit_module);
3333

@@ -42,22 +42,23 @@ ngx_http_rate_limit_handler(ngx_http_request_t *r)
4242
return NGX_AGAIN;
4343
}
4444

45+
status = r->upstream->state->status;
46+
4547
/* Return appropriate status */
4648

47-
if (ctx->status == NGX_HTTP_TOO_MANY_REQUESTS) {
49+
if (status == NGX_HTTP_TOO_MANY_REQUESTS) {
4850
ngx_log_error(rlcf->limit_log_level, r->connection->log, 0,
4951
"rate limit exceeded for key \"%V\"", &ctx->key);
5052

5153
return rlcf->status_code;
5254
}
5355

54-
if (ctx->status >= NGX_HTTP_OK
55-
&& ctx->status < NGX_HTTP_SPECIAL_RESPONSE) {
56+
if (status == NGX_HTTP_OK) {
5657
return NGX_OK;
5758
}
5859

5960
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
60-
"rate limit unexpected status: %ui", ctx->status);
61+
"rate limit unexpected status: %ui", status);
6162

6263
return NGX_HTTP_INTERNAL_SERVER_ERROR;
6364
}
@@ -99,6 +100,8 @@ ngx_http_rate_limit_handler(ngx_http_request_t *r)
99100
return NGX_HTTP_INTERNAL_SERVER_ERROR;
100101
}
101102

103+
ctx->request = r;
104+
102105
if (ngx_http_upstream_create(r) != NGX_OK) {
103106
return NGX_HTTP_INTERNAL_SERVER_ERROR;
104107
}
@@ -108,7 +111,8 @@ ngx_http_rate_limit_handler(ngx_http_request_t *r)
108111
if (rlcf->complex_target) {
109112
/* Variables used in the rate_limit_pass directive */
110113

111-
if (ngx_http_complex_value(r, rlcf->complex_target, &target) != NGX_OK) {
114+
if (ngx_http_complex_value(r, rlcf->complex_target, &target) !=
115+
NGX_OK) {
112116
return NGX_ERROR;
113117
}
114118

@@ -145,12 +149,9 @@ ngx_http_rate_limit_handler(ngx_http_request_t *r)
145149

146150
ngx_http_set_ctx(r, ctx, ngx_http_rate_limit_module);
147151

148-
/* We bypass the upstream input filter mechanism in
149-
* ngx_http_rate_limit_rev_handler */
150-
151152
u->input_filter_init = ngx_http_rate_limit_filter_init;
152153
u->input_filter = ngx_http_rate_limit_filter;
153-
u->input_filter_ctx = r;
154+
u->input_filter_ctx = ctx;
154155

155156
r->main->count++;
156157

@@ -163,11 +164,10 @@ ngx_http_rate_limit_handler(ngx_http_request_t *r)
163164
return NGX_AGAIN;
164165
}
165166

166-
167167
static ngx_int_t
168168
ngx_http_rate_limit_create_request(ngx_http_request_t *r)
169169
{
170-
ngx_int_t rc;
170+
ngx_int_t rc;
171171
ngx_buf_t *b;
172172
ngx_chain_t *cl;
173173

@@ -194,7 +194,6 @@ ngx_http_rate_limit_create_request(ngx_http_request_t *r)
194194
return NGX_OK;
195195
}
196196

197-
198197
static ngx_int_t
199198
ngx_http_rate_limit_reinit_request(ngx_http_request_t *r)
200199
{
@@ -208,17 +207,14 @@ ngx_http_rate_limit_reinit_request(ngx_http_request_t *r)
208207
return NGX_OK;
209208
}
210209

211-
212210
static ngx_int_t
213211
ngx_http_rate_limit_process_header(ngx_http_request_t *r)
214212
{
215-
ngx_http_upstream_t *u;
216-
ngx_http_rate_limit_ctx_t *ctx;
217-
ngx_http_rate_limit_loc_conf_t *rlcf;
218-
ngx_buf_t *b;
219-
ngx_str_t buf;
220-
u_char *line, *crnl, *arg;
221-
ngx_uint_t lnum;
213+
ngx_http_upstream_t *u;
214+
ngx_http_rate_limit_ctx_t *ctx;
215+
ngx_buf_t *b;
216+
u_char chr;
217+
ngx_str_t buf;
222218

223219
u = r->upstream;
224220
b = &u->buffer;
@@ -232,114 +228,41 @@ ngx_http_rate_limit_process_header(ngx_http_request_t *r)
232228
return NGX_ERROR;
233229
}
234230

235-
rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rate_limit_module);
231+
/* the first char is the response header */
232+
chr = *b->pos;
236233

237-
lnum = 0;
234+
/* we are always expecting a multi bulk reply */
235+
if (chr != '*') {
236+
buf.data = b->pos;
237+
buf.len = b->last - b->pos;
238238

239-
for (line = b->pos; (crnl = (u_char *) ngx_strstr(line, "\r\n")) != NULL; line = crnl + 2) {
240-
if (++lnum == 1) {
241-
/* the first char is the response header
242-
* the second char is number of return args */
243-
if (*line != '*' && *(line + 1) != '5') {
244-
buf.data = b->pos;
245-
buf.len = b->last - b->pos;
246-
247-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
248-
"rate limit: redis sent invalid header: \"%V\"", &buf);
249-
250-
return NGX_HTTP_UPSTREAM_INVALID_HEADER;
251-
}
252-
253-
continue;
254-
}
255-
if ((arg = (u_char *) ngx_strchr(line, ':')) == NULL || ++arg > crnl) {
256-
/* does not start with colon? */
257-
258-
buf.data = b->pos;
259-
buf.len = b->last - b->pos;
260-
261-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
262-
"rate limit: redis sent invalid response: \"%V\"", &buf);
263-
264-
return NGX_HTTP_UPSTREAM_INVALID_HEADER;
265-
}
239+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
240+
"rate limit: redis sent invalid response: \"%V\"", &buf);
266241

267-
if (lnum == 2) {
268-
/* 0 indicates the action is allowed
269-
* 1 indicates that the action was limited/blocked */
270-
if (*arg == '0') {
271-
ctx->status = NGX_HTTP_OK;
272-
} else {
273-
ctx->status = NGX_HTTP_TOO_MANY_REQUESTS;
274-
}
275-
} else if (ctx->status == NGX_HTTP_TOO_MANY_REQUESTS || rlcf->enable_headers) {
276-
buf.data = arg;
277-
buf.len = crnl - arg;
278-
279-
switch (lnum) {
280-
case 3:
281-
/* X-RateLimit-Limit HTTP header */
282-
(void) ngx_set_custom_header(r, &x_limit_header, &buf);
283-
break;
284-
case 4:
285-
/* X-RateLimit-Remaining HTTP header */
286-
(void) ngx_set_custom_header(r, &x_remaining_header, &buf);
287-
break;
288-
case 5:
289-
/* The number of seconds until the user should retry,
290-
* and always -1 if the action was allowed. */
291-
if (*arg != '-') {
292-
(void) ngx_set_custom_header(r, &x_retry_after_header, &buf);
293-
}
294-
break;
295-
case 6:
296-
/* X-RateLimit-Reset header */
297-
(void) ngx_set_custom_header(r, &x_reset_header, &buf);
298-
break;
299-
default:
300-
buf.data = arg;
301-
buf.len = b->last - arg;
302-
303-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
304-
"rate limit: redis sent extra bytes: \"%V\"", &buf);
305-
306-
return NGX_HTTP_UPSTREAM_INVALID_HEADER;
307-
}
308-
}
242+
return NGX_HTTP_UPSTREAM_INVALID_HEADER;
309243
}
310244

245+
++b->pos;
246+
311247
u->state->status = NGX_HTTP_OK;
312248

313249
return NGX_OK;
314250
}
315251

316-
317252
static ngx_int_t
318253
ngx_http_rate_limit_filter_init(void *data)
319254
{
320-
ngx_http_request_t *r = data;
321-
322-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
323-
"rate_limit: ngx_http_rate_limit_filter_init should not"
324-
" be called by the upstream");
325-
326-
return NGX_ERROR;
255+
return NGX_OK;
327256
}
328257

329-
330258
static ngx_int_t
331259
ngx_http_rate_limit_filter(void *data, ssize_t bytes)
332260
{
333-
ngx_http_request_t *r = data;
334-
335-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
336-
"rate_limit: ngx_http_rate_limit_filter should not"
337-
" be called by the upstream");
261+
ngx_http_rate_limit_ctx_t *ctx = data;
338262

339-
return NGX_ERROR;
263+
return ngx_http_rate_limit_process_reply(ctx, bytes);
340264
}
341265

342-
343266
static void
344267
ngx_http_rate_limit_abort_request(ngx_http_request_t *r)
345268
{
@@ -348,23 +271,38 @@ ngx_http_rate_limit_abort_request(ngx_http_request_t *r)
348271
return;
349272
}
350273

351-
352274
static void
353275
ngx_http_rate_limit_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
354276
{
355-
ngx_http_rate_limit_ctx_t *ctx;
277+
ngx_http_rate_limit_ctx_t *ctx;
278+
ngx_http_rate_limit_loc_conf_t *rlcf;
356279

357280
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
358281
"finalize http rate limit request");
359282

360283
ctx = ngx_http_get_module_ctx(r, ngx_http_rate_limit_module);
361-
if (ctx != NULL) {
362-
ctx->done = 1;
363-
}
364284

365-
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
366-
r->headers_out.status = rc;
285+
rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rate_limit_module);
286+
287+
if (r->upstream->state->status == NGX_HTTP_TOO_MANY_REQUESTS ||
288+
rlcf->enable_headers) {
289+
/* X-RateLimit-Limit HTTP header */
290+
(void) ngx_set_custom_header(r, &x_limit_header, ctx->limit);
291+
292+
/* X-RateLimit-Remaining HTTP header */
293+
(void) ngx_set_custom_header(r, &x_remaining_header, ctx->remaining);
294+
295+
/* Retry-After */
296+
if (r->upstream->state->status == NGX_HTTP_TOO_MANY_REQUESTS) {
297+
(void) ngx_set_custom_header(r, &x_retry_after_header,
298+
ctx->retry_after);
299+
}
300+
301+
/* X-RateLimit-Reset */
302+
(void) ngx_set_custom_header(r, &x_reset_header, ctx->reset);
367303
}
368304

369-
return;
305+
if (ctx != NULL) {
306+
ctx->done = 1;
307+
}
370308
}

0 commit comments

Comments
 (0)