~ubuntu-branches/ubuntu/trusty/nginx/trusty-proposed

« back to all changes in this revision

Viewing changes to src/http/modules/ngx_http_limit_req_module.c

  • Committer: Package Import Robot
  • Author(s): Kartik Mistry
  • Date: 2013-04-25 12:51:45 UTC
  • mfrom: (1.3.28)
  • mto: (1.3.29) (15.1.2 experimental)
  • mto: This revision was merged to the branch mainline in revision 64.
  • Revision ID: package-import@ubuntu.com-20130425125145-ugl0wor6bq0u5eae
Tags: upstream-1.4.0
ImportĀ upstreamĀ versionĀ 1.4.0

Show diffs side-by-side

added added

removed removed

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