ngx_http_limit_req_module.c revision e18a033b
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
13typedef 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
26typedef 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
33typedef 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
43typedef 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                   nodelay; /* unsigned  nodelay:1 */
48} ngx_http_limit_req_limit_t;
49
50
51typedef 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
59static void ngx_http_limit_req_delay(ngx_http_request_t *r);
60static 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);
62static 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);
64static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
65    ngx_uint_t n);
66
67static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
68static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
69    void *child);
70static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
71    void *conf);
72static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
73    void *conf);
74static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
75
76
77static 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
86static ngx_conf_num_bounds_t  ngx_http_limit_req_status_bounds = {
87    ngx_conf_check_num_bounds, 400, 599
88};
89
90
91static 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
125static 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
140ngx_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
156static ngx_int_t
157ngx_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
287static void
288ngx_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
318static void
319ngx_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
359static ngx_int_t
360ngx_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            excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
403
404            if (excess < 0) {
405                excess = 0;
406            }
407
408            *ep = excess;
409
410            if ((ngx_uint_t) excess > limit->burst) {
411                return NGX_BUSY;
412            }
413
414            if (account) {
415                lr->excess = excess;
416                lr->last = now;
417                return NGX_OK;
418            }
419
420            lr->count++;
421
422            ctx->node = lr;
423
424            return NGX_AGAIN;
425        }
426
427        node = (rc < 0) ? node->left : node->right;
428    }
429
430    *ep = 0;
431
432    size = offsetof(ngx_rbtree_node_t, color)
433           + offsetof(ngx_http_limit_req_node_t, data)
434           + key->len;
435
436    ngx_http_limit_req_expire(ctx, 1);
437
438    node = ngx_slab_alloc_locked(ctx->shpool, size);
439
440    if (node == NULL) {
441        ngx_http_limit_req_expire(ctx, 0);
442
443        node = ngx_slab_alloc_locked(ctx->shpool, size);
444        if (node == NULL) {
445            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
446                          "could not allocate node%s", ctx->shpool->log_ctx);
447            return NGX_ERROR;
448        }
449    }
450
451    node->key = hash;
452
453    lr = (ngx_http_limit_req_node_t *) &node->color;
454
455    lr->len = (u_short) key->len;
456    lr->excess = 0;
457
458    ngx_memcpy(lr->data, key->data, key->len);
459
460    ngx_rbtree_insert(&ctx->sh->rbtree, node);
461
462    ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
463
464    if (account) {
465        lr->last = now;
466        lr->count = 0;
467        return NGX_OK;
468    }
469
470    lr->last = 0;
471    lr->count = 1;
472
473    ctx->node = lr;
474
475    return NGX_AGAIN;
476}
477
478
479static ngx_msec_t
480ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n,
481    ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit)
482{
483    ngx_int_t                   excess;
484    ngx_msec_t                  now, delay, max_delay;
485    ngx_msec_int_t              ms;
486    ngx_http_limit_req_ctx_t   *ctx;
487    ngx_http_limit_req_node_t  *lr;
488
489    excess = *ep;
490
491    if (excess == 0 || (*limit)->nodelay) {
492        max_delay = 0;
493
494    } else {
495        ctx = (*limit)->shm_zone->data;
496        max_delay = excess * 1000 / ctx->rate;
497    }
498
499    while (n--) {
500        ctx = limits[n].shm_zone->data;
501        lr = ctx->node;
502
503        if (lr == NULL) {
504            continue;
505        }
506
507        ngx_shmtx_lock(&ctx->shpool->mutex);
508
509        now = ngx_current_msec;
510        ms = (ngx_msec_int_t) (now - lr->last);
511
512        excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
513
514        if (excess < 0) {
515            excess = 0;
516        }
517
518        lr->last = now;
519        lr->excess = excess;
520        lr->count--;
521
522        ngx_shmtx_unlock(&ctx->shpool->mutex);
523
524        ctx->node = NULL;
525
526        if (limits[n].nodelay) {
527            continue;
528        }
529
530        delay = excess * 1000 / ctx->rate;
531
532        if (delay > max_delay) {
533            max_delay = delay;
534            *ep = excess;
535            *limit = &limits[n];
536        }
537    }
538
539    return max_delay;
540}
541
542
543static void
544ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
545{
546    ngx_int_t                   excess;
547    ngx_msec_t                  now;
548    ngx_queue_t                *q;
549    ngx_msec_int_t              ms;
550    ngx_rbtree_node_t          *node;
551    ngx_http_limit_req_node_t  *lr;
552
553    now = ngx_current_msec;
554
555    /*
556     * n == 1 deletes one or two zero rate entries
557     * n == 0 deletes oldest entry by force
558     *        and one or two zero rate entries
559     */
560
561    while (n < 3) {
562
563        if (ngx_queue_empty(&ctx->sh->queue)) {
564            return;
565        }
566
567        q = ngx_queue_last(&ctx->sh->queue);
568
569        lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
570
571        if (lr->count) {
572
573            /*
574             * There is not much sense in looking further,
575             * because we bump nodes on the lookup stage.
576             */
577
578            return;
579        }
580
581        if (n++ != 0) {
582
583            ms = (ngx_msec_int_t) (now - lr->last);
584            ms = ngx_abs(ms);
585
586            if (ms < 60000) {
587                return;
588            }
589
590            excess = lr->excess - ctx->rate * ms / 1000;
591
592            if (excess > 0) {
593                return;
594            }
595        }
596
597        ngx_queue_remove(q);
598
599        node = (ngx_rbtree_node_t *)
600                   ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
601
602        ngx_rbtree_delete(&ctx->sh->rbtree, node);
603
604        ngx_slab_free_locked(ctx->shpool, node);
605    }
606}
607
608
609static ngx_int_t
610ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
611{
612    ngx_http_limit_req_ctx_t  *octx = data;
613
614    size_t                     len;
615    ngx_http_limit_req_ctx_t  *ctx;
616
617    ctx = shm_zone->data;
618
619    if (octx) {
620        if (ctx->key.value.len != octx->key.value.len
621            || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
622                           ctx->key.value.len)
623               != 0)
624        {
625            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
626                          "limit_req \"%V\" uses the \"%V\" key "
627                          "while previously it used the \"%V\" key",
628                          &shm_zone->shm.name, &ctx->key.value,
629                          &octx->key.value);
630            return NGX_ERROR;
631        }
632
633        ctx->sh = octx->sh;
634        ctx->shpool = octx->shpool;
635
636        return NGX_OK;
637    }
638
639    ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
640
641    if (shm_zone->shm.exists) {
642        ctx->sh = ctx->shpool->data;
643
644        return NGX_OK;
645    }
646
647    ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
648    if (ctx->sh == NULL) {
649        return NGX_ERROR;
650    }
651
652    ctx->shpool->data = ctx->sh;
653
654    ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
655                    ngx_http_limit_req_rbtree_insert_value);
656
657    ngx_queue_init(&ctx->sh->queue);
658
659    len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
660
661    ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
662    if (ctx->shpool->log_ctx == NULL) {
663        return NGX_ERROR;
664    }
665
666    ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
667                &shm_zone->shm.name);
668
669    ctx->shpool->log_nomem = 0;
670
671    return NGX_OK;
672}
673
674
675static void *
676ngx_http_limit_req_create_conf(ngx_conf_t *cf)
677{
678    ngx_http_limit_req_conf_t  *conf;
679
680    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
681    if (conf == NULL) {
682        return NULL;
683    }
684
685    /*
686     * set by ngx_pcalloc():
687     *
688     *     conf->limits.elts = NULL;
689     */
690
691    conf->limit_log_level = NGX_CONF_UNSET_UINT;
692    conf->status_code = NGX_CONF_UNSET_UINT;
693
694    return conf;
695}
696
697
698static char *
699ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
700{
701    ngx_http_limit_req_conf_t *prev = parent;
702    ngx_http_limit_req_conf_t *conf = child;
703
704    if (conf->limits.elts == NULL) {
705        conf->limits = prev->limits;
706    }
707
708    ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level,
709                              NGX_LOG_ERR);
710
711    conf->delay_log_level = (conf->limit_log_level == NGX_LOG_INFO) ?
712                                NGX_LOG_INFO : conf->limit_log_level + 1;
713
714    ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
715                              NGX_HTTP_SERVICE_UNAVAILABLE);
716
717    return NGX_CONF_OK;
718}
719
720
721static char *
722ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
723{
724    u_char                            *p;
725    size_t                             len;
726    ssize_t                            size;
727    ngx_str_t                         *value, name, s;
728    ngx_int_t                          rate, scale;
729    ngx_uint_t                         i;
730    ngx_shm_zone_t                    *shm_zone;
731    ngx_http_limit_req_ctx_t          *ctx;
732    ngx_http_compile_complex_value_t   ccv;
733
734    value = cf->args->elts;
735
736    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
737    if (ctx == NULL) {
738        return NGX_CONF_ERROR;
739    }
740
741    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
742
743    ccv.cf = cf;
744    ccv.value = &value[1];
745    ccv.complex_value = &ctx->key;
746
747    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
748        return NGX_CONF_ERROR;
749    }
750
751    size = 0;
752    rate = 1;
753    scale = 1;
754    name.len = 0;
755
756    for (i = 2; i < cf->args->nelts; i++) {
757
758        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
759
760            name.data = value[i].data + 5;
761
762            p = (u_char *) ngx_strchr(name.data, ':');
763
764            if (p == NULL) {
765                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
766                                   "invalid zone size \"%V\"", &value[i]);
767                return NGX_CONF_ERROR;
768            }
769
770            name.len = p - name.data;
771
772            s.data = p + 1;
773            s.len = value[i].data + value[i].len - s.data;
774
775            size = ngx_parse_size(&s);
776
777            if (size == NGX_ERROR) {
778                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
779                                   "invalid zone size \"%V\"", &value[i]);
780                return NGX_CONF_ERROR;
781            }
782
783            if (size < (ssize_t) (8 * ngx_pagesize)) {
784                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
785                                   "zone \"%V\" is too small", &value[i]);
786                return NGX_CONF_ERROR;
787            }
788
789            continue;
790        }
791
792        if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
793
794            len = value[i].len;
795            p = value[i].data + len - 3;
796
797            if (ngx_strncmp(p, "r/s", 3) == 0) {
798                scale = 1;
799                len -= 3;
800
801            } else if (ngx_strncmp(p, "r/m", 3) == 0) {
802                scale = 60;
803                len -= 3;
804            }
805
806            rate = ngx_atoi(value[i].data + 5, len - 5);
807            if (rate <= 0) {
808                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
809                                   "invalid rate \"%V\"", &value[i]);
810                return NGX_CONF_ERROR;
811            }
812
813            continue;
814        }
815
816        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
817                           "invalid parameter \"%V\"", &value[i]);
818        return NGX_CONF_ERROR;
819    }
820
821    if (name.len == 0) {
822        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
823                           "\"%V\" must have \"zone\" parameter",
824                           &cmd->name);
825        return NGX_CONF_ERROR;
826    }
827
828    ctx->rate = rate * 1000 / scale;
829
830    shm_zone = ngx_shared_memory_add(cf, &name, size,
831                                     &ngx_http_limit_req_module);
832    if (shm_zone == NULL) {
833        return NGX_CONF_ERROR;
834    }
835
836    if (shm_zone->data) {
837        ctx = shm_zone->data;
838
839        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
840                           "%V \"%V\" is already bound to key \"%V\"",
841                           &cmd->name, &name, &ctx->key.value);
842        return NGX_CONF_ERROR;
843    }
844
845    shm_zone->init = ngx_http_limit_req_init_zone;
846    shm_zone->data = ctx;
847
848    return NGX_CONF_OK;
849}
850
851
852static char *
853ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
854{
855    ngx_http_limit_req_conf_t  *lrcf = conf;
856
857    ngx_int_t                    burst;
858    ngx_str_t                   *value, s;
859    ngx_uint_t                   i, nodelay;
860    ngx_shm_zone_t              *shm_zone;
861    ngx_http_limit_req_limit_t  *limit, *limits;
862
863    value = cf->args->elts;
864
865    shm_zone = NULL;
866    burst = 0;
867    nodelay = 0;
868
869    for (i = 1; i < cf->args->nelts; i++) {
870
871        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
872
873            s.len = value[i].len - 5;
874            s.data = value[i].data + 5;
875
876            shm_zone = ngx_shared_memory_add(cf, &s, 0,
877                                             &ngx_http_limit_req_module);
878            if (shm_zone == NULL) {
879                return NGX_CONF_ERROR;
880            }
881
882            continue;
883        }
884
885        if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
886
887            burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
888            if (burst <= 0) {
889                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
890                                   "invalid burst rate \"%V\"", &value[i]);
891                return NGX_CONF_ERROR;
892            }
893
894            continue;
895        }
896
897        if (ngx_strcmp(value[i].data, "nodelay") == 0) {
898            nodelay = 1;
899            continue;
900        }
901
902        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
903                           "invalid parameter \"%V\"", &value[i]);
904        return NGX_CONF_ERROR;
905    }
906
907    if (shm_zone == NULL) {
908        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
909                           "\"%V\" must have \"zone\" parameter",
910                           &cmd->name);
911        return NGX_CONF_ERROR;
912    }
913
914    limits = lrcf->limits.elts;
915
916    if (limits == NULL) {
917        if (ngx_array_init(&lrcf->limits, cf->pool, 1,
918                           sizeof(ngx_http_limit_req_limit_t))
919            != NGX_OK)
920        {
921            return NGX_CONF_ERROR;
922        }
923    }
924
925    for (i = 0; i < lrcf->limits.nelts; i++) {
926        if (shm_zone == limits[i].shm_zone) {
927            return "is duplicate";
928        }
929    }
930
931    limit = ngx_array_push(&lrcf->limits);
932    if (limit == NULL) {
933        return NGX_CONF_ERROR;
934    }
935
936    limit->shm_zone = shm_zone;
937    limit->burst = burst * 1000;
938    limit->nodelay = nodelay;
939
940    return NGX_CONF_OK;
941}
942
943
944static ngx_int_t
945ngx_http_limit_req_init(ngx_conf_t *cf)
946{
947    ngx_http_handler_pt        *h;
948    ngx_http_core_main_conf_t  *cmcf;
949
950    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
951
952    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
953    if (h == NULL) {
954        return NGX_ERROR;
955    }
956
957    *h = ngx_http_limit_req_handler;
958
959    return NGX_OK;
960}
961