nginx 1.1.11
[nginx.git] / src / http / modules / ngx_http_limit_req_module.c
blob718fae8e2e29c8e37578ed73ff4b78c05032d4d2
2 /*
3 * Copyright (C) Igor Sysoev
4 */
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
12 typedef struct {
13 u_char color;
14 u_char dummy;
15 u_short len;
16 ngx_queue_t queue;
17 ngx_msec_t last;
18 /* integer value, 1 corresponds to 0.001 r/s */
19 ngx_uint_t excess;
20 u_char data[1];
21 } ngx_http_limit_req_node_t;
24 typedef struct {
25 ngx_rbtree_t rbtree;
26 ngx_rbtree_node_t sentinel;
27 ngx_queue_t queue;
28 } ngx_http_limit_req_shctx_t;
31 typedef struct {
32 ngx_http_limit_req_shctx_t *sh;
33 ngx_slab_pool_t *shpool;
34 /* integer value, 1 corresponds to 0.001 r/s */
35 ngx_uint_t rate;
36 ngx_int_t index;
37 ngx_str_t var;
38 } ngx_http_limit_req_ctx_t;
41 typedef struct {
42 ngx_shm_zone_t *shm_zone;
43 /* integer value, 1 corresponds to 0.001 r/s */
44 ngx_uint_t burst;
45 ngx_uint_t limit_log_level;
46 ngx_uint_t delay_log_level;
48 ngx_uint_t nodelay; /* unsigned nodelay:1 */
49 } ngx_http_limit_req_conf_t;
52 static void ngx_http_limit_req_delay(ngx_http_request_t *r);
53 static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf,
54 ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep);
55 static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
56 ngx_uint_t n);
58 static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
59 static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
60 void *child);
61 static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
62 void *conf);
63 static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
64 void *conf);
65 static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
68 static ngx_conf_enum_t ngx_http_limit_req_log_levels[] = {
69 { ngx_string("info"), NGX_LOG_INFO },
70 { ngx_string("notice"), NGX_LOG_NOTICE },
71 { ngx_string("warn"), NGX_LOG_WARN },
72 { ngx_string("error"), NGX_LOG_ERR },
73 { ngx_null_string, 0 }
77 static ngx_command_t ngx_http_limit_req_commands[] = {
79 { ngx_string("limit_req_zone"),
80 NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
81 ngx_http_limit_req_zone,
84 NULL },
86 { ngx_string("limit_req"),
87 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
88 ngx_http_limit_req,
89 NGX_HTTP_LOC_CONF_OFFSET,
91 NULL },
93 { ngx_string("limit_req_log_level"),
94 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
95 ngx_conf_set_enum_slot,
96 NGX_HTTP_LOC_CONF_OFFSET,
97 offsetof(ngx_http_limit_req_conf_t, limit_log_level),
98 &ngx_http_limit_req_log_levels },
100 ngx_null_command
104 static ngx_http_module_t ngx_http_limit_req_module_ctx = {
105 NULL, /* preconfiguration */
106 ngx_http_limit_req_init, /* postconfiguration */
108 NULL, /* create main configuration */
109 NULL, /* init main configuration */
111 NULL, /* create server configuration */
112 NULL, /* merge server configuration */
114 ngx_http_limit_req_create_conf, /* create location configration */
115 ngx_http_limit_req_merge_conf /* merge location configration */
119 ngx_module_t ngx_http_limit_req_module = {
120 NGX_MODULE_V1,
121 &ngx_http_limit_req_module_ctx, /* module context */
122 ngx_http_limit_req_commands, /* module directives */
123 NGX_HTTP_MODULE, /* module type */
124 NULL, /* init master */
125 NULL, /* init module */
126 NULL, /* init process */
127 NULL, /* init thread */
128 NULL, /* exit thread */
129 NULL, /* exit process */
130 NULL, /* exit master */
131 NGX_MODULE_V1_PADDING
135 static ngx_int_t
136 ngx_http_limit_req_handler(ngx_http_request_t *r)
138 size_t len, n;
139 uint32_t hash;
140 ngx_int_t rc;
141 ngx_uint_t excess;
142 ngx_time_t *tp;
143 ngx_rbtree_node_t *node;
144 ngx_http_variable_value_t *vv;
145 ngx_http_limit_req_ctx_t *ctx;
146 ngx_http_limit_req_node_t *lr;
147 ngx_http_limit_req_conf_t *lrcf;
149 if (r->main->limit_req_set) {
150 return NGX_DECLINED;
153 lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
155 if (lrcf->shm_zone == NULL) {
156 return NGX_DECLINED;
159 ctx = lrcf->shm_zone->data;
161 vv = ngx_http_get_indexed_variable(r, ctx->index);
163 if (vv == NULL || vv->not_found) {
164 return NGX_DECLINED;
167 len = vv->len;
169 if (len == 0) {
170 return NGX_DECLINED;
173 if (len > 65535) {
174 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
175 "the value of the \"%V\" variable "
176 "is more than 65535 bytes: \"%v\"",
177 &ctx->var, vv);
178 return NGX_DECLINED;
181 r->main->limit_req_set = 1;
183 hash = ngx_crc32_short(vv->data, len);
185 ngx_shmtx_lock(&ctx->shpool->mutex);
187 ngx_http_limit_req_expire(ctx, 1);
189 rc = ngx_http_limit_req_lookup(lrcf, hash, vv->data, len, &excess);
191 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
192 "limit_req: %i %ui.%03ui", rc, excess / 1000, excess % 1000);
194 if (rc == NGX_DECLINED) {
196 n = offsetof(ngx_rbtree_node_t, color)
197 + offsetof(ngx_http_limit_req_node_t, data)
198 + len;
200 node = ngx_slab_alloc_locked(ctx->shpool, n);
201 if (node == NULL) {
203 ngx_http_limit_req_expire(ctx, 0);
205 node = ngx_slab_alloc_locked(ctx->shpool, n);
206 if (node == NULL) {
207 ngx_shmtx_unlock(&ctx->shpool->mutex);
208 return NGX_HTTP_SERVICE_UNAVAILABLE;
212 lr = (ngx_http_limit_req_node_t *) &node->color;
214 node->key = hash;
215 lr->len = (u_char) len;
217 tp = ngx_timeofday();
218 lr->last = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
220 lr->excess = 0;
221 ngx_memcpy(lr->data, vv->data, len);
223 ngx_rbtree_insert(&ctx->sh->rbtree, node);
225 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
227 ngx_shmtx_unlock(&ctx->shpool->mutex);
229 return NGX_DECLINED;
232 ngx_shmtx_unlock(&ctx->shpool->mutex);
234 if (rc == NGX_OK) {
235 return NGX_DECLINED;
238 if (rc == NGX_BUSY) {
239 ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
240 "limiting requests, excess: %ui.%03ui by zone \"%V\"",
241 excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name);
243 return NGX_HTTP_SERVICE_UNAVAILABLE;
246 /* rc == NGX_AGAIN */
248 if (lrcf->nodelay) {
249 return NGX_DECLINED;
252 ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
253 "delaying request, excess: %ui.%03ui, by zone \"%V\"",
254 excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name);
256 if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
257 return NGX_HTTP_INTERNAL_SERVER_ERROR;
260 r->read_event_handler = ngx_http_test_reading;
261 r->write_event_handler = ngx_http_limit_req_delay;
262 ngx_add_timer(r->connection->write,
263 (ngx_msec_t) excess * 1000 / ctx->rate);
265 return NGX_AGAIN;
269 static void
270 ngx_http_limit_req_delay(ngx_http_request_t *r)
272 ngx_event_t *wev;
274 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
275 "limit_req delay");
277 wev = r->connection->write;
279 if (!wev->timedout) {
281 if (ngx_handle_write_event(wev, 0) != NGX_OK) {
282 ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
285 return;
288 wev->timedout = 0;
290 if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
291 ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
292 return;
295 r->read_event_handler = ngx_http_block_reading;
296 r->write_event_handler = ngx_http_core_run_phases;
298 ngx_http_core_run_phases(r);
302 static void
303 ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp,
304 ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
306 ngx_rbtree_node_t **p;
307 ngx_http_limit_req_node_t *lrn, *lrnt;
309 for ( ;; ) {
311 if (node->key < temp->key) {
313 p = &temp->left;
315 } else if (node->key > temp->key) {
317 p = &temp->right;
319 } else { /* node->key == temp->key */
321 lrn = (ngx_http_limit_req_node_t *) &node->color;
322 lrnt = (ngx_http_limit_req_node_t *) &temp->color;
324 p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0)
325 ? &temp->left : &temp->right;
328 if (*p == sentinel) {
329 break;
332 temp = *p;
335 *p = node;
336 node->parent = temp;
337 node->left = sentinel;
338 node->right = sentinel;
339 ngx_rbt_red(node);
343 static ngx_int_t
344 ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf, ngx_uint_t hash,
345 u_char *data, size_t len, ngx_uint_t *ep)
347 ngx_int_t rc, excess;
348 ngx_time_t *tp;
349 ngx_msec_t now;
350 ngx_msec_int_t ms;
351 ngx_rbtree_node_t *node, *sentinel;
352 ngx_http_limit_req_ctx_t *ctx;
353 ngx_http_limit_req_node_t *lr;
355 ctx = lrcf->shm_zone->data;
357 node = ctx->sh->rbtree.root;
358 sentinel = ctx->sh->rbtree.sentinel;
360 while (node != sentinel) {
362 if (hash < node->key) {
363 node = node->left;
364 continue;
367 if (hash > node->key) {
368 node = node->right;
369 continue;
372 /* hash == node->key */
374 do {
375 lr = (ngx_http_limit_req_node_t *) &node->color;
377 rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len);
379 if (rc == 0) {
380 ngx_queue_remove(&lr->queue);
381 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
383 tp = ngx_timeofday();
385 now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
386 ms = (ngx_msec_int_t) (now - lr->last);
388 excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
390 if (excess < 0) {
391 excess = 0;
394 *ep = excess;
396 if ((ngx_uint_t) excess > lrcf->burst) {
397 return NGX_BUSY;
400 lr->excess = excess;
401 lr->last = now;
403 if (excess) {
404 return NGX_AGAIN;
407 return NGX_OK;
410 node = (rc < 0) ? node->left : node->right;
412 } while (node != sentinel && hash == node->key);
414 break;
417 *ep = 0;
419 return NGX_DECLINED;
423 static void
424 ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
426 ngx_int_t excess;
427 ngx_time_t *tp;
428 ngx_msec_t now;
429 ngx_queue_t *q;
430 ngx_msec_int_t ms;
431 ngx_rbtree_node_t *node;
432 ngx_http_limit_req_node_t *lr;
434 tp = ngx_timeofday();
436 now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
439 * n == 1 deletes one or two zero rate entries
440 * n == 0 deletes oldest entry by force
441 * and one or two zero rate entries
444 while (n < 3) {
446 if (ngx_queue_empty(&ctx->sh->queue)) {
447 return;
450 q = ngx_queue_last(&ctx->sh->queue);
452 lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
454 if (n++ != 0) {
456 ms = (ngx_msec_int_t) (now - lr->last);
457 ms = ngx_abs(ms);
459 if (ms < 60000) {
460 return;
463 excess = lr->excess - ctx->rate * ms / 1000;
465 if (excess > 0) {
466 return;
470 ngx_queue_remove(q);
472 node = (ngx_rbtree_node_t *)
473 ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
475 ngx_rbtree_delete(&ctx->sh->rbtree, node);
477 ngx_slab_free_locked(ctx->shpool, node);
482 static ngx_int_t
483 ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
485 ngx_http_limit_req_ctx_t *octx = data;
487 size_t len;
488 ngx_http_limit_req_ctx_t *ctx;
490 ctx = shm_zone->data;
492 if (octx) {
493 if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) {
494 ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
495 "limit_req \"%V\" uses the \"%V\" variable "
496 "while previously it used the \"%V\" variable",
497 &shm_zone->shm.name, &ctx->var, &octx->var);
498 return NGX_ERROR;
501 ctx->sh = octx->sh;
502 ctx->shpool = octx->shpool;
504 return NGX_OK;
507 ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
509 if (shm_zone->shm.exists) {
510 ctx->sh = ctx->shpool->data;
512 return NGX_OK;
515 ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
516 if (ctx->sh == NULL) {
517 return NGX_ERROR;
520 ctx->shpool->data = ctx->sh;
522 ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
523 ngx_http_limit_req_rbtree_insert_value);
525 ngx_queue_init(&ctx->sh->queue);
527 len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
529 ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
530 if (ctx->shpool->log_ctx == NULL) {
531 return NGX_ERROR;
534 ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
535 &shm_zone->shm.name);
537 return NGX_OK;
541 static void *
542 ngx_http_limit_req_create_conf(ngx_conf_t *cf)
544 ngx_http_limit_req_conf_t *conf;
546 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
547 if (conf == NULL) {
548 return NULL;
552 * set by ngx_pcalloc():
554 * conf->shm_zone = NULL;
555 * conf->burst = 0;
556 * conf->nodelay = 0;
559 conf->limit_log_level = NGX_CONF_UNSET_UINT;
561 return conf;
565 static char *
566 ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
568 ngx_http_limit_req_conf_t *prev = parent;
569 ngx_http_limit_req_conf_t *conf = child;
571 if (conf->shm_zone == NULL) {
572 *conf = *prev;
575 ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level,
576 NGX_LOG_ERR);
578 conf->delay_log_level = (conf->limit_log_level == NGX_LOG_INFO) ?
579 NGX_LOG_INFO : conf->limit_log_level + 1;
581 return NGX_CONF_OK;
585 static char *
586 ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
588 u_char *p;
589 size_t size, len;
590 ngx_str_t *value, name, s;
591 ngx_int_t rate, scale;
592 ngx_uint_t i;
593 ngx_shm_zone_t *shm_zone;
594 ngx_http_limit_req_ctx_t *ctx;
596 value = cf->args->elts;
598 ctx = NULL;
599 size = 0;
600 rate = 1;
601 scale = 1;
602 name.len = 0;
604 for (i = 1; i < cf->args->nelts; i++) {
606 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
608 name.data = value[i].data + 5;
610 p = (u_char *) ngx_strchr(name.data, ':');
612 if (p) {
613 *p = '\0';
615 name.len = p - name.data;
617 p++;
619 s.len = value[i].data + value[i].len - p;
620 s.data = p;
622 size = ngx_parse_size(&s);
623 if (size > 8191) {
624 continue;
628 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
629 "invalid zone size \"%V\"", &value[i]);
630 return NGX_CONF_ERROR;
633 if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
635 len = value[i].len;
636 p = value[i].data + len - 3;
638 if (ngx_strncmp(p, "r/s", 3) == 0) {
639 scale = 1;
640 len -= 3;
642 } else if (ngx_strncmp(p, "r/m", 3) == 0) {
643 scale = 60;
644 len -= 3;
647 rate = ngx_atoi(value[i].data + 5, len - 5);
648 if (rate <= NGX_ERROR) {
649 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
650 "invalid rate \"%V\"", &value[i]);
651 return NGX_CONF_ERROR;
654 continue;
657 if (value[i].data[0] == '$') {
659 value[i].len--;
660 value[i].data++;
662 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
663 if (ctx == NULL) {
664 return NGX_CONF_ERROR;
667 ctx->index = ngx_http_get_variable_index(cf, &value[i]);
668 if (ctx->index == NGX_ERROR) {
669 return NGX_CONF_ERROR;
672 ctx->var = value[i];
674 continue;
677 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
678 "invalid parameter \"%V\"", &value[i]);
679 return NGX_CONF_ERROR;
682 if (name.len == 0 || size == 0) {
683 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
684 "\"%V\" must have \"zone\" parameter",
685 &cmd->name);
686 return NGX_CONF_ERROR;
689 if (ctx == NULL) {
690 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
691 "no variable is defined for limit_req_zone \"%V\"",
692 &cmd->name);
693 return NGX_CONF_ERROR;
696 ctx->rate = rate * 1000 / scale;
698 shm_zone = ngx_shared_memory_add(cf, &name, size,
699 &ngx_http_limit_req_module);
700 if (shm_zone == NULL) {
701 return NGX_CONF_ERROR;
704 if (shm_zone->data) {
705 ctx = shm_zone->data;
707 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
708 "limit_req_zone \"%V\" is already bound to variable \"%V\"",
709 &value[1], &ctx->var);
710 return NGX_CONF_ERROR;
713 shm_zone->init = ngx_http_limit_req_init_zone;
714 shm_zone->data = ctx;
716 return NGX_CONF_OK;
720 static char *
721 ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
723 ngx_http_limit_req_conf_t *lrcf = conf;
725 ngx_int_t burst;
726 ngx_str_t *value, s;
727 ngx_uint_t i;
729 if (lrcf->shm_zone) {
730 return "is duplicate";
733 value = cf->args->elts;
735 burst = 0;
737 for (i = 1; i < cf->args->nelts; i++) {
739 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
741 s.len = value[i].len - 5;
742 s.data = value[i].data + 5;
744 lrcf->shm_zone = ngx_shared_memory_add(cf, &s, 0,
745 &ngx_http_limit_req_module);
746 if (lrcf->shm_zone == NULL) {
747 return NGX_CONF_ERROR;
750 continue;
753 if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
755 burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
756 if (burst <= 0) {
757 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
758 "invalid burst rate \"%V\"", &value[i]);
759 return NGX_CONF_ERROR;
762 continue;
765 if (ngx_strncmp(value[i].data, "nodelay", 7) == 0) {
766 lrcf->nodelay = 1;
767 continue;
770 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
771 "invalid parameter \"%V\"", &value[i]);
772 return NGX_CONF_ERROR;
775 if (lrcf->shm_zone == NULL) {
776 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
777 "\"%V\" must have \"zone\" parameter",
778 &cmd->name);
779 return NGX_CONF_ERROR;
782 if (lrcf->shm_zone->data == NULL) {
783 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
784 "unknown limit_req_zone \"%V\"",
785 &lrcf->shm_zone->shm.name);
786 return NGX_CONF_ERROR;
789 lrcf->burst = burst * 1000;
791 return NGX_CONF_OK;
795 static ngx_int_t
796 ngx_http_limit_req_init(ngx_conf_t *cf)
798 ngx_http_handler_pt *h;
799 ngx_http_core_main_conf_t *cmcf;
801 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
803 h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
804 if (h == NULL) {
805 return NGX_ERROR;
808 *h = ngx_http_limit_req_handler;
810 return NGX_OK;