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 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
,
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
,
61 static char *ngx_http_limit_req_zone(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
63 static char *ngx_http_limit_req(ngx_conf_t
*cf
, ngx_command_t
*cmd
,
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
,
86 { ngx_string("limit_req"),
87 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE123
,
89 NGX_HTTP_LOC_CONF_OFFSET
,
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
},
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
= {
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
136 ngx_http_limit_req_handler(ngx_http_request_t
*r
)
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
) {
153 lrcf
= ngx_http_get_module_loc_conf(r
, ngx_http_limit_req_module
);
155 if (lrcf
->shm_zone
== NULL
) {
159 ctx
= lrcf
->shm_zone
->data
;
161 vv
= ngx_http_get_indexed_variable(r
, ctx
->index
);
163 if (vv
== NULL
|| vv
->not_found
) {
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\"",
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
)
200 node
= ngx_slab_alloc_locked(ctx
->shpool
, n
);
203 ngx_http_limit_req_expire(ctx
, 0);
205 node
= ngx_slab_alloc_locked(ctx
->shpool
, n
);
207 ngx_shmtx_unlock(&ctx
->shpool
->mutex
);
208 return NGX_HTTP_SERVICE_UNAVAILABLE
;
212 lr
= (ngx_http_limit_req_node_t
*) &node
->color
;
215 lr
->len
= (u_char
) len
;
217 tp
= ngx_timeofday();
218 lr
->last
= (ngx_msec_t
) (tp
->sec
* 1000 + tp
->msec
);
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
);
232 ngx_shmtx_unlock(&ctx
->shpool
->mutex
);
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 */
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
);
270 ngx_http_limit_req_delay(ngx_http_request_t
*r
)
274 ngx_log_debug0(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
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
);
290 if (ngx_handle_read_event(r
->connection
->read
, 0) != NGX_OK
) {
291 ngx_http_finalize_request(r
, NGX_HTTP_INTERNAL_SERVER_ERROR
);
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
);
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
;
311 if (node
->key
< temp
->key
) {
315 } else if (node
->key
> temp
->key
) {
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
) {
337 node
->left
= sentinel
;
338 node
->right
= sentinel
;
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
;
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
) {
367 if (hash
> node
->key
) {
372 /* hash == node->key */
375 lr
= (ngx_http_limit_req_node_t
*) &node
->color
;
377 rc
= ngx_memn2cmp(data
, lr
->data
, len
, (size_t) lr
->len
);
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;
396 if ((ngx_uint_t
) excess
> lrcf
->burst
) {
410 node
= (rc
< 0) ? node
->left
: node
->right
;
412 } while (node
!= sentinel
&& hash
== node
->key
);
424 ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t
*ctx
, ngx_uint_t n
)
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
446 if (ngx_queue_empty(&ctx
->sh
->queue
)) {
450 q
= ngx_queue_last(&ctx
->sh
->queue
);
452 lr
= ngx_queue_data(q
, ngx_http_limit_req_node_t
, queue
);
456 ms
= (ngx_msec_int_t
) (now
- lr
->last
);
463 excess
= lr
->excess
- ctx
->rate
* ms
/ 1000;
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
);
483 ngx_http_limit_req_init_zone(ngx_shm_zone_t
*shm_zone
, void *data
)
485 ngx_http_limit_req_ctx_t
*octx
= data
;
488 ngx_http_limit_req_ctx_t
*ctx
;
490 ctx
= shm_zone
->data
;
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
);
502 ctx
->shpool
= octx
->shpool
;
507 ctx
->shpool
= (ngx_slab_pool_t
*) shm_zone
->shm
.addr
;
509 if (shm_zone
->shm
.exists
) {
510 ctx
->sh
= ctx
->shpool
->data
;
515 ctx
->sh
= ngx_slab_alloc(ctx
->shpool
, sizeof(ngx_http_limit_req_shctx_t
));
516 if (ctx
->sh
== NULL
) {
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
) {
534 ngx_sprintf(ctx
->shpool
->log_ctx
, " in limit_req zone \"%V\"%Z",
535 &shm_zone
->shm
.name
);
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
));
552 * set by ngx_pcalloc():
554 * conf->shm_zone = NULL;
559 conf
->limit_log_level
= NGX_CONF_UNSET_UINT
;
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
) {
575 ngx_conf_merge_uint_value(conf
->limit_log_level
, prev
->limit_log_level
,
578 conf
->delay_log_level
= (conf
->limit_log_level
== NGX_LOG_INFO
) ?
579 NGX_LOG_INFO
: conf
->limit_log_level
+ 1;
586 ngx_http_limit_req_zone(ngx_conf_t
*cf
, ngx_command_t
*cmd
, void *conf
)
590 ngx_str_t
*value
, name
, s
;
591 ngx_int_t rate
, scale
;
593 ngx_shm_zone_t
*shm_zone
;
594 ngx_http_limit_req_ctx_t
*ctx
;
596 value
= cf
->args
->elts
;
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
, ':');
615 name
.len
= p
- name
.data
;
619 s
.len
= value
[i
].data
+ value
[i
].len
- p
;
622 size
= ngx_parse_size(&s
);
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) {
636 p
= value
[i
].data
+ len
- 3;
638 if (ngx_strncmp(p
, "r/s", 3) == 0) {
642 } else if (ngx_strncmp(p
, "r/m", 3) == 0) {
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
;
657 if (value
[i
].data
[0] == '$') {
662 ctx
= ngx_pcalloc(cf
->pool
, sizeof(ngx_http_limit_req_ctx_t
));
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
;
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",
686 return NGX_CONF_ERROR
;
690 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
691 "no variable is defined for limit_req_zone \"%V\"",
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
;
721 ngx_http_limit_req(ngx_conf_t
*cf
, ngx_command_t
*cmd
, void *conf
)
723 ngx_http_limit_req_conf_t
*lrcf
= conf
;
729 if (lrcf
->shm_zone
) {
730 return "is duplicate";
733 value
= cf
->args
->elts
;
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
;
753 if (ngx_strncmp(value
[i
].data
, "burst=", 6) == 0) {
755 burst
= ngx_atoi(value
[i
].data
+ 6, value
[i
].len
- 6);
757 ngx_conf_log_error(NGX_LOG_EMERG
, cf
, 0,
758 "invalid burst rate \"%V\"", &value
[i
]);
759 return NGX_CONF_ERROR
;
765 if (ngx_strncmp(value
[i
].data
, "nodelay", 7) == 0) {
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",
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;
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
);
808 *h
= ngx_http_limit_req_handler
;