summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/http/modules/ngx_http_upstream_sticky_module.c859
1 files changed, 767 insertions, 92 deletions
diff --git a/src/http/modules/ngx_http_upstream_sticky_module.c b/src/http/modules/ngx_http_upstream_sticky_module.c
index fa0d14816..75ebd3123 100644
--- a/src/http/modules/ngx_http_upstream_sticky_module.c
+++ b/src/http/modules/ngx_http_upstream_sticky_module.c
@@ -12,6 +12,52 @@
#define NGX_HTTP_STICKY_COOKIE_MAX_EXPIRES 2145916555
+#define ngx_http_upstream_sticky_sess_node(rbn, mb) \
+ (ngx_http_upstream_sticky_sess_node_t *) \
+ ((char *) (rbn) - offsetof(ngx_http_upstream_sticky_sess_node_t, mb))
+
+
+typedef union {
+ u_char md5[16];
+ ngx_uint_t hash;
+} ngx_http_upstream_sticky_sess_key_t;
+
+
+typedef struct {
+ ngx_rbtree_t rbtree;
+ ngx_rbtree_node_t sentinel;
+
+ ngx_rbtree_t exp_rbtree;
+ ngx_rbtree_node_t exp_sentinel;
+} ngx_http_upstream_sticky_sess_shared_t;
+
+
+typedef struct {
+ ngx_http_upstream_sticky_sess_shared_t *sh;
+ ngx_slab_pool_t *shpool;
+ ngx_str_t *host;
+
+ ngx_msec_t timeout;
+ ngx_event_t event;
+} ngx_http_upstream_sticky_sess_t;
+
+
+/* session data: mapping of session ID hash to server ID */
+typedef struct {
+ ngx_rbtree_node_t rbnode;
+ ngx_rbtree_node_t enode;
+
+ union {
+ u_char md5[16];
+ ngx_uint_t hash;
+ } u;
+
+ ngx_msec_t last;
+
+ u_char sid_len;
+ u_char sid[NGX_HTTP_UPSTREAM_SID_LEN];
+} ngx_http_upstream_sticky_sess_node_t;
+
/* per-upstream sticky configuration */
typedef struct {
@@ -19,6 +65,8 @@ typedef struct {
ngx_http_upstream_init_peer_pt original_init_peer;
ngx_array_t *lookup_vars; /* of ngx_int_t */
+ ngx_array_t *create_vars; /* of ngx_int_t */
+ ngx_shm_zone_t *shm_zone; /* sessions */
ngx_str_t cookie_name;
ngx_str_t cookie_domain;
@@ -52,7 +100,9 @@ static ngx_int_t ngx_http_upstream_sticky_init_peer(ngx_http_request_t *r,
ngx_http_upstream_srv_conf_t *us);
static ngx_int_t ngx_http_upstream_sticky_get_id(
ngx_http_upstream_sticky_srv_conf_t *stcf, ngx_http_request_t *r,
- ngx_str_t *id);
+ ngx_array_t *vars, ngx_str_t *id);
+static void ngx_http_upstream_sticky_sess_init_key(ngx_str_t *sess_id,
+ ngx_http_upstream_sticky_sess_key_t *key);
static ngx_int_t ngx_http_upstream_sticky_get_peer(ngx_peer_connection_t *pc,
void *data);
static void ngx_http_upstream_sticky_free_peer(ngx_peer_connection_t *pc,
@@ -71,9 +121,30 @@ static ngx_int_t ngx_http_upstream_sticky_cookie_insert(
ngx_peer_connection_t *pc, ngx_http_upstream_sticky_peer_data_t *stp);
+static ngx_http_upstream_sticky_sess_node_t *
+ ngx_http_upstream_sticky_sess_lookup(ngx_http_upstream_sticky_sess_t *sess,
+ ngx_http_upstream_sticky_sess_key_t *key);
+static ngx_http_upstream_sticky_sess_node_t *
+ ngx_http_upstream_sticky_sess_create(ngx_http_upstream_sticky_sess_t *sess,
+ ngx_http_upstream_sticky_sess_key_t *key, ngx_str_t *sid);
+static void ngx_http_upstream_sticky_sess_rbtree_insert_value(
+ ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
+ ngx_rbtree_node_t *sentinel);
+static void ngx_http_upstream_sticky_sess_timer_handler(ngx_event_t *ev);
+static ngx_msec_t ngx_http_upstream_sticky_sess_expire(
+ ngx_http_upstream_sticky_sess_t *sess, ngx_uint_t force);
+static ngx_int_t ngx_http_upstream_sticky_sess_init_zone(
+ ngx_shm_zone_t *shm_zone, void *data);
+
+
static void *ngx_http_upstream_sticky_create_conf(ngx_conf_t *cf);
static char *ngx_http_upstream_sticky(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+static char *ngx_http_upstream_sticky_cookie(ngx_conf_t *cf,
+ ngx_http_upstream_sticky_srv_conf_t *stcf);
+static char *ngx_http_upstream_sticky_learn(ngx_conf_t *cf,
+ ngx_http_upstream_sticky_srv_conf_t *stcf,
+ ngx_http_upstream_srv_conf_t *us);
static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT";
@@ -193,7 +264,7 @@ ngx_http_upstream_sticky_init_peer(ngx_http_request_t *r,
u->peer.save_session = ngx_http_upstream_sticky_save_session;
#endif
- ngx_http_upstream_sticky_get_id(stcf, r, &stp->id);
+ ngx_http_upstream_sticky_get_id(stcf, r, stcf->lookup_vars, &stp->id);
return NGX_OK;
}
@@ -201,15 +272,15 @@ ngx_http_upstream_sticky_init_peer(ngx_http_request_t *r,
static ngx_int_t
ngx_http_upstream_sticky_get_id(ngx_http_upstream_sticky_srv_conf_t *stcf,
- ngx_http_request_t *r, ngx_str_t *id)
+ ngx_http_request_t *r, ngx_array_t *vars, ngx_str_t *id)
{
ngx_int_t *index;
ngx_uint_t i;
ngx_http_variable_value_t *v;
- index = stcf->lookup_vars->elts;
+ index = vars->elts;
- for (i = 0; i < stcf->lookup_vars->nelts; i++) {
+ for (i = 0; i < vars->nelts; i++) {
v = ngx_http_get_flushed_variable(r, index[i]);
@@ -232,14 +303,62 @@ ngx_http_upstream_sticky_get_id(ngx_http_upstream_sticky_srv_conf_t *stcf,
}
+static ngx_inline void
+ngx_http_upstream_sticky_sess_init_key(ngx_str_t *sess_id,
+ ngx_http_upstream_sticky_sess_key_t *key)
+{
+ ngx_md5_t md5;
+
+ ngx_md5_init(&md5);
+ ngx_md5_update(&md5, sess_id->data, sess_id->len);
+ ngx_md5_final(key->md5, &md5);
+}
+
+
static ngx_int_t
ngx_http_upstream_sticky_get_peer(ngx_peer_connection_t *pc, void *data)
{
ngx_http_upstream_sticky_peer_data_t *stp = data;
- ngx_int_t rc;
+ ngx_int_t rc;
+ ngx_str_t sid;
+ ngx_http_upstream_sticky_sess_t *sess;
+ ngx_http_upstream_sticky_sess_key_t key;
+ ngx_http_upstream_sticky_sess_node_t *sn;
+ u_char sid_data[NGX_HTTP_UPSTREAM_SID_LEN];
+
+ if (pc->hint == NULL && stp->conf->shm_zone && stp->id.len) {
+
+ /* request holds session ID, extract server ID from session */
+
+ sess = stp->conf->shm_zone->data;
+
+ ngx_http_upstream_sticky_sess_init_key(&stp->id, &key);
+
+ ngx_shmtx_lock(&sess->shpool->mutex);
+
+ sn = ngx_http_upstream_sticky_sess_lookup(sess, &key);
+ if (sn == NULL) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "sticky: session \"%V\" not found", &stp->id);
+
+ } else {
+ ngx_memcpy(sid_data, sn->sid, sn->sid_len);
+ sid.len = sn->sid_len;
+ sid.data = sid_data;
+ pc->hint = &sid;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "sticky: session \"%V\", SID \"%V\"",
+ &stp->id, &sid);
+ }
+
+ ngx_shmtx_unlock(&sess->shpool->mutex);
+
+ } else if (pc->hint == NULL && stp->id.len) {
+
+ /* request holds server ID */
- if (pc->hint == NULL && stp->id.len) {
pc->hint = &stp->id;
}
@@ -269,6 +388,101 @@ ngx_http_upstream_sticky_free_peer(ngx_peer_connection_t *pc, void *data,
{
ngx_http_upstream_sticky_peer_data_t *stp = data;
+ ngx_str_t sess_id;
+ ngx_msec_t now;
+ ngx_time_t *tp;
+ ngx_uint_t create;
+ ngx_http_request_t *r;
+ ngx_http_upstream_sticky_sess_t *sess;
+ ngx_http_upstream_sticky_sess_key_t key;
+ ngx_http_upstream_sticky_srv_conf_t *stcf;
+ ngx_http_upstream_sticky_sess_node_t *sn;
+
+ if (state & (NGX_PEER_FAILED|NGX_PEER_NEXT)) {
+ goto done;
+ }
+
+ stcf = stp->conf;
+
+ if (stcf->shm_zone == NULL) {
+ goto done;
+ }
+
+ if (pc->sid == NULL) {
+ ngx_log_error(NGX_LOG_WARN, pc->log, 0,
+ "balancer does not support sticky");
+ goto done;
+ }
+
+ r = stp->request;
+
+ sess = stcf->shm_zone->data;
+
+ if (ngx_http_upstream_sticky_get_id(stcf, r, stcf->create_vars, &sess_id)
+ == NGX_OK)
+ {
+ create = 1;
+
+ } else if (stp->id.len) {
+ sess_id = stp->id;
+ create = 0;
+
+ } else {
+ return;
+ }
+
+ tp = ngx_timeofday();
+ now = tp->sec * 1000 + tp->msec;
+
+ ngx_http_upstream_sticky_sess_init_key(&sess_id, &key);
+
+ ngx_shmtx_lock(&sess->shpool->mutex);
+
+ sn = ngx_http_upstream_sticky_sess_lookup(sess, &key);
+
+ if (sn) {
+ if (pc->sid->len != sn->sid_len
+ || ngx_memcmp(pc->sid->data, sn->sid, sn->sid_len) != 0)
+ {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "sticky: session \"%V\" reused for SID \"%V\"",
+ &sess_id, pc->sid);
+
+ sn->sid_len = pc->sid->len;
+ ngx_memcpy(sn->sid, pc->sid->data, pc->sid->len);
+ }
+
+ ngx_rbtree_delete(&sess->sh->exp_rbtree, &sn->enode);
+ sn->last = now;
+ sn->enode.key = sn->last;
+ ngx_rbtree_insert(&sess->sh->exp_rbtree, &sn->enode);
+
+ ngx_shmtx_unlock(&sess->shpool->mutex);
+ return;
+ }
+
+ if (create) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "sticky: creating session \"%V\", SID \"%V\"",
+ &sess_id, pc->sid);
+
+ sn = ngx_http_upstream_sticky_sess_create(sess, &key, pc->sid);
+
+ if (sn) {
+ sn->last = now;
+ sn->enode.key = sn->last;
+ ngx_rbtree_insert(&sess->sh->exp_rbtree, &sn->enode);
+
+ if (!sess->event.timer_set) {
+ ngx_add_timer(&sess->event, sess->timeout);
+ }
+ }
+ }
+
+ ngx_shmtx_unlock(&sess->shpool->mutex);
+
+done:
+
stp->original_free_peer(pc, stp->original_data, state);
}
@@ -386,6 +600,275 @@ ngx_http_upstream_sticky_cookie_insert(ngx_peer_connection_t *pc,
}
+static ngx_http_upstream_sticky_sess_node_t *
+ngx_http_upstream_sticky_sess_lookup(ngx_http_upstream_sticky_sess_t *sess,
+ ngx_http_upstream_sticky_sess_key_t *key)
+{
+ ngx_int_t rc;
+ ngx_uint_t hash;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_http_upstream_sticky_sess_node_t *sn;
+
+ hash = key->hash;
+ node = sess->sh->rbtree.root;
+ sentinel = sess->sh->rbtree.sentinel;
+
+ while (node != sentinel) {
+
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+
+ do {
+
+ sn = (ngx_http_upstream_sticky_sess_node_t *) node;
+
+ rc = ngx_memcmp(key->md5, sn->u.md5, 16);
+
+ if (rc == 0) {
+ return sn;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+
+ } while (node != sentinel && hash == node->key);
+
+ break;
+ }
+
+ return NULL;
+}
+
+
+static ngx_http_upstream_sticky_sess_node_t *
+ngx_http_upstream_sticky_sess_create(ngx_http_upstream_sticky_sess_t *sess,
+ ngx_http_upstream_sticky_sess_key_t *key, ngx_str_t *sid)
+{
+ size_t n;
+ ngx_rbtree_node_t *node;
+ ngx_http_upstream_sticky_sess_node_t *sn;
+
+ n = sizeof(ngx_http_upstream_sticky_sess_node_t);
+
+ sn = ngx_slab_alloc_locked(sess->shpool, n);
+ if (sn == NULL) {
+
+ ngx_log_error(NGX_LOG_WARN, ngx_cycle->log, 0,
+ "could not allocate node%s, expiring least "
+ "recently used session", sess->shpool->log_ctx);
+
+ (void) ngx_http_upstream_sticky_sess_expire(sess, 1);
+
+ sn = ngx_slab_alloc_locked(sess->shpool, n);
+ if (sn == NULL) {
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "could not allocate node%s", sess->shpool->log_ctx);
+ return NULL;
+ }
+ }
+
+ ngx_memcpy(sn->u.md5, key->md5, 16);
+
+ sn->sid_len = sid->len;
+ ngx_memcpy(sn->sid, sid->data, sid->len);
+
+ node = &sn->rbnode;
+ node->key = sn->u.hash;
+
+ ngx_rbtree_insert(&sess->sh->rbtree, node);
+
+ return sn;
+}
+
+
+static void
+ngx_http_upstream_sticky_sess_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+ ngx_rbtree_node_t **p;
+ ngx_http_upstream_sticky_sess_node_t *sn, *snt;
+
+ for ( ;; ) {
+
+ if (node->key < temp->key) {
+
+ p = &temp->left;
+
+ } else if (node->key > temp->key) {
+
+ p = &temp->right;
+
+ } else { /* node->key == temp->key */
+
+ sn = (ngx_http_upstream_sticky_sess_node_t *) node;
+ snt = (ngx_http_upstream_sticky_sess_node_t *) temp;
+
+ p = (ngx_memcmp(sn->u.md5, snt->u.md5, 16) < 0)
+ ? &temp->left : &temp->right;
+ }
+
+ if (*p == sentinel) {
+ break;
+ }
+
+ temp = *p;
+ }
+
+ *p = node;
+ node->parent = temp;
+ node->left = sentinel;
+ node->right = sentinel;
+ ngx_rbt_red(node);
+}
+
+
+static void
+ngx_http_upstream_sticky_sess_timer_handler(ngx_event_t *ev)
+{
+ ngx_msec_t wait;
+ ngx_http_upstream_sticky_sess_t *sess;
+
+ sess = ev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "sticky: session timer");
+
+ ngx_shmtx_lock(&sess->shpool->mutex);
+
+ wait = ngx_http_upstream_sticky_sess_expire(sess, 0);
+
+ ngx_shmtx_unlock(&sess->shpool->mutex);
+
+ if (wait > 0) {
+ ngx_add_timer(&sess->event, wait);
+ }
+}
+
+
+static ngx_msec_t
+ngx_http_upstream_sticky_sess_expire(ngx_http_upstream_sticky_sess_t *sess,
+ ngx_uint_t force)
+{
+ ngx_msec_t now, wait;
+ ngx_time_t *tp;
+ ngx_rbtree_node_t *node, *next;
+ ngx_http_upstream_sticky_sess_node_t *sn;
+
+ wait = 0;
+
+ tp = ngx_timeofday();
+ now = tp->sec * 1000 + tp->msec;
+
+ if (sess->sh->exp_rbtree.root == sess->sh->exp_rbtree.sentinel) {
+ return 0;
+ }
+
+#if (NGX_SUPPRESS_WARN)
+ next = NULL;
+#endif
+
+ for (node = ngx_rbtree_min(sess->sh->exp_rbtree.root,
+ sess->sh->exp_rbtree.sentinel);
+ node;
+ node = next)
+ {
+
+ sn = ngx_http_upstream_sticky_sess_node(node, enode);
+ wait = sn->last + sess->timeout - now;
+
+ if (!force && (ngx_msec_int_t) wait > 0) {
+ break;
+ }
+
+ force = 0;
+
+ next = ngx_rbtree_next(&sess->sh->exp_rbtree, node);
+
+ /* remove node */
+ node = &sn->enode;
+ ngx_rbtree_delete(&sess->sh->exp_rbtree, node);
+
+ node = &sn->rbnode;
+ ngx_rbtree_delete(&sess->sh->rbtree, node);
+ ngx_slab_free_locked(sess->shpool, node);
+ }
+
+ return wait;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_sticky_sess_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+ ngx_http_upstream_sticky_sess_t *old_sess = data;
+
+ size_t len;
+ ngx_http_upstream_sticky_sess_t *sess;
+
+ sess = shm_zone->data;
+
+ if (old_sess) {
+
+ if (ngx_strcmp(sess->host->data, old_sess->host->data) != 0) {
+
+ ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+ "sticky zone \"%V\" is used in upstream \"%V\" "
+ "while previously it was used in upstream \"%V\"",
+ &shm_zone->shm.name, sess->host, old_sess->host);
+
+ return NGX_ERROR;
+ }
+
+ sess->sh = old_sess->sh;
+ sess->shpool = old_sess->shpool;
+ return NGX_OK;
+ }
+
+ sess->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ if (shm_zone->shm.exists) {
+ sess->sh = sess->shpool->data;
+ return NGX_OK;
+ }
+
+ sess->sh = ngx_slab_alloc(sess->shpool,
+ sizeof(ngx_http_upstream_sticky_sess_shared_t));
+ if (sess->sh == NULL) {
+ return NGX_ERROR;
+ }
+
+ sess->shpool->data = sess->sh;
+
+ ngx_rbtree_init(&sess->sh->rbtree, &sess->sh->sentinel,
+ ngx_http_upstream_sticky_sess_rbtree_insert_value);
+
+ ngx_rbtree_init(&sess->sh->exp_rbtree, &sess->sh->exp_sentinel,
+ ngx_rbtree_insert_timer_value);
+
+ len = sizeof(" in sticky session zone \"\"") + shm_zone->shm.name.len;
+
+ sess->shpool->log_ctx = ngx_slab_alloc(sess->shpool, len);
+ if (sess->shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(sess->shpool->log_ctx, " in sticky session zone \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ sess->shpool->log_nomem = 0;
+
+ return NGX_OK;
+}
+
+
static void *
ngx_http_upstream_sticky_create_conf(ngx_conf_t *cf)
{
@@ -403,6 +886,8 @@ ngx_http_upstream_sticky_create_conf(ngx_conf_t *cf)
* stcf->original_init_peer = NULL;
*
* stcf->lookup_vars = NULL;
+ * stcf->create_vars = NULL;
+ * stcf->shm_zone = NULL;
*
* stcf->cookie_name = { 0, NULL };
* stcf->cookie_domain = { 0, NULL };
@@ -418,9 +903,8 @@ ngx_http_upstream_sticky_create_conf(ngx_conf_t *cf)
static char *
ngx_http_upstream_sticky(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
- u_char *p;
- ngx_str_t *value, varname;
- ngx_int_t index, *indexp;
+ ngx_str_t *value;
+ ngx_int_t *indexp, index;
ngx_uint_t i;
ngx_http_upstream_srv_conf_t *us;
ngx_http_upstream_sticky_srv_conf_t *stcf;
@@ -437,9 +921,18 @@ ngx_http_upstream_sticky(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR;
}
+ stcf->original_init_upstream = us->peer.init_upstream
+ ? us->peer.init_upstream
+ : ngx_http_upstream_init_round_robin;
+
+ us->peer.init_upstream = ngx_http_upstream_sticky_init_upstream;
+
value = cf->args->elts;
- if (ngx_strcmp(value[1].data, "route") == 0) {
+ if (ngx_strcmp(value[1].data, "cookie") == 0) {
+ return ngx_http_upstream_sticky_cookie(cf, stcf);
+
+ } else if (ngx_strcmp(value[1].data, "route") == 0) {
for (i = 2; i < cf->args->nelts; i++) {
@@ -465,128 +958,310 @@ ngx_http_upstream_sticky(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
*indexp = index;
}
- /*
- * stcf->stick = NULL;
- */
+ return NGX_CONF_OK;
- } else if (ngx_strcmp(value[1].data, "cookie") == 0) {
+ } else if (ngx_strcmp(value[1].data, "learn") == 0) {
+ return ngx_http_upstream_sticky_learn(cf, stcf, us);
+ }
- if (value[2].len == 0) {
- return "empty cookie name";
- }
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown parameter \"%V\"",
+ &value[1]);
+ return NGX_CONF_ERROR;
+}
- stcf->cookie_name = value[2];
- for (i = 3; i < cf->args->nelts; i++) {
+static char *
+ngx_http_upstream_sticky_cookie(ngx_conf_t *cf,
+ ngx_http_upstream_sticky_srv_conf_t *stcf)
+{
+ u_char *p;
+ ngx_str_t name, *value;
+ ngx_int_t index, *indexp;
+ ngx_uint_t i;
- if (ngx_strncmp(value[i].data, "domain=", 7) == 0) {
+ value = cf->args->elts;
- if (stcf->cookie_domain.data != NULL) {
- return "parameter \"domain\" is duplicate";
- }
+ if (value[2].len == 0) {
+ return "empty cookie name";
+ }
- value[i].data += 7;
- value[i].len -= 7;
+ stcf->cookie_name = value[2];
- if (value[i].len == 0) {
- return "no value for \"domain\"";
- }
+ for (i = 3; i < cf->args->nelts; i++) {
- stcf->cookie_domain.len = sizeof("; domain=") - 1
- + value[i].len;
+ if (ngx_strncmp(value[i].data, "domain=", 7) == 0) {
- stcf->cookie_domain.data = ngx_pnalloc(cf->pool,
- stcf->cookie_domain.len);
- if (stcf->cookie_domain.data == NULL) {
- return NGX_CONF_ERROR;
- }
+ if (stcf->cookie_domain.data != NULL) {
+ return "parameter \"domain\" is duplicate";
+ }
- p = ngx_cpymem(stcf->cookie_domain.data,
- "; domain=", sizeof("; domain=") - 1);
- ngx_memcpy(p, value[i].data, value[i].len);
+ value[i].data += 7;
+ value[i].len -= 7;
+ if (value[i].len == 0) {
+ return "no value for \"domain\"";
+ }
- } else if (ngx_strncmp(value[i].data, "path=", 5) == 0) {
+ stcf->cookie_domain.len = sizeof("; domain=") - 1
+ + value[i].len;
- if (stcf->cookie_path.data != NULL) {
- return "parameter \"path\" is duplicate";
- }
+ stcf->cookie_domain.data = ngx_pnalloc(cf->pool,
+ stcf->cookie_domain.len);
+ if (stcf->cookie_domain.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
- value[i].data += 5;
- value[i].len -= 5;
+ p = ngx_cpymem(stcf->cookie_domain.data,
+ "; domain=", sizeof("; domain=") - 1);
+ ngx_memcpy(p, value[i].data, value[i].len);
- if (value[i].len == 0) {
- return "no value for \"path\"";
- }
- stcf->cookie_path.len = sizeof("; path=") - 1 + value[i].len;
+ } else if (ngx_strncmp(value[i].data, "path=", 5) == 0) {
- stcf->cookie_path.data = ngx_pnalloc(cf->pool,
- stcf->cookie_path.len);
- if (stcf->cookie_path.data == NULL) {
- return NGX_CONF_ERROR;
- }
+ if (stcf->cookie_path.data != NULL) {
+ return "parameter \"path\" is duplicate";
+ }
- p = ngx_cpymem(stcf->cookie_path.data,
- "; path=", sizeof("; path=") - 1);
- ngx_memcpy(p, value[i].data, value[i].len);
+ value[i].data += 5;
+ value[i].len -= 5;
+ if (value[i].len == 0) {
+ return "no value for \"path\"";
+ }
- } else if (ngx_strncmp(value[i].data, "expires=", 8) == 0) {
+ stcf->cookie_path.len = sizeof("; path=") - 1 + value[i].len;
- if (stcf->cookie_expires != (time_t) NGX_CONF_UNSET) {
- return "parameter \"expires\" is duplicate";
- }
+ stcf->cookie_path.data = ngx_pnalloc(cf->pool,
+ stcf->cookie_path.len);
+ if (stcf->cookie_path.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
- value[i].data += 8;
- value[i].len -= 8;
+ p = ngx_cpymem(stcf->cookie_path.data,
+ "; path=", sizeof("; path=") - 1);
+ ngx_memcpy(p, value[i].data, value[i].len);
- if (ngx_strcmp(value[i].data, "max") == 0) {
- stcf->cookie_expires = NGX_HTTP_STICKY_COOKIE_MAX_EXPIRES;
- } else {
- stcf->cookie_expires = ngx_parse_time(&value[i], 1);
- if (stcf->cookie_expires == (time_t) NGX_ERROR) {
- return "invalid \"expires\" parameter value";
- }
- }
+ } else if (ngx_strncmp(value[i].data, "expires=", 8) == 0) {
- } else {
- return "unknown parameter";
+ if (stcf->cookie_expires != (time_t) NGX_CONF_UNSET) {
+ return "parameter \"expires\" is duplicate";
}
- }
- varname.len = sizeof("cookie_") - 1 + stcf->cookie_name.len;
- varname.data = ngx_pnalloc(cf->pool, varname.len);
- if (varname.data == NULL) {
- return NGX_CONF_ERROR;
- }
+ value[i].data += 8;
+ value[i].len -= 8;
+
+ if (ngx_strcmp(value[i].data, "max") == 0) {
+ stcf->cookie_expires = NGX_HTTP_STICKY_COOKIE_MAX_EXPIRES;
- ngx_sprintf(varname.data, "cookie_%V", &stcf->cookie_name);
+ } else {
+ stcf->cookie_expires = ngx_parse_time(&value[i], 1);
+ if (stcf->cookie_expires == (time_t) NGX_ERROR) {
+ return "invalid \"expires\" parameter value";
+ }
+ }
- index = ngx_http_get_variable_index(cf, &varname);
- if (index == NGX_ERROR) {
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
+ }
+
+ name.len = sizeof("cookie_") - 1 + stcf->cookie_name.len;
+ name.data = ngx_pnalloc(cf->pool, name.len);
+ if (name.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_sprintf(name.data, "cookie_%V", &stcf->cookie_name);
+
+ index = ngx_http_get_variable_index(cf, &name);
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ indexp = ngx_array_push(stcf->lookup_vars);
+ if (indexp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *indexp = index;
+
+ return NGX_CONF_OK;
+}
- indexp = ngx_array_push(stcf->lookup_vars);
- if (indexp == NULL) {
+
+static char *
+ngx_http_upstream_sticky_learn(ngx_conf_t *cf,
+ ngx_http_upstream_sticky_srv_conf_t *stcf, ngx_http_upstream_srv_conf_t *us)
+{
+ u_char *p;
+ ssize_t zone_size;
+ ngx_str_t *value, name, size;
+ ngx_int_t index, *indexp;
+ ngx_uint_t i;
+ ngx_msec_t timeout;
+ ngx_shm_zone_t *shm_zone;
+ ngx_http_upstream_sticky_sess_t *sess;
+
+ zone_size = 0;
+ timeout = NGX_CONF_UNSET_MSEC;
+
+ stcf->create_vars = ngx_array_create(cf->pool, 1, sizeof(ngx_int_t));
+ if (stcf->create_vars == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+ if (zone_size != 0) {
+ return "duplicate zone";
+ }
+
+ name.data = value[i].data + 5;
+
+ p = (u_char *) ngx_strchr(name.data, ':');
+
+ if (p == NULL) {
+ return "zone size is not specified";
+ }
+
+ name.len = p - name.data;
+
+ if (name.len == 0) {
+ return "zone name is not specified";
+ }
+
+ size.data = ++p;
+ size.len = value[i].data + value[i].len - p;
+
+ zone_size = ngx_parse_size(&size);
+ if (zone_size == NGX_ERROR) {
+ return "invalid zone size";
+ }
+
+ /* 32k ~ 200 sessions, 1m ~ 8000 sessions */
+ if (zone_size < (ssize_t) (8 * ngx_pagesize)) {
+ return "zone is too small";
+ }
+
+ } else if (ngx_strncmp(value[i].data, "timeout=", 8) == 0) {
+
+ if (timeout != NGX_CONF_UNSET_MSEC) {
+ return "duplicate timeout";
+ }
+
+ value[i].data += 8;
+ value[i].len -= 8;
+
+ timeout = ngx_parse_time(&value[i], 0);
+ if (timeout == (ngx_msec_t) NGX_ERROR || timeout == 0) {
+ return "invalid timeout";
+ }
+
+ } else if (ngx_strncmp(value[i].data, "create=", 7) == 0) {
+
+ if (value[i].data[7] != '$') {
+ return "missing variable in the \"create\" parameter";
+ }
+
+ value[i].data += 8;
+ value[i].len -= 8;
+
+ index = ngx_http_get_variable_index(cf, &value[i]);
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ indexp = ngx_array_push(stcf->create_vars);
+ if (indexp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *indexp = index;
+
+ } else if (ngx_strncmp(value[i].data, "lookup=", 7) == 0) {
+
+ if (value[i].data[7] != '$') {
+ return "missing variable in the \"lookup\" parameter";
+ }
+
+ value[i].data += 8;
+ value[i].len -= 8;
+
+ index = ngx_http_get_variable_index(cf, &value[i]);
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ indexp = ngx_array_push(stcf->lookup_vars);
+ if (indexp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *indexp = index;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
+ }
+
+ if (stcf->lookup_vars->nelts == 0) {
+ return "\"lookup\" parameter is not specified";
+ }
- *indexp = index;
+ if (stcf->create_vars->nelts == 0) {
+ return "\"create\" parameter is not specified";
+ }
- } else {
- ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown parameter \"%V\"",
- &value[1]);
+ if (zone_size == 0) {
+ return "\"zone\" parameter is not specified";
+ }
+
+ if (timeout == NGX_CONF_UNSET_MSEC) {
+ timeout = 600000; /* 10m */
+ }
+
+ shm_zone = ngx_shared_memory_add(cf, &name, zone_size,
+ &ngx_http_upstream_sticky_module);
+ if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}
- stcf->original_init_upstream = us->peer.init_upstream
- ? us->peer.init_upstream
- : ngx_http_upstream_init_round_robin;
+ if (shm_zone->data) {
+ sess = shm_zone->data;
- us->peer.init_upstream = ngx_http_upstream_sticky_init_upstream;
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "sticky zone \"%V\" is already used in "
+ "upstream \"%V\"", &name, sess->host);
+
+ return NGX_CONF_ERROR;
+ }
+
+ sess = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_sticky_sess_t));
+ if (sess == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ sess->timeout = timeout;
+ sess->host = &us->host;
+
+ sess->event.data = sess;
+ sess->event.log = &cf->cycle->new_log;
+ sess->event.handler = ngx_http_upstream_sticky_sess_timer_handler;
+ sess->event.cancelable = 1;
+
+ shm_zone->init = ngx_http_upstream_sticky_sess_init_zone;
+ shm_zone->data = sess;
+
+ stcf->shm_zone = shm_zone;
return NGX_CONF_OK;
}