/* * Copyright (C) Jerome Loyet */ #include #include #include #include "ngx_http_sticky_misc.h" /* define a peer */ typedef struct { ngx_http_upstream_rr_peer_t *rr_peer; ngx_str_t digest; } ngx_http_sticky_peer_t; /* the configuration structure */ typedef struct { ngx_http_upstream_srv_conf_t uscf; ngx_str_t cookie_name; ngx_str_t cookie_domain; ngx_str_t cookie_path; time_t cookie_expires; ngx_str_t hmac_key; ngx_http_sticky_misc_hash_pt hash; ngx_http_sticky_misc_hmac_pt hmac; ngx_http_sticky_misc_text_pt text; ngx_uint_t no_fallback; ngx_http_sticky_peer_t *peers; } ngx_http_sticky_srv_conf_t; /* the custom sticky struct used on each request */ typedef struct { /* the round robin data must be first */ ngx_http_upstream_rr_peer_data_t rrp; ngx_event_get_peer_pt get_rr_peer; int selected_peer; int no_fallback; ngx_http_sticky_srv_conf_t *sticky_conf; ngx_http_request_t *request; } ngx_http_sticky_peer_data_t; static ngx_int_t ngx_http_init_sticky_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data); static char *ngx_http_sticky_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void *ngx_http_sticky_create_conf(ngx_conf_t *cf); static ngx_command_t ngx_http_sticky_commands[] = { { ngx_string("sticky"), NGX_HTTP_UPS_CONF|NGX_CONF_ANY, ngx_http_sticky_set, 0, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_sticky_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ ngx_http_sticky_create_conf, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_sticky_module = { NGX_MODULE_V1, &ngx_http_sticky_module_ctx, /* module context */ ngx_http_sticky_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; /* * function called by the upstream module to init itself * it's called once per instance */ ngx_int_t ngx_http_init_upstream_sticky(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { ngx_http_upstream_rr_peers_t *rr_peers; ngx_http_sticky_srv_conf_t *conf; ngx_uint_t i; /* call the rr module on wich the sticky module is based on */ if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) { return NGX_ERROR; } /* calculate each peer digest once and save */ rr_peers = us->peer.data; /* do nothing there's only one peer */ if (rr_peers->number <= 1 || rr_peers->single) { return NGX_OK; } /* tell the upstream module to call ngx_http_init_sticky_peer when it inits peer */ us->peer.init = ngx_http_init_sticky_peer; conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_sticky_module); /* if 'index', no need to alloc and generate digest */ if (!conf->hash && !conf->hmac && !conf->text) { conf->peers = NULL; return NGX_OK; } /* create our own upstream indexes */ conf->peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_sticky_peer_t) * rr_peers->number); if (conf->peers == NULL) { return NGX_ERROR; } /* parse each peer and generate digest if necessary */ for (i = 0; i < rr_peers->number; i++) { conf->peers[i].rr_peer = &rr_peers->peer[i]; if (conf->hmac) { /* generate hmac */ conf->hmac(cf->pool, rr_peers->peer[i].sockaddr, rr_peers->peer[i].socklen, &conf->hmac_key, &conf->peers[i].digest); } else if (conf->text) { /* generate text */ conf->text(cf->pool, rr_peers->peer[i].sockaddr, &conf->peers[i].digest); } else { /* generate hash */ conf->hash(cf->pool, rr_peers->peer[i].sockaddr, rr_peers->peer[i].socklen, &conf->peers[i].digest); } #if 0 /* FIXME: is it possible to log to debug level when at configuration stage ? */ ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "[sticky/ngx_http_init_upstream_sticky] generated digest \"%V\" for upstream at index %d", &conf->peers[i].digest, i); #endif } return NGX_OK; } /* * function called by the upstream module when it inits each peer * it's called once per request */ static ngx_int_t ngx_http_init_sticky_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { ngx_http_sticky_peer_data_t *iphp; ngx_str_t route; ngx_uint_t i; ngx_int_t n; /* alloc custom sticky struct */ iphp = ngx_palloc(r->pool, sizeof(ngx_http_sticky_peer_data_t)); if (iphp == NULL) { return NGX_ERROR; } /* attach it to the request upstream data */ r->upstream->peer.data = &iphp->rrp; /* call the rr module on which the sticky is based on */ if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) { return NGX_ERROR; } /* set the callback to select the next peer to use */ r->upstream->peer.get = ngx_http_get_sticky_peer; /* init the custom sticky struct */ iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer; iphp->selected_peer = -1; iphp->no_fallback = 0; iphp->sticky_conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_sticky_module); iphp->request = r; /* check weather a cookie is present or not and save it */ if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &iphp->sticky_conf->cookie_name, &route) != NGX_DECLINED) { /* a route cookie has been found. Let's give it a try */ ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] got cookie route=%V, let's try to find a matching peer", &route); /* hash, hmac or text, just compare digest */ if (iphp->sticky_conf->hash || iphp->sticky_conf->hmac || iphp->sticky_conf->text) { /* check internal struct has been set */ if (!iphp->sticky_conf->peers) { /* log a warning, as it will continue without the sticky */ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[sticky/init_sticky_peer] internal peers struct has not been set"); return NGX_OK; /* return OK, in order to continue */ } /* search the digest found in the cookie in the peer digest list */ for (i = 0; i < iphp->rrp.peers->number; i++) { /* ensure the both len are equal and > 0 */ if (iphp->sticky_conf->peers[i].digest.len != route.len || route.len <= 0) { continue; } if (!ngx_strncmp(iphp->sticky_conf->peers[i].digest.data, route.data, route.len)) { /* we found a match */ iphp->selected_peer = i; ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" matches peer at index %ui", &route, i); return NGX_OK; } } } else { /* switch back to index, just convert to integer and ensure it corresponds to a valid peer */ n = ngx_atoi(route.data, route.len); if (n == NGX_ERROR) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[sticky/init_sticky_peer] unable to convert the route \"%V\" to an integer value", &route); } else if (n >= 0 && n < (ngx_int_t)iphp->rrp.peers->number) { /* found one */ ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" matches peer at index %i", &route, n); iphp->selected_peer = n; return NGX_OK; } } /* nothing was found, just continue with rr */ ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" does not match any peer. Just ignoring it ...", &route); return NGX_OK; } /* nothing found */ ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] route cookie not found", &route); return NGX_OK; /* return OK, in order to continue */ } /* * function called by the upstream module to choose the next peer to use * called at least one time per request */ static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data) { ngx_http_sticky_peer_data_t *iphp = data; ngx_http_sticky_srv_conf_t *conf = iphp->sticky_conf; ngx_int_t selected_peer = -1; time_t now = ngx_time(); uintptr_t m; ngx_uint_t n, i; ngx_http_upstream_rr_peer_t *peer = NULL; ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] get sticky peer, try: %ui, n_peers: %ui, no_fallback: %ui/%ui", pc->tries, iphp->rrp.peers->number, conf->no_fallback, iphp->no_fallback); /* TODO: cached */ /* has the sticky module already choosen a peer to connect to and is it a valid peer */ /* is there more than one peer (otherwise, no choices to make) */ if (iphp->selected_peer >= 0 && iphp->selected_peer < (ngx_int_t)iphp->rrp.peers->number && !iphp->rrp.peers->single) { ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] let's try the selected peer (%i)", iphp->selected_peer); n = iphp->selected_peer / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << iphp->selected_peer % (8 * sizeof(uintptr_t)); /* has the peer not already been tried ? */ if (!(iphp->rrp.tried[n] & m)) { peer = &iphp->rrp.peers->peer[iphp->selected_peer]; /* if the no_fallback flag is set */ if (conf->no_fallback) { iphp->no_fallback = 1; /* if peer is down */ if (peer->down) { ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] the selected peer is down and no_fallback is flagged"); return NGX_BUSY; } /* if it's been ignored for long enought (fail_timeout), reset timeout */ /* do this check before testing peer->fails ! :) */ if (now - peer->accessed > peer->fail_timeout) { peer->fails = 0; } /* if peer is failed */ if (peer->max_fails > 0 && peer->fails >= peer->max_fails) { ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] the selected peer is maked as failed and no_fallback is flagged"); return NGX_BUSY; } } /* ensure the peer is not marked as down */ if (!peer->down) { /* if it's not failedi, use it */ if (peer->max_fails == 0 || peer->fails < peer->max_fails) { selected_peer = (ngx_int_t)n; /* if it's been ignored for long enought (fail_timeout), reset timeout and use it */ } else if (now - peer->accessed > peer->fail_timeout) { peer->fails = 0; selected_peer = (ngx_int_t)n; /* it's failed or timeout did not expire yet */ } else { /* mark the peer as tried */ iphp->rrp.tried[n] |= m; } } } } /* we have a valid peer, tell the upstream module to use it */ if (peer && selected_peer >= 0) { ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] peer found at index %i", selected_peer); iphp->rrp.current = iphp->selected_peer; pc->cached = 0; pc->connection = NULL; pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; iphp->rrp.tried[n] |= m; } else { ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] no sticky peer selected, switch back to classic rr"); if (iphp->no_fallback) { ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] No fallback in action !"); return NGX_BUSY; } ngx_int_t ret = iphp->get_rr_peer(pc, &iphp->rrp); if (ret != NGX_OK) { ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] ngx_http_upstream_get_round_robin_peer returned %i", ret); return ret; } /* search for the choosen peer in order to set the cookie */ for (i = 0; i < iphp->rrp.peers->number; i++) { if (iphp->rrp.peers->peer[i].sockaddr == pc->sockaddr && iphp->rrp.peers->peer[i].socklen == pc->socklen) { if (conf->hash || conf->hmac || conf->text) { ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &conf->peers[i].digest, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires); ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] set cookie \"%V\" value=\"%V\" index=%ui", &conf->cookie_name, &conf->peers[i].digest, i); } else { ngx_str_t route; ngx_uint_t tmp = i; route.len = 0; do { route.len++; } while (tmp /= 10); route.data = ngx_pcalloc(iphp->request->pool, sizeof(u_char) * (route.len + 1)); if (route.data == NULL) { break; } ngx_snprintf(route.data, route.len, "%d", i); route.len = ngx_strlen(route.data); ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &route, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires); ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] set cookie \"%V\" value=\"%V\" index=%ui", &conf->cookie_name, &tmp, i); } break; /* found and hopefully the cookie have been set */ } } } /* reset the selection in order to bypass the sticky module when the upstream module will try another peers if necessary */ iphp->selected_peer = -1; return NGX_OK; } /* * Function called when the sticky command is parsed on the conf file */ static char *ngx_http_sticky_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_srv_conf_t *upstream_conf; ngx_http_sticky_srv_conf_t *sticky_conf; ngx_uint_t i; ngx_str_t tmp; ngx_str_t name = ngx_string("route"); ngx_str_t domain = ngx_string(""); ngx_str_t path = ngx_string(""); ngx_str_t hmac_key = ngx_string(""); time_t expires = NGX_CONF_UNSET; ngx_http_sticky_misc_hash_pt hash = NGX_CONF_UNSET_PTR; ngx_http_sticky_misc_hmac_pt hmac = NULL; ngx_http_sticky_misc_text_pt text = NULL; ngx_uint_t no_fallback = 0; /* parse all elements */ for (i = 1; i < cf->args->nelts; i++) { ngx_str_t *value = cf->args->elts; /* is "name=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "name=") == value[i].data) { /* do we have at least on char after "name=" ? */ if (value[i].len <= sizeof("name=") - 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"name=\""); return NGX_CONF_ERROR; } /* save what's after "name=" */ name.len = value[i].len - ngx_strlen("name="); name.data = (u_char *)(value[i].data + sizeof("name=") - 1); continue; } /* is "domain=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "domain=") == value[i].data) { /* do we have at least on char after "domain=" ? */ if (value[i].len <= ngx_strlen("domain=")) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"domain=\""); return NGX_CONF_ERROR; } /* save what's after "domain=" */ domain.len = value[i].len - ngx_strlen("domain="); domain.data = (u_char *)(value[i].data + sizeof("domain=") - 1); continue; } /* is "path=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "path=") == value[i].data) { /* do we have at least on char after "path=" ? */ if (value[i].len <= ngx_strlen("path=")) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"path=\""); return NGX_CONF_ERROR; } /* save what's after "domain=" */ path.len = value[i].len - ngx_strlen("path="); path.data = (u_char *)(value[i].data + sizeof("path=") - 1); continue; } /* is "expires=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "expires=") == value[i].data) { /* do we have at least on char after "expires=" ? */ if (value[i].len <= sizeof("expires=") - 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"expires=\""); return NGX_CONF_ERROR; } /* extract value */ tmp.len = value[i].len - ngx_strlen("expires="); tmp.data = (u_char *)(value[i].data + sizeof("expires=") - 1); /* convert to time, save and validate */ expires = ngx_parse_time(&tmp, 1); if (expires == NGX_ERROR || expires < 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value for \"expires=\""); return NGX_CONF_ERROR; } continue; } /* is "text=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "text=") == value[i].data) { /* only hash or hmac can be used, not both */ if (hmac || hash != NGX_CONF_UNSET_PTR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text\""); return NGX_CONF_ERROR; } /* do we have at least on char after "name=" ? */ if (value[i].len <= sizeof("text=") - 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"text=\""); return NGX_CONF_ERROR; } /* extract value to temp */ tmp.len = value[i].len - ngx_strlen("text="); tmp.data = (u_char *)(value[i].data + sizeof("text=") - 1); /* is name=raw */ if (ngx_strncmp(tmp.data, "raw", sizeof("raw") - 1) == 0 ) { text = ngx_http_sticky_misc_text_raw; continue; } /* is name=md5 */ if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) { text = ngx_http_sticky_misc_text_md5; continue; } /* is name=sha1 */ if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) { text = ngx_http_sticky_misc_text_sha1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"text=\": raw, md5 or sha1"); return NGX_CONF_ERROR; } /* is "hash=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "hash=") == value[i].data) { /* only hash or hmac can be used, not both */ if (hmac || text) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text=\""); return NGX_CONF_ERROR; } /* do we have at least on char after "hash=" ? */ if (value[i].len <= sizeof("hash=") - 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hash=\""); return NGX_CONF_ERROR; } /* extract value to temp */ tmp.len = value[i].len - ngx_strlen("hash="); tmp.data = (u_char *)(value[i].data + sizeof("hash=") - 1); /* is hash=index */ if (ngx_strncmp(tmp.data, "index", sizeof("index") - 1) == 0 ) { hash = NULL; continue; } /* is hash=md5 */ if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) { hash = ngx_http_sticky_misc_md5; continue; } /* is hash=sha1 */ if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) { hash = ngx_http_sticky_misc_sha1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"hash=\": index, md5 or sha1"); return NGX_CONF_ERROR; } /* is "hmac=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "hmac=") == value[i].data) { /* only hash or hmac can be used, not both */ if (hash != NGX_CONF_UNSET_PTR || text) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text\""); return NGX_CONF_ERROR; } /* do we have at least on char after "hmac=" ? */ if (value[i].len <= sizeof("hmac=") - 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hmac=\""); return NGX_CONF_ERROR; } /* extract value */ tmp.len = value[i].len - ngx_strlen("hmac="); tmp.data = (u_char *)(value[i].data + sizeof("hmac=") - 1); /* is hmac=md5 ? */ if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) { hmac = ngx_http_sticky_misc_hmac_md5; continue; } /* is hmac=sha1 ? */ if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) { hmac = ngx_http_sticky_misc_hmac_sha1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"hmac=\": md5 or sha1"); return NGX_CONF_ERROR; } /* is "hmac_key=" is starting the argument ? */ if ((u_char *)ngx_strstr(value[i].data, "hmac_key=") == value[i].data) { /* do we have at least on char after "hmac_key=" ? */ if (value[i].len <= ngx_strlen("hmac_key=")) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hmac_key=\""); return NGX_CONF_ERROR; } /* save what's after "hmac_key=" */ hmac_key.len = value[i].len - ngx_strlen("hmac_key="); hmac_key.data = (u_char *)(value[i].data + sizeof("hmac_key=") - 1); continue; } /* is "no_fallback" flag present ? */ if (ngx_strncmp(value[i].data, "no_fallback", sizeof("no_fallback") - 1) == 0 ) { no_fallback = 1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid arguement (%V)", &value[i]); return NGX_CONF_ERROR; } /* if has and hmac and name have not been set, default to md5 */ if (hash == NGX_CONF_UNSET_PTR && hmac == NULL && text == NULL) { hash = ngx_http_sticky_misc_md5; } /* don't allow meaning less parameters */ if (hmac_key.len > 0 && hash != NGX_CONF_UNSET_PTR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"hmac_key=\" is meaningless when \"hmac\" is used. Please remove it."); return NGX_CONF_ERROR; } /* ensure we have an hmac key if hmac's been set */ if (hmac_key.len == 0 && hmac != NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please specify \"hmac_key=\" when using \"hmac\""); return NGX_CONF_ERROR; } /* ensure hash is NULL to avoid conflicts later */ if (hash == NGX_CONF_UNSET_PTR) { hash = NULL; } /* save the sticky parameters */ sticky_conf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_sticky_module); sticky_conf->cookie_name = name; sticky_conf->cookie_domain = domain; sticky_conf->cookie_path = path; sticky_conf->cookie_expires = expires; sticky_conf->hash = hash; sticky_conf->hmac = hmac; sticky_conf->text = text; sticky_conf->hmac_key = hmac_key; sticky_conf->no_fallback = no_fallback; sticky_conf->peers = NULL; /* ensure it's null before running */ upstream_conf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); /* * ensure another upstream module has not been already loaded * peer.init_upstream is set to null and the upstream module use RR if not set * But this check only works when the other module is declared before sticky */ if (upstream_conf->peer.init_upstream) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "You can't use sticky with another upstream module"); return NGX_CONF_ERROR; } /* configure the upstream to get back to this module */ upstream_conf->peer.init_upstream = ngx_http_init_upstream_sticky; upstream_conf->flags = NGX_HTTP_UPSTREAM_CREATE | NGX_HTTP_UPSTREAM_MAX_FAILS | NGX_HTTP_UPSTREAM_FAIL_TIMEOUT | NGX_HTTP_UPSTREAM_DOWN | NGX_HTTP_UPSTREAM_WEIGHT; return NGX_CONF_OK; } /* * alloc stick configuration */ static void *ngx_http_sticky_create_conf(ngx_conf_t *cf) { ngx_http_sticky_srv_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sticky_srv_conf_t)); if (conf == NULL) { return NGX_CONF_ERROR; } return conf; }