1
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11
12
13 typedef struct {
14 u_char color;
15 u_char dummy;
16 u_short len;
17 ngx_queue_t queue;
18 ngx_msec_t last;
19 /* integer value, 1 corresponds to 0.001 r/s */
20 ngx_uint_t excess;
21 ngx_uint_t count;
22 u_char data[1];
23 } ngx_http_limit_req_node_t;
24
25
26 typedef struct {
27 ngx_rbtree_t rbtree;
28 ngx_rbtree_node_t sentinel;
29 ngx_queue_t queue;
30 } ngx_http_limit_req_shctx_t;
31
32
33 typedef struct {
34 ngx_http_limit_req_shctx_t *sh;
35 ngx_slab_pool_t *shpool;
36 /* integer value, 1 corresponds to 0.001 r/s */
37 ngx_uint_t rate;
38 ngx_http_complex_value_t key;
39 ngx_http_limit_req_node_t *node;
40 } ngx_http_limit_req_ctx_t;
41
42
43 typedef struct {
44 ngx_shm_zone_t *shm_zone;
45 /* integer value, 1 corresponds to 0.001 r/s */
46 ngx_uint_t burst;
47 ngx_uint_t delay;
48 } ngx_http_limit_req_limit_t;
49
50
51 typedef struct {
52 ngx_array_t limits;
53 ngx_uint_t limit_log_level;
54 ngx_uint_t delay_log_level;
55 ngx_uint_t status_code;
56 } ngx_http_limit_req_conf_t;
57
58
59 static void ngx_http_limit_req_delay(ngx_http_request_t *r);
60 static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit,
61 ngx_uint_t hash, ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account);
62 static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits,
63 ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit);
64 static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
65 ngx_uint_t n);
66
67 static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
68 static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
69 void *child);
70 static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
71 void *conf);
72 static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
73 void *conf);
74 static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
75
76
77 static ngx_conf_enum_t ngx_http_limit_req_log_levels[] = {
78 { ngx_string("info"), NGX_LOG_INFO },
79 { ngx_string("notice"), NGX_LOG_NOTICE },
80 { ngx_string("warn"), NGX_LOG_WARN },
81 { ngx_string("error"), NGX_LOG_ERR },
82 { ngx_null_string, 0 }
83 };
84
85
86 static ngx_conf_num_bounds_t ngx_http_limit_req_status_bounds = {
87 ngx_conf_check_num_bounds, 400, 599
88 };
89
90
91 static ngx_command_t ngx_http_limit_req_commands[] = {
92
93 { ngx_string("limit_req_zone"),
94 NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
95 ngx_http_limit_req_zone,
96 0,
97 0,
98 NULL },
99
100 { ngx_string("limit_req"),
101 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
102 ngx_http_limit_req,
103 NGX_HTTP_LOC_CONF_OFFSET,
104 0,
105 NULL },
106
107 { ngx_string("limit_req_log_level"),
108 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
109 ngx_conf_set_enum_slot,
110 NGX_HTTP_LOC_CONF_OFFSET,
111 offsetof(ngx_http_limit_req_conf_t, limit_log_level),
112 &ngx_http_limit_req_log_levels },
113
114 { ngx_string("limit_req_status"),
115 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
116 ngx_conf_set_num_slot,
117 NGX_HTTP_LOC_CONF_OFFSET,
118 offsetof(ngx_http_limit_req_conf_t, status_code),
119 &ngx_http_limit_req_status_bounds },
120
121 ngx_null_command
122 };
123
124
125 static ngx_http_module_t ngx_http_limit_req_module_ctx = {
126 NULL, /* preconfiguration */
127 ngx_http_limit_req_init, /* postconfiguration */
128
129 NULL, /* create main configuration */
130 NULL, /* init main configuration */
131
132 NULL, /* create server configuration */
133 NULL, /* merge server configuration */
134
135 ngx_http_limit_req_create_conf, /* create location configuration */
136 ngx_http_limit_req_merge_conf /* merge location configuration */
137 };
138
139
140 ngx_module_t ngx_http_limit_req_module = {
141 NGX_MODULE_V1,
142 &ngx_http_limit_req_module_ctx, /* module context */
143 ngx_http_limit_req_commands, /* module directives */
144 NGX_HTTP_MODULE, /* module type */
145 NULL, /* init master */
146 NULL, /* init module */
147 NULL, /* init process */
148 NULL, /* init thread */
149 NULL, /* exit thread */
150 NULL, /* exit process */
151 NULL, /* exit master */
152 NGX_MODULE_V1_PADDING
153 };
154
155
156 static ngx_int_t
ngx_http_limit_req_handler(ngx_http_request_t * r)157 ngx_http_limit_req_handler(ngx_http_request_t *r)
158 {
159 uint32_t hash;
160 ngx_str_t key;
161 ngx_int_t rc;
162 ngx_uint_t n, excess;
163 ngx_msec_t delay;
164 ngx_http_limit_req_ctx_t *ctx;
165 ngx_http_limit_req_conf_t *lrcf;
166 ngx_http_limit_req_limit_t *limit, *limits;
167
168 if (r->main->limit_req_set) {
169 return NGX_DECLINED;
170 }
171
172 lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
173 limits = lrcf->limits.elts;
174
175 excess = 0;
176
177 rc = NGX_DECLINED;
178
179 #if (NGX_SUPPRESS_WARN)
180 limit = NULL;
181 #endif
182
183 for (n = 0; n < lrcf->limits.nelts; n++) {
184
185 limit = &limits[n];
186
187 ctx = limit->shm_zone->data;
188
189 if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
190 return NGX_HTTP_INTERNAL_SERVER_ERROR;
191 }
192
193 if (key.len == 0) {
194 continue;
195 }
196
197 if (key.len > 65535) {
198 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
199 "the value of the \"%V\" key "
200 "is more than 65535 bytes: \"%V\"",
201 &ctx->key.value, &key);
202 continue;
203 }
204
205 hash = ngx_crc32_short(key.data, key.len);
206
207 ngx_shmtx_lock(&ctx->shpool->mutex);
208
209 rc = ngx_http_limit_req_lookup(limit, hash, &key, &excess,
210 (n == lrcf->limits.nelts - 1));
211
212 ngx_shmtx_unlock(&ctx->shpool->mutex);
213
214 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
215 "limit_req[%ui]: %i %ui.%03ui",
216 n, rc, excess / 1000, excess % 1000);
217
218 if (rc != NGX_AGAIN) {
219 break;
220 }
221 }
222
223 if (rc == NGX_DECLINED) {
224 return NGX_DECLINED;
225 }
226
227 r->main->limit_req_set = 1;
228
229 if (rc == NGX_BUSY || rc == NGX_ERROR) {
230
231 if (rc == NGX_BUSY) {
232 ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
233 "limiting requests, excess: %ui.%03ui by zone \"%V\"",
234 excess / 1000, excess % 1000,
235 &limit->shm_zone->shm.name);
236 }
237
238 while (n--) {
239 ctx = limits[n].shm_zone->data;
240
241 if (ctx->node == NULL) {
242 continue;
243 }
244
245 ngx_shmtx_lock(&ctx->shpool->mutex);
246
247 ctx->node->count--;
248
249 ngx_shmtx_unlock(&ctx->shpool->mutex);
250
251 ctx->node = NULL;
252 }
253
254 return lrcf->status_code;
255 }
256
257 /* rc == NGX_AGAIN || rc == NGX_OK */
258
259 if (rc == NGX_AGAIN) {
260 excess = 0;
261 }
262
263 delay = ngx_http_limit_req_account(limits, n, &excess, &limit);
264
265 if (!delay) {
266 return NGX_DECLINED;
267 }
268
269 ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
270 "delaying request, excess: %ui.%03ui, by zone \"%V\"",
271 excess / 1000, excess % 1000, &limit->shm_zone->shm.name);
272
273 if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
274 return NGX_HTTP_INTERNAL_SERVER_ERROR;
275 }
276
277 r->read_event_handler = ngx_http_test_reading;
278 r->write_event_handler = ngx_http_limit_req_delay;
279
280 r->connection->write->delayed = 1;
281 ngx_add_timer(r->connection->write, delay);
282
283 return NGX_AGAIN;
284 }
285
286
287 static void
ngx_http_limit_req_delay(ngx_http_request_t * r)288 ngx_http_limit_req_delay(ngx_http_request_t *r)
289 {
290 ngx_event_t *wev;
291
292 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
293 "limit_req delay");
294
295 wev = r->connection->write;
296
297 if (wev->delayed) {
298
299 if (ngx_handle_write_event(wev, 0) != NGX_OK) {
300 ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
301 }
302
303 return;
304 }
305
306 if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
307 ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
308 return;
309 }
310
311 r->read_event_handler = ngx_http_block_reading;
312 r->write_event_handler = ngx_http_core_run_phases;
313
314 ngx_http_core_run_phases(r);
315 }
316
317
318 static void
ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t * temp,ngx_rbtree_node_t * node,ngx_rbtree_node_t * sentinel)319 ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp,
320 ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
321 {
322 ngx_rbtree_node_t **p;
323 ngx_http_limit_req_node_t *lrn, *lrnt;
324
325 for ( ;; ) {
326
327 if (node->key < temp->key) {
328
329 p = &temp->left;
330
331 } else if (node->key > temp->key) {
332
333 p = &temp->right;
334
335 } else { /* node->key == temp->key */
336
337 lrn = (ngx_http_limit_req_node_t *) &node->color;
338 lrnt = (ngx_http_limit_req_node_t *) &temp->color;
339
340 p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0)
341 ? &temp->left : &temp->right;
342 }
343
344 if (*p == sentinel) {
345 break;
346 }
347
348 temp = *p;
349 }
350
351 *p = node;
352 node->parent = temp;
353 node->left = sentinel;
354 node->right = sentinel;
355 ngx_rbt_red(node);
356 }
357
358
359 static ngx_int_t
ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t * limit,ngx_uint_t hash,ngx_str_t * key,ngx_uint_t * ep,ngx_uint_t account)360 ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash,
361 ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account)
362 {
363 size_t size;
364 ngx_int_t rc, excess;
365 ngx_msec_t now;
366 ngx_msec_int_t ms;
367 ngx_rbtree_node_t *node, *sentinel;
368 ngx_http_limit_req_ctx_t *ctx;
369 ngx_http_limit_req_node_t *lr;
370
371 now = ngx_current_msec;
372
373 ctx = limit->shm_zone->data;
374
375 node = ctx->sh->rbtree.root;
376 sentinel = ctx->sh->rbtree.sentinel;
377
378 while (node != sentinel) {
379
380 if (hash < node->key) {
381 node = node->left;
382 continue;
383 }
384
385 if (hash > node->key) {
386 node = node->right;
387 continue;
388 }
389
390 /* hash == node->key */
391
392 lr = (ngx_http_limit_req_node_t *) &node->color;
393
394 rc = ngx_memn2cmp(key->data, lr->data, key->len, (size_t) lr->len);
395
396 if (rc == 0) {
397 ngx_queue_remove(&lr->queue);
398 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
399
400 ms = (ngx_msec_int_t) (now - lr->last);
401
402 if (ms < -60000) {
403 ms = 1;
404
405 } else if (ms < 0) {
406 ms = 0;
407 }
408
409 excess = lr->excess - ctx->rate * ms / 1000 + 1000;
410
411 if (excess < 0) {
412 excess = 0;
413 }
414
415 *ep = excess;
416
417 if ((ngx_uint_t) excess > limit->burst) {
418 return NGX_BUSY;
419 }
420
421 if (account) {
422 lr->excess = excess;
423
424 if (ms) {
425 lr->last = now;
426 }
427
428 return NGX_OK;
429 }
430
431 lr->count++;
432
433 ctx->node = lr;
434
435 return NGX_AGAIN;
436 }
437
438 node = (rc < 0) ? node->left : node->right;
439 }
440
441 *ep = 0;
442
443 size = offsetof(ngx_rbtree_node_t, color)
444 + offsetof(ngx_http_limit_req_node_t, data)
445 + key->len;
446
447 ngx_http_limit_req_expire(ctx, 1);
448
449 node = ngx_slab_alloc_locked(ctx->shpool, size);
450
451 if (node == NULL) {
452 ngx_http_limit_req_expire(ctx, 0);
453
454 node = ngx_slab_alloc_locked(ctx->shpool, size);
455 if (node == NULL) {
456 ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
457 "could not allocate node%s", ctx->shpool->log_ctx);
458 return NGX_ERROR;
459 }
460 }
461
462 node->key = hash;
463
464 lr = (ngx_http_limit_req_node_t *) &node->color;
465
466 lr->len = (u_short) key->len;
467 lr->excess = 0;
468
469 ngx_memcpy(lr->data, key->data, key->len);
470
471 ngx_rbtree_insert(&ctx->sh->rbtree, node);
472
473 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
474
475 if (account) {
476 lr->last = now;
477 lr->count = 0;
478 return NGX_OK;
479 }
480
481 lr->last = 0;
482 lr->count = 1;
483
484 ctx->node = lr;
485
486 return NGX_AGAIN;
487 }
488
489
490 static ngx_msec_t
ngx_http_limit_req_account(ngx_http_limit_req_limit_t * limits,ngx_uint_t n,ngx_uint_t * ep,ngx_http_limit_req_limit_t ** limit)491 ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n,
492 ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit)
493 {
494 ngx_int_t excess;
495 ngx_msec_t now, delay, max_delay;
496 ngx_msec_int_t ms;
497 ngx_http_limit_req_ctx_t *ctx;
498 ngx_http_limit_req_node_t *lr;
499
500 excess = *ep;
501
502 if ((ngx_uint_t) excess <= (*limit)->delay) {
503 max_delay = 0;
504
505 } else {
506 ctx = (*limit)->shm_zone->data;
507 max_delay = (excess - (*limit)->delay) * 1000 / ctx->rate;
508 }
509
510 while (n--) {
511 ctx = limits[n].shm_zone->data;
512 lr = ctx->node;
513
514 if (lr == NULL) {
515 continue;
516 }
517
518 ngx_shmtx_lock(&ctx->shpool->mutex);
519
520 now = ngx_current_msec;
521 ms = (ngx_msec_int_t) (now - lr->last);
522
523 if (ms < -60000) {
524 ms = 1;
525
526 } else if (ms < 0) {
527 ms = 0;
528 }
529
530 excess = lr->excess - ctx->rate * ms / 1000 + 1000;
531
532 if (excess < 0) {
533 excess = 0;
534 }
535
536 if (ms) {
537 lr->last = now;
538 }
539
540 lr->excess = excess;
541 lr->count--;
542
543 ngx_shmtx_unlock(&ctx->shpool->mutex);
544
545 ctx->node = NULL;
546
547 if ((ngx_uint_t) excess <= limits[n].delay) {
548 continue;
549 }
550
551 delay = (excess - limits[n].delay) * 1000 / ctx->rate;
552
553 if (delay > max_delay) {
554 max_delay = delay;
555 *ep = excess;
556 *limit = &limits[n];
557 }
558 }
559
560 return max_delay;
561 }
562
563
564 static void
ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t * ctx,ngx_uint_t n)565 ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
566 {
567 ngx_int_t excess;
568 ngx_msec_t now;
569 ngx_queue_t *q;
570 ngx_msec_int_t ms;
571 ngx_rbtree_node_t *node;
572 ngx_http_limit_req_node_t *lr;
573
574 now = ngx_current_msec;
575
576 /*
577 * n == 1 deletes one or two zero rate entries
578 * n == 0 deletes oldest entry by force
579 * and one or two zero rate entries
580 */
581
582 while (n < 3) {
583
584 if (ngx_queue_empty(&ctx->sh->queue)) {
585 return;
586 }
587
588 q = ngx_queue_last(&ctx->sh->queue);
589
590 lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
591
592 if (lr->count) {
593
594 /*
595 * There is not much sense in looking further,
596 * because we bump nodes on the lookup stage.
597 */
598
599 return;
600 }
601
602 if (n++ != 0) {
603
604 ms = (ngx_msec_int_t) (now - lr->last);
605 ms = ngx_abs(ms);
606
607 if (ms < 60000) {
608 return;
609 }
610
611 excess = lr->excess - ctx->rate * ms / 1000;
612
613 if (excess > 0) {
614 return;
615 }
616 }
617
618 ngx_queue_remove(q);
619
620 node = (ngx_rbtree_node_t *)
621 ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
622
623 ngx_rbtree_delete(&ctx->sh->rbtree, node);
624
625 ngx_slab_free_locked(ctx->shpool, node);
626 }
627 }
628
629
630 static ngx_int_t
ngx_http_limit_req_init_zone(ngx_shm_zone_t * shm_zone,void * data)631 ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
632 {
633 ngx_http_limit_req_ctx_t *octx = data;
634
635 size_t len;
636 ngx_http_limit_req_ctx_t *ctx;
637
638 ctx = shm_zone->data;
639
640 if (octx) {
641 if (ctx->key.value.len != octx->key.value.len
642 || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
643 ctx->key.value.len)
644 != 0)
645 {
646 ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
647 "limit_req \"%V\" uses the \"%V\" key "
648 "while previously it used the \"%V\" key",
649 &shm_zone->shm.name, &ctx->key.value,
650 &octx->key.value);
651 return NGX_ERROR;
652 }
653
654 ctx->sh = octx->sh;
655 ctx->shpool = octx->shpool;
656
657 return NGX_OK;
658 }
659
660 ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
661
662 if (shm_zone->shm.exists) {
663 ctx->sh = ctx->shpool->data;
664
665 return NGX_OK;
666 }
667
668 ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
669 if (ctx->sh == NULL) {
670 return NGX_ERROR;
671 }
672
673 ctx->shpool->data = ctx->sh;
674
675 ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
676 ngx_http_limit_req_rbtree_insert_value);
677
678 ngx_queue_init(&ctx->sh->queue);
679
680 len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
681
682 ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
683 if (ctx->shpool->log_ctx == NULL) {
684 return NGX_ERROR;
685 }
686
687 ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
688 &shm_zone->shm.name);
689
690 ctx->shpool->log_nomem = 0;
691
692 return NGX_OK;
693 }
694
695
696 static void *
ngx_http_limit_req_create_conf(ngx_conf_t * cf)697 ngx_http_limit_req_create_conf(ngx_conf_t *cf)
698 {
699 ngx_http_limit_req_conf_t *conf;
700
701 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
702 if (conf == NULL) {
703 return NULL;
704 }
705
706 /*
707 * set by ngx_pcalloc():
708 *
709 * conf->limits.elts = NULL;
710 */
711
712 conf->limit_log_level = NGX_CONF_UNSET_UINT;
713 conf->status_code = NGX_CONF_UNSET_UINT;
714
715 return conf;
716 }
717
718
719 static char *
ngx_http_limit_req_merge_conf(ngx_conf_t * cf,void * parent,void * child)720 ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
721 {
722 ngx_http_limit_req_conf_t *prev = parent;
723 ngx_http_limit_req_conf_t *conf = child;
724
725 if (conf->limits.elts == NULL) {
726 conf->limits = prev->limits;
727 }
728
729 ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level,
730 NGX_LOG_ERR);
731
732 conf->delay_log_level = (conf->limit_log_level == NGX_LOG_INFO) ?
733 NGX_LOG_INFO : conf->limit_log_level + 1;
734
735 ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
736 NGX_HTTP_SERVICE_UNAVAILABLE);
737
738 return NGX_CONF_OK;
739 }
740
741
742 static char *
ngx_http_limit_req_zone(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)743 ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
744 {
745 u_char *p;
746 size_t len;
747 ssize_t size;
748 ngx_str_t *value, name, s;
749 ngx_int_t rate, scale;
750 ngx_uint_t i;
751 ngx_shm_zone_t *shm_zone;
752 ngx_http_limit_req_ctx_t *ctx;
753 ngx_http_compile_complex_value_t ccv;
754
755 value = cf->args->elts;
756
757 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
758 if (ctx == NULL) {
759 return NGX_CONF_ERROR;
760 }
761
762 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
763
764 ccv.cf = cf;
765 ccv.value = &value[1];
766 ccv.complex_value = &ctx->key;
767
768 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
769 return NGX_CONF_ERROR;
770 }
771
772 size = 0;
773 rate = 1;
774 scale = 1;
775 name.len = 0;
776
777 for (i = 2; i < cf->args->nelts; i++) {
778
779 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
780
781 name.data = value[i].data + 5;
782
783 p = (u_char *) ngx_strchr(name.data, ':');
784
785 if (p == NULL) {
786 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
787 "invalid zone size \"%V\"", &value[i]);
788 return NGX_CONF_ERROR;
789 }
790
791 name.len = p - name.data;
792
793 s.data = p + 1;
794 s.len = value[i].data + value[i].len - s.data;
795
796 size = ngx_parse_size(&s);
797
798 if (size == NGX_ERROR) {
799 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
800 "invalid zone size \"%V\"", &value[i]);
801 return NGX_CONF_ERROR;
802 }
803
804 if (size < (ssize_t) (8 * ngx_pagesize)) {
805 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
806 "zone \"%V\" is too small", &value[i]);
807 return NGX_CONF_ERROR;
808 }
809
810 continue;
811 }
812
813 if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
814
815 len = value[i].len;
816 p = value[i].data + len - 3;
817
818 if (ngx_strncmp(p, "r/s", 3) == 0) {
819 scale = 1;
820 len -= 3;
821
822 } else if (ngx_strncmp(p, "r/m", 3) == 0) {
823 scale = 60;
824 len -= 3;
825 }
826
827 rate = ngx_atoi(value[i].data + 5, len - 5);
828 if (rate <= 0) {
829 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
830 "invalid rate \"%V\"", &value[i]);
831 return NGX_CONF_ERROR;
832 }
833
834 continue;
835 }
836
837 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
838 "invalid parameter \"%V\"", &value[i]);
839 return NGX_CONF_ERROR;
840 }
841
842 if (name.len == 0) {
843 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
844 "\"%V\" must have \"zone\" parameter",
845 &cmd->name);
846 return NGX_CONF_ERROR;
847 }
848
849 ctx->rate = rate * 1000 / scale;
850
851 shm_zone = ngx_shared_memory_add(cf, &name, size,
852 &ngx_http_limit_req_module);
853 if (shm_zone == NULL) {
854 return NGX_CONF_ERROR;
855 }
856
857 if (shm_zone->data) {
858 ctx = shm_zone->data;
859
860 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
861 "%V \"%V\" is already bound to key \"%V\"",
862 &cmd->name, &name, &ctx->key.value);
863 return NGX_CONF_ERROR;
864 }
865
866 shm_zone->init = ngx_http_limit_req_init_zone;
867 shm_zone->data = ctx;
868
869 return NGX_CONF_OK;
870 }
871
872
873 static char *
ngx_http_limit_req(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)874 ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
875 {
876 ngx_http_limit_req_conf_t *lrcf = conf;
877
878 ngx_int_t burst, delay;
879 ngx_str_t *value, s;
880 ngx_uint_t i;
881 ngx_shm_zone_t *shm_zone;
882 ngx_http_limit_req_limit_t *limit, *limits;
883
884 value = cf->args->elts;
885
886 shm_zone = NULL;
887 burst = 0;
888 delay = 0;
889
890 for (i = 1; i < cf->args->nelts; i++) {
891
892 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
893
894 s.len = value[i].len - 5;
895 s.data = value[i].data + 5;
896
897 shm_zone = ngx_shared_memory_add(cf, &s, 0,
898 &ngx_http_limit_req_module);
899 if (shm_zone == NULL) {
900 return NGX_CONF_ERROR;
901 }
902
903 continue;
904 }
905
906 if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
907
908 burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
909 if (burst <= 0) {
910 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
911 "invalid burst value \"%V\"", &value[i]);
912 return NGX_CONF_ERROR;
913 }
914
915 continue;
916 }
917
918 if (ngx_strncmp(value[i].data, "delay=", 6) == 0) {
919
920 delay = ngx_atoi(value[i].data + 6, value[i].len - 6);
921 if (delay <= 0) {
922 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
923 "invalid delay value \"%V\"", &value[i]);
924 return NGX_CONF_ERROR;
925 }
926
927 continue;
928 }
929
930 if (ngx_strcmp(value[i].data, "nodelay") == 0) {
931 delay = NGX_MAX_INT_T_VALUE / 1000;
932 continue;
933 }
934
935 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
936 "invalid parameter \"%V\"", &value[i]);
937 return NGX_CONF_ERROR;
938 }
939
940 if (shm_zone == NULL) {
941 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
942 "\"%V\" must have \"zone\" parameter",
943 &cmd->name);
944 return NGX_CONF_ERROR;
945 }
946
947 limits = lrcf->limits.elts;
948
949 if (limits == NULL) {
950 if (ngx_array_init(&lrcf->limits, cf->pool, 1,
951 sizeof(ngx_http_limit_req_limit_t))
952 != NGX_OK)
953 {
954 return NGX_CONF_ERROR;
955 }
956 }
957
958 for (i = 0; i < lrcf->limits.nelts; i++) {
959 if (shm_zone == limits[i].shm_zone) {
960 return "is duplicate";
961 }
962 }
963
964 limit = ngx_array_push(&lrcf->limits);
965 if (limit == NULL) {
966 return NGX_CONF_ERROR;
967 }
968
969 limit->shm_zone = shm_zone;
970 limit->burst = burst * 1000;
971 limit->delay = delay * 1000;
972
973 return NGX_CONF_OK;
974 }
975
976
977 static ngx_int_t
ngx_http_limit_req_init(ngx_conf_t * cf)978 ngx_http_limit_req_init(ngx_conf_t *cf)
979 {
980 ngx_http_handler_pt *h;
981 ngx_http_core_main_conf_t *cmcf;
982
983 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
984
985 h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
986 if (h == NULL) {
987 return NGX_ERROR;
988 }
989
990 *h = ngx_http_limit_req_handler;
991
992 return NGX_OK;
993 }
994