comparison src/http/modules/ngx_http_limit_req_module.c @ 4420:9ce48f9eb85b

Limit req: support for multiple "limit_req" limits.
author Valentin Bartenev <vbart@nginx.com>
date Mon, 30 Jan 2012 10:17:56 +0000
parents 7084faa7a4b4
children aacd7356c197
comparison
equal deleted inserted replaced
4419:7084faa7a4b4 4420:9ce48f9eb85b
16 u_short len; 16 u_short len;
17 ngx_queue_t queue; 17 ngx_queue_t queue;
18 ngx_msec_t last; 18 ngx_msec_t last;
19 /* integer value, 1 corresponds to 0.001 r/s */ 19 /* integer value, 1 corresponds to 0.001 r/s */
20 ngx_uint_t excess; 20 ngx_uint_t excess;
21 ngx_uint_t count;
21 u_char data[1]; 22 u_char data[1];
22 } ngx_http_limit_req_node_t; 23 } ngx_http_limit_req_node_t;
23 24
24 25
25 typedef struct { 26 typedef struct {
34 ngx_slab_pool_t *shpool; 35 ngx_slab_pool_t *shpool;
35 /* integer value, 1 corresponds to 0.001 r/s */ 36 /* integer value, 1 corresponds to 0.001 r/s */
36 ngx_uint_t rate; 37 ngx_uint_t rate;
37 ngx_int_t index; 38 ngx_int_t index;
38 ngx_str_t var; 39 ngx_str_t var;
40 ngx_http_limit_req_node_t *node;
39 } ngx_http_limit_req_ctx_t; 41 } ngx_http_limit_req_ctx_t;
40 42
41 43
42 typedef struct { 44 typedef struct {
43 ngx_shm_zone_t *shm_zone; 45 ngx_shm_zone_t *shm_zone;
44 /* integer value, 1 corresponds to 0.001 r/s */ 46 /* integer value, 1 corresponds to 0.001 r/s */
45 ngx_uint_t burst; 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;
46 ngx_uint_t limit_log_level; 54 ngx_uint_t limit_log_level;
47 ngx_uint_t delay_log_level; 55 ngx_uint_t delay_log_level;
48
49 ngx_uint_t nodelay; /* unsigned nodelay:1 */
50 } ngx_http_limit_req_conf_t; 56 } ngx_http_limit_req_conf_t;
51 57
52 58
53 static void ngx_http_limit_req_delay(ngx_http_request_t *r); 59 static void ngx_http_limit_req_delay(ngx_http_request_t *r);
54 static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf, 60 static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit,
55 ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep); 61 ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep,
62 ngx_uint_t account);
63 static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits,
64 ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit);
56 static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, 65 static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
57 ngx_uint_t n); 66 ngx_uint_t n);
58 67
59 static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf); 68 static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
60 static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, 69 static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
134 143
135 144
136 static ngx_int_t 145 static ngx_int_t
137 ngx_http_limit_req_handler(ngx_http_request_t *r) 146 ngx_http_limit_req_handler(ngx_http_request_t *r)
138 { 147 {
139 size_t len; 148 size_t len;
140 uint32_t hash; 149 uint32_t hash;
141 ngx_int_t rc; 150 ngx_int_t rc;
142 ngx_uint_t excess; 151 ngx_uint_t n, excess;
143 ngx_http_variable_value_t *vv; 152 ngx_msec_t delay;
144 ngx_http_limit_req_ctx_t *ctx; 153 ngx_http_variable_value_t *vv;
145 ngx_http_limit_req_conf_t *lrcf; 154 ngx_http_limit_req_ctx_t *ctx;
155 ngx_http_limit_req_conf_t *lrcf;
156 ngx_http_limit_req_limit_t *limit, *limits;
146 157
147 if (r->main->limit_req_set) { 158 if (r->main->limit_req_set) {
148 return NGX_DECLINED; 159 return NGX_DECLINED;
149 } 160 }
150 161
151 lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module); 162 lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
152 163 limits = lrcf->limits.elts;
153 if (lrcf->shm_zone == NULL) { 164
165 excess = 0;
166
167 rc = NGX_DECLINED;
168
169 for (n = 0; n < lrcf->limits.nelts; n++) {
170
171 limit = &limits[n];
172
173 ctx = limit->shm_zone->data;
174
175 vv = ngx_http_get_indexed_variable(r, ctx->index);
176
177 if (vv == NULL || vv->not_found) {
178 continue;
179 }
180
181 len = vv->len;
182
183 if (len == 0) {
184 continue;
185 }
186
187 if (len > 65535) {
188 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
189 "the value of the \"%V\" variable "
190 "is more than 65535 bytes: \"%v\"",
191 &ctx->var, vv);
192 continue;
193 }
194
195 hash = ngx_crc32_short(vv->data, len);
196
197 ngx_shmtx_lock(&ctx->shpool->mutex);
198
199 rc = ngx_http_limit_req_lookup(limit, hash, vv->data, len, &excess,
200 (n == lrcf->limits.nelts - 1));
201
202 ngx_shmtx_unlock(&ctx->shpool->mutex);
203
204 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
205 "limit_req[%ui]: %i %ui.%03ui",
206 n, rc, excess / 1000, excess % 1000);
207
208 if (rc != NGX_AGAIN) {
209 break;
210 }
211 }
212
213 if (rc == NGX_DECLINED) {
154 return NGX_DECLINED; 214 return NGX_DECLINED;
155 } 215 }
156 216
157 ctx = lrcf->shm_zone->data;
158
159 vv = ngx_http_get_indexed_variable(r, ctx->index);
160
161 if (vv == NULL || vv->not_found) {
162 return NGX_DECLINED;
163 }
164
165 len = vv->len;
166
167 if (len == 0) {
168 return NGX_DECLINED;
169 }
170
171 if (len > 65535) {
172 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
173 "the value of the \"%V\" variable "
174 "is more than 65535 bytes: \"%v\"",
175 &ctx->var, vv);
176 return NGX_DECLINED;
177 }
178
179 r->main->limit_req_set = 1; 217 r->main->limit_req_set = 1;
180
181 hash = ngx_crc32_short(vv->data, len);
182
183 ngx_shmtx_lock(&ctx->shpool->mutex);
184
185 rc = ngx_http_limit_req_lookup(lrcf, hash, vv->data, len, &excess);
186
187 ngx_shmtx_unlock(&ctx->shpool->mutex);
188
189 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
190 "limit_req: %i %ui.%03ui", rc, excess / 1000, excess % 1000);
191
192 if (rc == NGX_OK) {
193 return NGX_DECLINED;
194 }
195 218
196 if (rc == NGX_BUSY || rc == NGX_ERROR) { 219 if (rc == NGX_BUSY || rc == NGX_ERROR) {
197 220
198 if (rc == NGX_BUSY) { 221 if (rc == NGX_BUSY) {
199 ngx_log_error(lrcf->limit_log_level, r->connection->log, 0, 222 ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
200 "limiting requests, excess: %ui.%03ui by zone \"%V\"", 223 "limiting requests, excess: %ui.%03ui by zone \"%V\"",
201 excess / 1000, excess % 1000, 224 excess / 1000, excess % 1000,
202 &lrcf->shm_zone->shm.name); 225 &limit->shm_zone->shm.name);
226 }
227
228 while (n--) {
229 ctx = limits[n].shm_zone->data;
230
231 if (ctx->node == NULL) {
232 continue;
233 }
234
235 ngx_shmtx_lock(&ctx->shpool->mutex);
236
237 ctx->node->count--;
238
239 ngx_shmtx_unlock(&ctx->shpool->mutex);
240
241 ctx->node = NULL;
203 } 242 }
204 243
205 return NGX_HTTP_SERVICE_UNAVAILABLE; 244 return NGX_HTTP_SERVICE_UNAVAILABLE;
206 } 245 }
207 246
208 /* rc == NGX_AGAIN */ 247 /* rc == NGX_AGAIN || rc == NGX_OK */
209 248
210 if (lrcf->nodelay) { 249 if (rc == NGX_AGAIN) {
250 excess = 0;
251 }
252
253 delay = ngx_http_limit_req_account(limits, n, &excess, &limit);
254
255 if (!delay) {
211 return NGX_DECLINED; 256 return NGX_DECLINED;
212 } 257 }
213 258
214 ngx_log_error(lrcf->delay_log_level, r->connection->log, 0, 259 ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
215 "delaying request, excess: %ui.%03ui, by zone \"%V\"", 260 "delaying request, excess: %ui.%03ui, by zone \"%V\"",
216 excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name); 261 excess / 1000, excess % 1000, &limit->shm_zone->shm.name);
217 262
218 if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { 263 if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
219 return NGX_HTTP_INTERNAL_SERVER_ERROR; 264 return NGX_HTTP_INTERNAL_SERVER_ERROR;
220 } 265 }
221 266
222 r->read_event_handler = ngx_http_test_reading; 267 r->read_event_handler = ngx_http_test_reading;
223 r->write_event_handler = ngx_http_limit_req_delay; 268 r->write_event_handler = ngx_http_limit_req_delay;
224 ngx_add_timer(r->connection->write, 269 ngx_add_timer(r->connection->write, delay);
225 (ngx_msec_t) excess * 1000 / ctx->rate);
226 270
227 return NGX_AGAIN; 271 return NGX_AGAIN;
228 } 272 }
229 273
230 274
301 ngx_rbt_red(node); 345 ngx_rbt_red(node);
302 } 346 }
303 347
304 348
305 static ngx_int_t 349 static ngx_int_t
306 ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf, ngx_uint_t hash, 350 ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash,
307 u_char *data, size_t len, ngx_uint_t *ep) 351 u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account)
308 { 352 {
309 size_t size; 353 size_t size;
310 ngx_int_t rc, excess; 354 ngx_int_t rc, excess;
311 ngx_time_t *tp; 355 ngx_time_t *tp;
312 ngx_msec_t now; 356 ngx_msec_t now;
316 ngx_http_limit_req_node_t *lr; 360 ngx_http_limit_req_node_t *lr;
317 361
318 tp = ngx_timeofday(); 362 tp = ngx_timeofday();
319 now = (ngx_msec_t) (tp->sec * 1000 + tp->msec); 363 now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
320 364
321 ctx = lrcf->shm_zone->data; 365 ctx = limit->shm_zone->data;
322 366
323 node = ctx->sh->rbtree.root; 367 node = ctx->sh->rbtree.root;
324 sentinel = ctx->sh->rbtree.sentinel; 368 sentinel = ctx->sh->rbtree.sentinel;
325 369
326 while (node != sentinel) { 370 while (node != sentinel) {
354 excess = 0; 398 excess = 0;
355 } 399 }
356 400
357 *ep = excess; 401 *ep = excess;
358 402
359 if ((ngx_uint_t) excess > lrcf->burst) { 403 if ((ngx_uint_t) excess > limit->burst) {
360 return NGX_BUSY; 404 return NGX_BUSY;
361 } 405 }
362 406
363 lr->excess = excess; 407 if (account) {
364 lr->last = now; 408 lr->excess = excess;
365 409 lr->last = now;
366 if (excess) { 410 return NGX_OK;
367 return NGX_AGAIN;
368 } 411 }
369 412
370 return NGX_OK; 413 lr->count++;
414
415 ctx->node = lr;
416
417 return NGX_AGAIN;
371 } 418 }
372 419
373 node = (rc < 0) ? node->left : node->right; 420 node = (rc < 0) ? node->left : node->right;
374 421
375 } while (node != sentinel && hash == node->key); 422 } while (node != sentinel && hash == node->key);
404 451
405 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); 452 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
406 453
407 lr->len = (u_char) len; 454 lr->len = (u_char) len;
408 lr->excess = 0; 455 lr->excess = 0;
409 lr->last = now;
410 456
411 ngx_memcpy(lr->data, data, len); 457 ngx_memcpy(lr->data, data, len);
412 458
413 return NGX_OK; 459 if (account) {
460 lr->last = now;
461 lr->count = 0;
462 return NGX_OK;
463 }
464
465 lr->last = 0;
466 lr->count = 1;
467
468 ctx->node = lr;
469
470 return NGX_AGAIN;
471 }
472
473
474 static ngx_msec_t
475 ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n,
476 ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit)
477 {
478 ngx_int_t excess;
479 ngx_time_t *tp;
480 ngx_msec_t now, delay, max_delay;
481 ngx_msec_int_t ms;
482 ngx_http_limit_req_ctx_t *ctx;
483 ngx_http_limit_req_node_t *lr;
484
485 excess = *ep;
486
487 if (excess == 0 || (*limit)->nodelay) {
488 max_delay = 0;
489
490 } else {
491 ctx = (*limit)->shm_zone->data;
492 max_delay = excess * 1000 / ctx->rate;
493 }
494
495 while (n--) {
496 ctx = limits[n].shm_zone->data;
497 lr = ctx->node;
498
499 if (lr == NULL) {
500 continue;
501 }
502
503 ngx_shmtx_lock(&ctx->shpool->mutex);
504
505 tp = ngx_timeofday();
506
507 now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
508 ms = (ngx_msec_int_t) (now - lr->last);
509
510 excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
511
512 if (excess < 0) {
513 excess = 0;
514 }
515
516 lr->last = now;
517 lr->excess = excess;
518 lr->count--;
519
520 ngx_shmtx_unlock(&ctx->shpool->mutex);
521
522 ctx->node = NULL;
523
524 if (limits[n].nodelay) {
525 continue;
526 }
527
528 delay = excess * 1000 / ctx->rate;
529
530 if (delay > max_delay) {
531 max_delay = delay;
532 *ep = excess;
533 *limit = &limits[n];
534 }
535 }
536
537 return max_delay;
414 } 538 }
415 539
416 540
417 static void 541 static void
418 ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n) 542 ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
443 567
444 q = ngx_queue_last(&ctx->sh->queue); 568 q = ngx_queue_last(&ctx->sh->queue);
445 569
446 lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); 570 lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
447 571
572 if (lr->count) {
573
574 /*
575 * There is not much sense in looking further,
576 * because we bump nodes on the lookup stage.
577 */
578
579 return;
580 }
581
448 if (n++ != 0) { 582 if (n++ != 0) {
449 583
450 ms = (ngx_msec_int_t) (now - lr->last); 584 ms = (ngx_msec_int_t) (now - lr->last);
451 ms = ngx_abs(ms); 585 ms = ngx_abs(ms);
452 586
543 } 677 }
544 678
545 /* 679 /*
546 * set by ngx_pcalloc(): 680 * set by ngx_pcalloc():
547 * 681 *
548 * conf->shm_zone = NULL; 682 * conf->limits.elts = NULL;
549 * conf->burst = 0;
550 * conf->nodelay = 0;
551 */ 683 */
552 684
553 conf->limit_log_level = NGX_CONF_UNSET_UINT; 685 conf->limit_log_level = NGX_CONF_UNSET_UINT;
554 686
555 return conf; 687 return conf;
560 ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child) 692 ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
561 { 693 {
562 ngx_http_limit_req_conf_t *prev = parent; 694 ngx_http_limit_req_conf_t *prev = parent;
563 ngx_http_limit_req_conf_t *conf = child; 695 ngx_http_limit_req_conf_t *conf = child;
564 696
565 if (conf->shm_zone == NULL) { 697 if (conf->limits.elts == NULL) {
566 conf->shm_zone = prev->shm_zone; 698 conf->limits = prev->limits;
567 conf->burst = prev->burst;
568 conf->nodelay = prev->nodelay;
569 } 699 }
570 700
571 ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level, 701 ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level,
572 NGX_LOG_ERR); 702 NGX_LOG_ERR);
573 703
724 static char * 854 static char *
725 ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 855 ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
726 { 856 {
727 ngx_http_limit_req_conf_t *lrcf = conf; 857 ngx_http_limit_req_conf_t *lrcf = conf;
728 858
729 ngx_int_t burst; 859 ngx_int_t burst;
730 ngx_str_t *value, s; 860 ngx_str_t *value, s;
731 ngx_uint_t i; 861 ngx_uint_t i, nodelay;
732 862 ngx_shm_zone_t *shm_zone;
733 if (lrcf->shm_zone) { 863 ngx_http_limit_req_limit_t *limit, *limits;
734 return "is duplicate";
735 }
736 864
737 value = cf->args->elts; 865 value = cf->args->elts;
738 866
867 shm_zone = NULL;
739 burst = 0; 868 burst = 0;
869 nodelay = 0;
740 870
741 for (i = 1; i < cf->args->nelts; i++) { 871 for (i = 1; i < cf->args->nelts; i++) {
742 872
743 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) { 873 if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
744 874
745 s.len = value[i].len - 5; 875 s.len = value[i].len - 5;
746 s.data = value[i].data + 5; 876 s.data = value[i].data + 5;
747 877
748 lrcf->shm_zone = ngx_shared_memory_add(cf, &s, 0, 878 shm_zone = ngx_shared_memory_add(cf, &s, 0,
749 &ngx_http_limit_req_module); 879 &ngx_http_limit_req_module);
750 if (lrcf->shm_zone == NULL) { 880 if (shm_zone == NULL) {
751 return NGX_CONF_ERROR; 881 return NGX_CONF_ERROR;
752 } 882 }
753 883
754 continue; 884 continue;
755 } 885 }
765 895
766 continue; 896 continue;
767 } 897 }
768 898
769 if (ngx_strncmp(value[i].data, "nodelay", 7) == 0) { 899 if (ngx_strncmp(value[i].data, "nodelay", 7) == 0) {
770 lrcf->nodelay = 1; 900 nodelay = 1;
771 continue; 901 continue;
772 } 902 }
773 903
774 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 904 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
775 "invalid parameter \"%V\"", &value[i]); 905 "invalid parameter \"%V\"", &value[i]);
776 return NGX_CONF_ERROR; 906 return NGX_CONF_ERROR;
777 } 907 }
778 908
779 if (lrcf->shm_zone == NULL) { 909 if (shm_zone == NULL) {
780 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 910 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
781 "\"%V\" must have \"zone\" parameter", 911 "\"%V\" must have \"zone\" parameter",
782 &cmd->name); 912 &cmd->name);
783 return NGX_CONF_ERROR; 913 return NGX_CONF_ERROR;
784 } 914 }
785 915
786 if (lrcf->shm_zone->data == NULL) { 916 if (shm_zone->data == NULL) {
787 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 917 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
788 "unknown limit_req_zone \"%V\"", 918 "unknown limit_req_zone \"%V\"",
789 &lrcf->shm_zone->shm.name); 919 &shm_zone->shm.name);
790 return NGX_CONF_ERROR; 920 return NGX_CONF_ERROR;
791 } 921 }
792 922
793 lrcf->burst = burst * 1000; 923 limits = lrcf->limits.elts;
924
925 if (limits == NULL) {
926 if (ngx_array_init(&lrcf->limits, cf->pool, 1,
927 sizeof(ngx_http_limit_req_limit_t))
928 != NGX_OK)
929 {
930 return NGX_CONF_ERROR;
931 }
932 }
933
934 for (i = 0; i < lrcf->limits.nelts; i++) {
935 if (shm_zone == limits[i].shm_zone) {
936 return "is duplicate";
937 }
938 }
939
940 limit = ngx_array_push(&lrcf->limits);
941
942 limit->shm_zone = shm_zone;
943 limit->burst = burst * 1000;
944 limit->nodelay = nodelay;
794 945
795 return NGX_CONF_OK; 946 return NGX_CONF_OK;
796 } 947 }
797 948
798 949