# HG changeset patch # User Valentin Bartenev # Date 1327918676 0 # Node ID 9ce48f9eb85b8e301366916af78c0d6aaa4d2e01 # Parent 7084faa7a4b4c45335bf06b8765a38549dc43b0e Limit req: support for multiple "limit_req" limits. diff --git a/src/http/modules/ngx_http_limit_req_module.c b/src/http/modules/ngx_http_limit_req_module.c --- a/src/http/modules/ngx_http_limit_req_module.c +++ b/src/http/modules/ngx_http_limit_req_module.c @@ -18,6 +18,7 @@ typedef struct { ngx_msec_t last; /* integer value, 1 corresponds to 0.001 r/s */ ngx_uint_t excess; + ngx_uint_t count; u_char data[1]; } ngx_http_limit_req_node_t; @@ -36,6 +37,7 @@ typedef struct { ngx_uint_t rate; ngx_int_t index; ngx_str_t var; + ngx_http_limit_req_node_t *node; } ngx_http_limit_req_ctx_t; @@ -43,16 +45,23 @@ typedef struct { ngx_shm_zone_t *shm_zone; /* integer value, 1 corresponds to 0.001 r/s */ ngx_uint_t burst; + ngx_uint_t nodelay; /* unsigned nodelay:1 */ +} ngx_http_limit_req_limit_t; + + +typedef struct { + ngx_array_t limits; ngx_uint_t limit_log_level; ngx_uint_t delay_log_level; - - ngx_uint_t nodelay; /* unsigned nodelay:1 */ } ngx_http_limit_req_conf_t; static void ngx_http_limit_req_delay(ngx_http_request_t *r); -static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf, - ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep); +static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, + ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep, + ngx_uint_t account); +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); static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n); @@ -136,84 +145,120 @@ ngx_module_t ngx_http_limit_req_module static ngx_int_t ngx_http_limit_req_handler(ngx_http_request_t *r) { - size_t len; - uint32_t hash; - ngx_int_t rc; - ngx_uint_t excess; - ngx_http_variable_value_t *vv; - ngx_http_limit_req_ctx_t *ctx; - ngx_http_limit_req_conf_t *lrcf; + size_t len; + uint32_t hash; + ngx_int_t rc; + ngx_uint_t n, excess; + ngx_msec_t delay; + ngx_http_variable_value_t *vv; + ngx_http_limit_req_ctx_t *ctx; + ngx_http_limit_req_conf_t *lrcf; + ngx_http_limit_req_limit_t *limit, *limits; if (r->main->limit_req_set) { return NGX_DECLINED; } lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module); + limits = lrcf->limits.elts; - if (lrcf->shm_zone == NULL) { - return NGX_DECLINED; + excess = 0; + + rc = NGX_DECLINED; + + for (n = 0; n < lrcf->limits.nelts; n++) { + + limit = &limits[n]; + + ctx = limit->shm_zone->data; + + vv = ngx_http_get_indexed_variable(r, ctx->index); + + if (vv == NULL || vv->not_found) { + continue; + } + + len = vv->len; + + if (len == 0) { + continue; + } + + if (len > 65535) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "the value of the \"%V\" variable " + "is more than 65535 bytes: \"%v\"", + &ctx->var, vv); + continue; + } + + hash = ngx_crc32_short(vv->data, len); + + ngx_shmtx_lock(&ctx->shpool->mutex); + + rc = ngx_http_limit_req_lookup(limit, hash, vv->data, len, &excess, + (n == lrcf->limits.nelts - 1)); + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "limit_req[%ui]: %i %ui.%03ui", + n, rc, excess / 1000, excess % 1000); + + if (rc != NGX_AGAIN) { + break; + } } - ctx = lrcf->shm_zone->data; - - vv = ngx_http_get_indexed_variable(r, ctx->index); - - if (vv == NULL || vv->not_found) { - return NGX_DECLINED; - } - - len = vv->len; - - if (len == 0) { - return NGX_DECLINED; - } - - if (len > 65535) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "the value of the \"%V\" variable " - "is more than 65535 bytes: \"%v\"", - &ctx->var, vv); + if (rc == NGX_DECLINED) { return NGX_DECLINED; } r->main->limit_req_set = 1; - hash = ngx_crc32_short(vv->data, len); - - ngx_shmtx_lock(&ctx->shpool->mutex); - - rc = ngx_http_limit_req_lookup(lrcf, hash, vv->data, len, &excess); - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "limit_req: %i %ui.%03ui", rc, excess / 1000, excess % 1000); - - if (rc == NGX_OK) { - return NGX_DECLINED; - } - if (rc == NGX_BUSY || rc == NGX_ERROR) { if (rc == NGX_BUSY) { ngx_log_error(lrcf->limit_log_level, r->connection->log, 0, "limiting requests, excess: %ui.%03ui by zone \"%V\"", excess / 1000, excess % 1000, - &lrcf->shm_zone->shm.name); + &limit->shm_zone->shm.name); + } + + while (n--) { + ctx = limits[n].shm_zone->data; + + if (ctx->node == NULL) { + continue; + } + + ngx_shmtx_lock(&ctx->shpool->mutex); + + ctx->node->count--; + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + ctx->node = NULL; } return NGX_HTTP_SERVICE_UNAVAILABLE; } - /* rc == NGX_AGAIN */ + /* rc == NGX_AGAIN || rc == NGX_OK */ - if (lrcf->nodelay) { + if (rc == NGX_AGAIN) { + excess = 0; + } + + delay = ngx_http_limit_req_account(limits, n, &excess, &limit); + + if (!delay) { return NGX_DECLINED; } ngx_log_error(lrcf->delay_log_level, r->connection->log, 0, "delaying request, excess: %ui.%03ui, by zone \"%V\"", - excess / 1000, excess % 1000, &lrcf->shm_zone->shm.name); + excess / 1000, excess % 1000, &limit->shm_zone->shm.name); if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -221,8 +266,7 @@ ngx_http_limit_req_handler(ngx_http_requ r->read_event_handler = ngx_http_test_reading; r->write_event_handler = ngx_http_limit_req_delay; - ngx_add_timer(r->connection->write, - (ngx_msec_t) excess * 1000 / ctx->rate); + ngx_add_timer(r->connection->write, delay); return NGX_AGAIN; } @@ -303,8 +347,8 @@ ngx_http_limit_req_rbtree_insert_value(n static ngx_int_t -ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf, ngx_uint_t hash, - u_char *data, size_t len, ngx_uint_t *ep) +ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, + u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account) { size_t size; ngx_int_t rc, excess; @@ -318,7 +362,7 @@ ngx_http_limit_req_lookup(ngx_http_limit tp = ngx_timeofday(); now = (ngx_msec_t) (tp->sec * 1000 + tp->msec); - ctx = lrcf->shm_zone->data; + ctx = limit->shm_zone->data; node = ctx->sh->rbtree.root; sentinel = ctx->sh->rbtree.sentinel; @@ -356,18 +400,21 @@ ngx_http_limit_req_lookup(ngx_http_limit *ep = excess; - if ((ngx_uint_t) excess > lrcf->burst) { + if ((ngx_uint_t) excess > limit->burst) { return NGX_BUSY; } - lr->excess = excess; - lr->last = now; - - if (excess) { - return NGX_AGAIN; + if (account) { + lr->excess = excess; + lr->last = now; + return NGX_OK; } - return NGX_OK; + lr->count++; + + ctx->node = lr; + + return NGX_AGAIN; } node = (rc < 0) ? node->left : node->right; @@ -406,11 +453,88 @@ ngx_http_limit_req_lookup(ngx_http_limit lr->len = (u_char) len; lr->excess = 0; - lr->last = now; ngx_memcpy(lr->data, data, len); - return NGX_OK; + if (account) { + lr->last = now; + lr->count = 0; + return NGX_OK; + } + + lr->last = 0; + lr->count = 1; + + ctx->node = lr; + + return NGX_AGAIN; +} + + +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) +{ + ngx_int_t excess; + ngx_time_t *tp; + ngx_msec_t now, delay, max_delay; + ngx_msec_int_t ms; + ngx_http_limit_req_ctx_t *ctx; + ngx_http_limit_req_node_t *lr; + + excess = *ep; + + if (excess == 0 || (*limit)->nodelay) { + max_delay = 0; + + } else { + ctx = (*limit)->shm_zone->data; + max_delay = excess * 1000 / ctx->rate; + } + + while (n--) { + ctx = limits[n].shm_zone->data; + lr = ctx->node; + + if (lr == NULL) { + continue; + } + + ngx_shmtx_lock(&ctx->shpool->mutex); + + tp = ngx_timeofday(); + + now = (ngx_msec_t) (tp->sec * 1000 + tp->msec); + ms = (ngx_msec_int_t) (now - lr->last); + + excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; + + if (excess < 0) { + excess = 0; + } + + lr->last = now; + lr->excess = excess; + lr->count--; + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + ctx->node = NULL; + + if (limits[n].nodelay) { + continue; + } + + delay = excess * 1000 / ctx->rate; + + if (delay > max_delay) { + max_delay = delay; + *ep = excess; + *limit = &limits[n]; + } + } + + return max_delay; } @@ -445,6 +569,16 @@ ngx_http_limit_req_expire(ngx_http_limit lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue); + if (lr->count) { + + /* + * There is not much sense in looking further, + * because we bump nodes on the lookup stage. + */ + + return; + } + if (n++ != 0) { ms = (ngx_msec_int_t) (now - lr->last); @@ -545,9 +679,7 @@ ngx_http_limit_req_create_conf(ngx_conf_ /* * set by ngx_pcalloc(): * - * conf->shm_zone = NULL; - * conf->burst = 0; - * conf->nodelay = 0; + * conf->limits.elts = NULL; */ conf->limit_log_level = NGX_CONF_UNSET_UINT; @@ -562,10 +694,8 @@ ngx_http_limit_req_merge_conf(ngx_conf_t ngx_http_limit_req_conf_t *prev = parent; ngx_http_limit_req_conf_t *conf = child; - if (conf->shm_zone == NULL) { - conf->shm_zone = prev->shm_zone; - conf->burst = prev->burst; - conf->nodelay = prev->nodelay; + if (conf->limits.elts == NULL) { + conf->limits = prev->limits; } ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level, @@ -726,17 +856,17 @@ ngx_http_limit_req(ngx_conf_t *cf, ngx_c { ngx_http_limit_req_conf_t *lrcf = conf; - ngx_int_t burst; - ngx_str_t *value, s; - ngx_uint_t i; - - if (lrcf->shm_zone) { - return "is duplicate"; - } + ngx_int_t burst; + ngx_str_t *value, s; + ngx_uint_t i, nodelay; + ngx_shm_zone_t *shm_zone; + ngx_http_limit_req_limit_t *limit, *limits; value = cf->args->elts; + shm_zone = NULL; burst = 0; + nodelay = 0; for (i = 1; i < cf->args->nelts; i++) { @@ -745,9 +875,9 @@ ngx_http_limit_req(ngx_conf_t *cf, ngx_c s.len = value[i].len - 5; s.data = value[i].data + 5; - lrcf->shm_zone = ngx_shared_memory_add(cf, &s, 0, - &ngx_http_limit_req_module); - if (lrcf->shm_zone == NULL) { + shm_zone = ngx_shared_memory_add(cf, &s, 0, + &ngx_http_limit_req_module); + if (shm_zone == NULL) { return NGX_CONF_ERROR; } @@ -767,7 +897,7 @@ ngx_http_limit_req(ngx_conf_t *cf, ngx_c } if (ngx_strncmp(value[i].data, "nodelay", 7) == 0) { - lrcf->nodelay = 1; + nodelay = 1; continue; } @@ -776,21 +906,42 @@ ngx_http_limit_req(ngx_conf_t *cf, ngx_c return NGX_CONF_ERROR; } - if (lrcf->shm_zone == NULL) { + if (shm_zone == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%V\" must have \"zone\" parameter", &cmd->name); return NGX_CONF_ERROR; } - if (lrcf->shm_zone->data == NULL) { + if (shm_zone->data == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown limit_req_zone \"%V\"", - &lrcf->shm_zone->shm.name); + &shm_zone->shm.name); return NGX_CONF_ERROR; } - lrcf->burst = burst * 1000; + limits = lrcf->limits.elts; + + if (limits == NULL) { + if (ngx_array_init(&lrcf->limits, cf->pool, 1, + sizeof(ngx_http_limit_req_limit_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + + for (i = 0; i < lrcf->limits.nelts; i++) { + if (shm_zone == limits[i].shm_zone) { + return "is duplicate"; + } + } + + limit = ngx_array_push(&lrcf->limits); + + limit->shm_zone = shm_zone; + limit->burst = burst * 1000; + limit->nodelay = nodelay; return NGX_CONF_OK; }