mirror of
https://github.com/nginx/nginx.git
synced 2025-06-07 09:42:39 +08:00
Limit req: support for multiple "limit_req" limits.
This commit is contained in:
parent
73d0b6a721
commit
b06200f3dc
@ -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_request_t *r)
|
||||
|
||||
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(ngx_rbtree_node_t *temp,
|
||||
|
||||
|
||||
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_req_conf_t *lrcf, ngx_uint_t hash,
|
||||
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_req_conf_t *lrcf, ngx_uint_t hash,
|
||||
|
||||
*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_req_conf_t *lrcf, ngx_uint_t hash,
|
||||
|
||||
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_req_ctx_t *ctx, ngx_uint_t n)
|
||||
|
||||
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_t *cf)
|
||||
/*
|
||||
* 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 *cf, void *parent, void *child)
|
||||
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_command_t *cmd, void *conf)
|
||||
{
|
||||
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_command_t *cmd, void *conf)
|
||||
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_command_t *cmd, void *conf)
|
||||
}
|
||||
|
||||
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_command_t *cmd, void *conf)
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user