3
* Copyright (C) Igor Sysoev
7
#include <ngx_config.h>
18
/* integer value, 1 corresponds to 0.001 r/s */
21
} ngx_http_limit_req_node_t;
26
ngx_rbtree_node_t sentinel;
28
} ngx_http_limit_req_shctx_t;
32
ngx_http_limit_req_shctx_t *sh;
33
ngx_slab_pool_t *shpool;
34
/* integer value, 1 corresponds to 0.001 r/s */
38
} ngx_http_limit_req_ctx_t;
42
ngx_shm_zone_t *shm_zone;
43
/* integer value, 1 corresponds to 0.001 r/s */
45
ngx_uint_t nodelay;/* unsigned nodelay:1 */
46
} ngx_http_limit_req_conf_t;
49
static void ngx_http_limit_req_delay(ngx_http_request_t *r);
50
static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf,
51
ngx_uint_t hash, u_char *data, size_t len, ngx_http_limit_req_node_t **lrp);
52
static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
55
static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
56
static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
58
static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
60
static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
62
static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
65
static ngx_command_t ngx_http_limit_req_commands[] = {
67
{ ngx_string("limit_req_zone"),
68
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
69
ngx_http_limit_req_zone,
74
{ ngx_string("limit_req"),
75
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
77
NGX_HTTP_LOC_CONF_OFFSET,
85
static ngx_http_module_t ngx_http_limit_req_module_ctx = {
86
NULL, /* preconfiguration */
87
ngx_http_limit_req_init, /* postconfiguration */
89
NULL, /* create main configuration */
90
NULL, /* init main configuration */
92
NULL, /* create server configuration */
93
NULL, /* merge server configuration */
95
ngx_http_limit_req_create_conf, /* create location configration */
96
ngx_http_limit_req_merge_conf /* merge location configration */
100
ngx_module_t ngx_http_limit_req_module = {
102
&ngx_http_limit_req_module_ctx, /* module context */
103
ngx_http_limit_req_commands, /* module directives */
104
NGX_HTTP_MODULE, /* module type */
105
NULL, /* init master */
106
NULL, /* init module */
107
NULL, /* init process */
108
NULL, /* init thread */
109
NULL, /* exit thread */
110
NULL, /* exit process */
111
NULL, /* exit master */
112
NGX_MODULE_V1_PADDING
117
ngx_http_limit_req_handler(ngx_http_request_t *r)
124
ngx_rbtree_node_t *node;
125
ngx_http_variable_value_t *vv;
126
ngx_http_limit_req_ctx_t *ctx;
127
ngx_http_limit_req_node_t *lr;
128
ngx_http_limit_req_conf_t *lrcf;
130
if (r->main->limit_req_set) {
134
lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
136
if (lrcf->shm_zone == NULL) {
140
ctx = lrcf->shm_zone->data;
142
vv = ngx_http_get_indexed_variable(r, ctx->index);
144
if (vv == NULL || vv->not_found) {
155
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
156
"the value of the \"%V\" variable "
157
"is more than 65535 bytes: \"%v\"",
162
r->main->limit_req_set = 1;
164
hash = ngx_crc32_short(vv->data, len);
166
ngx_shmtx_lock(&ctx->shpool->mutex);
168
ngx_http_limit_req_expire(ctx, 1);
170
rc = ngx_http_limit_req_lookup(lrcf, hash, vv->data, len, &lr);
173
ngx_queue_remove(&lr->queue);
175
ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
183
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
184
"limit_req: %i %ui.%03ui", rc, excess / 1000, excess % 1000);
186
if (rc == NGX_BUSY) {
187
ngx_shmtx_unlock(&ctx->shpool->mutex);
189
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
190
"limiting requests, excess: %ui.%03ui by zone \"%V\"",
191
excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name);
193
return NGX_HTTP_SERVICE_UNAVAILABLE;
196
if (rc == NGX_AGAIN) {
197
ngx_shmtx_unlock(&ctx->shpool->mutex);
203
ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
204
"delaying request, excess: %ui.%03ui, by zone \"%V\"",
205
excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name);
207
if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
208
return NGX_HTTP_INTERNAL_SERVER_ERROR;
211
r->read_event_handler = ngx_http_test_reading;
212
r->write_event_handler = ngx_http_limit_req_delay;
213
ngx_add_timer(r->connection->write, (ngx_msec_t) excess);
222
/* rc == NGX_DECLINED */
224
n = offsetof(ngx_rbtree_node_t, color)
225
+ offsetof(ngx_http_limit_req_node_t, data)
228
node = ngx_slab_alloc_locked(ctx->shpool, n);
231
ngx_http_limit_req_expire(ctx, 0);
233
node = ngx_slab_alloc_locked(ctx->shpool, n);
235
ngx_shmtx_unlock(&ctx->shpool->mutex);
236
return NGX_HTTP_SERVICE_UNAVAILABLE;
240
lr = (ngx_http_limit_req_node_t *) &node->color;
243
lr->len = (u_char) len;
245
tp = ngx_timeofday();
246
lr->last = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
249
ngx_memcpy(lr->data, vv->data, len);
251
ngx_rbtree_insert(&ctx->sh->rbtree, node);
253
ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
257
ngx_shmtx_unlock(&ctx->shpool->mutex);
264
ngx_http_limit_req_delay(ngx_http_request_t *r)
266
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
269
if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
270
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
274
r->read_event_handler = ngx_http_block_reading;
275
r->write_event_handler = ngx_http_core_run_phases;
277
ngx_http_core_run_phases(r);
282
ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp,
283
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
285
ngx_rbtree_node_t **p;
286
ngx_http_limit_req_node_t *lrn, *lrnt;
290
if (node->key < temp->key) {
294
} else if (node->key > temp->key) {
298
} else { /* node->key == temp->key */
300
lrn = (ngx_http_limit_req_node_t *) &node->color;
301
lrnt = (ngx_http_limit_req_node_t *) &temp->color;
303
p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0)
304
? &temp->left : &temp->right;
307
if (*p == sentinel) {
316
node->left = sentinel;
317
node->right = sentinel;
323
ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf, ngx_uint_t hash,
324
u_char *data, size_t len, ngx_http_limit_req_node_t **lrp)
326
ngx_int_t rc, excess;
330
ngx_rbtree_node_t *node, *sentinel;
331
ngx_http_limit_req_ctx_t *ctx;
332
ngx_http_limit_req_node_t *lr;
334
ctx = lrcf->shm_zone->data;
336
node = ctx->sh->rbtree.root;
337
sentinel = ctx->sh->rbtree.sentinel;
339
while (node != sentinel) {
341
if (hash < node->key) {
346
if (hash > node->key) {
351
/* hash == node->key */
354
lr = (ngx_http_limit_req_node_t *) &node->color;
356
rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len);
360
tp = ngx_timeofday();
362
now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
363
ms = (ngx_msec_int_t) (now - lr->last);
365
excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
376
if ((ngx_uint_t) excess > lrcf->burst) {
387
node = (rc < 0) ? node->left : node->right;
389
} while (node != sentinel && hash == node->key);
401
ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
408
ngx_rbtree_node_t *node;
409
ngx_http_limit_req_node_t *lr;
411
tp = ngx_timeofday();
413
now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
416
* n == 1 deletes one or two zero rate entries
417
* n == 0 deletes oldest entry by force
418
* and one or two zero rate entries
423
if (ngx_queue_empty(&ctx->sh->queue)) {
427
q = ngx_queue_last(&ctx->sh->queue);
429
lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
433
ms = (ngx_msec_int_t) (now - lr->last);
440
excess = lr->excess - ctx->rate * ms / 1000;
449
node = (ngx_rbtree_node_t *)
450
((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
452
ngx_rbtree_delete(&ctx->sh->rbtree, node);
454
ngx_slab_free_locked(ctx->shpool, node);
460
ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
462
ngx_http_limit_req_ctx_t *octx = data;
465
ngx_http_limit_req_ctx_t *ctx;
467
ctx = shm_zone->data;
470
if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) {
471
ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
472
"limit_req \"%V\" uses the \"%V\" variable "
473
"while previously it used the \"%V\" variable",
474
&shm_zone->shm.name, &ctx->var, &octx->var);
479
ctx->shpool = octx->shpool;
484
ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
486
if (shm_zone->shm.exists) {
487
ctx->sh = ctx->shpool->data;
492
ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
493
if (ctx->sh == NULL) {
497
ctx->shpool->data = ctx->sh;
499
ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
500
ngx_http_limit_req_rbtree_insert_value);
502
ngx_queue_init(&ctx->sh->queue);
504
len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
506
ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
507
if (ctx->shpool->log_ctx == NULL) {
511
ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
512
&shm_zone->shm.name);
519
ngx_http_limit_req_create_conf(ngx_conf_t *cf)
521
ngx_http_limit_req_conf_t *conf;
523
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
525
return NGX_CONF_ERROR;
529
* set by ngx_pcalloc():
531
* conf->shm_zone = NULL;
541
ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
543
ngx_http_limit_req_conf_t *prev = parent;
544
ngx_http_limit_req_conf_t *conf = child;
546
if (conf->shm_zone == NULL) {
555
ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
559
ngx_str_t *value, name, s;
560
ngx_int_t rate, scale;
562
ngx_shm_zone_t *shm_zone;
563
ngx_http_limit_req_ctx_t *ctx;
565
value = cf->args->elts;
573
for (i = 1; i < cf->args->nelts; i++) {
575
if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
577
name.data = value[i].data + 5;
579
p = (u_char *) ngx_strchr(name.data, ':');
584
name.len = p - name.data;
588
s.len = value[i].data + value[i].len - p;
591
size = ngx_parse_size(&s);
597
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
598
"invalid zone size \"%V\"", &value[i]);
599
return NGX_CONF_ERROR;
602
if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
605
p = value[i].data + len - 3;
607
if (ngx_strncmp(p, "r/s", 3) == 0) {
611
} else if (ngx_strncmp(p, "r/m", 3) == 0) {
616
rate = ngx_atoi(value[i].data + 5, len - 5);
617
if (rate <= NGX_ERROR) {
618
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
619
"invalid rate \"%V\"", &value[i]);
620
return NGX_CONF_ERROR;
626
if (value[i].data[0] == '$') {
631
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
633
return NGX_CONF_ERROR;
636
ctx->index = ngx_http_get_variable_index(cf, &value[i]);
637
if (ctx->index == NGX_ERROR) {
638
return NGX_CONF_ERROR;
646
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
647
"invalid parameter \"%V\"", &value[i]);
648
return NGX_CONF_ERROR;
651
if (name.len == 0 || size == 0) {
652
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
653
"\"%V\" must have \"zone\" parameter",
655
return NGX_CONF_ERROR;
659
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
660
"no variable is defined for limit_req_zone \"%V\"",
662
return NGX_CONF_ERROR;
665
ctx->rate = rate * 1000 / scale;
667
shm_zone = ngx_shared_memory_add(cf, &name, size,
668
&ngx_http_limit_req_module);
669
if (shm_zone == NULL) {
670
return NGX_CONF_ERROR;
673
if (shm_zone->data) {
674
ctx = shm_zone->data;
676
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
677
"limit_req_zone \"%V\" is already bound to variable \"%V\"",
678
&value[1], &ctx->var);
679
return NGX_CONF_ERROR;
682
shm_zone->init = ngx_http_limit_req_init_zone;
683
shm_zone->data = ctx;
690
ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
692
ngx_http_limit_req_conf_t *lrcf = conf;
698
if (lrcf->shm_zone) {
699
return "is duplicate";
702
value = cf->args->elts;
706
for (i = 1; i < cf->args->nelts; i++) {
708
if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
710
s.len = value[i].len - 5;
711
s.data = value[i].data + 5;
713
lrcf->shm_zone = ngx_shared_memory_add(cf, &s, 0,
714
&ngx_http_limit_req_module);
715
if (lrcf->shm_zone == NULL) {
716
return NGX_CONF_ERROR;
722
if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
724
burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
726
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
727
"invalid burst rate \"%V\"", &value[i]);
728
return NGX_CONF_ERROR;
734
if (ngx_strncmp(value[i].data, "nodelay", 7) == 0) {
739
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
740
"invalid parameter \"%V\"", &value[i]);
741
return NGX_CONF_ERROR;
744
if (lrcf->shm_zone == NULL) {
745
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
746
"\"%V\" must have \"zone\" parameter",
748
return NGX_CONF_ERROR;
751
if (lrcf->shm_zone->data == NULL) {
752
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
753
"unknown limit_req_zone \"%V\"",
754
&lrcf->shm_zone->shm.name);
755
return NGX_CONF_ERROR;
758
lrcf->burst = burst * 1000;
765
ngx_http_limit_req_init(ngx_conf_t *cf)
767
ngx_http_handler_pt *h;
768
ngx_http_core_main_conf_t *cmcf;
770
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
772
h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
777
*h = ngx_http_limit_req_handler;