|  |  | 
|  | /* | 
|  | * Copyright (C) Maxim Dounin | 
|  | * Copyright (C) Nginx, Inc. | 
|  | */ | 
|  |  | 
|  |  | 
|  | #include <ngx_config.h> | 
|  | #include <ngx_core.h> | 
|  | #include <ngx_http.h> | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_uint_t                         max_cached; | 
|  |  | 
|  | ngx_queue_t                        cache; | 
|  | ngx_queue_t                        free; | 
|  |  | 
|  | ngx_http_upstream_init_pt          original_init_upstream; | 
|  | ngx_http_upstream_init_peer_pt     original_init_peer; | 
|  |  | 
|  | } ngx_http_upstream_keepalive_srv_conf_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_http_upstream_keepalive_srv_conf_t  *conf; | 
|  |  | 
|  | ngx_http_upstream_t               *upstream; | 
|  |  | 
|  | void                              *data; | 
|  |  | 
|  | ngx_event_get_peer_pt              original_get_peer; | 
|  | ngx_event_free_peer_pt             original_free_peer; | 
|  |  | 
|  | #if (NGX_HTTP_SSL) | 
|  | ngx_event_set_peer_session_pt      original_set_session; | 
|  | ngx_event_save_peer_session_pt     original_save_session; | 
|  | #endif | 
|  |  | 
|  | } ngx_http_upstream_keepalive_peer_data_t; | 
|  |  | 
|  |  | 
|  | typedef struct { | 
|  | ngx_http_upstream_keepalive_srv_conf_t  *conf; | 
|  |  | 
|  | ngx_queue_t                        queue; | 
|  | ngx_connection_t                  *connection; | 
|  |  | 
|  | socklen_t                          socklen; | 
|  | u_char                             sockaddr[NGX_SOCKADDRLEN]; | 
|  |  | 
|  | } ngx_http_upstream_keepalive_cache_t; | 
|  |  | 
|  |  | 
|  | static ngx_int_t ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r, | 
|  | ngx_http_upstream_srv_conf_t *us); | 
|  | static ngx_int_t ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, | 
|  | void *data); | 
|  | static void ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, | 
|  | void *data, ngx_uint_t state); | 
|  |  | 
|  | static void ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev); | 
|  | static void ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev); | 
|  | static void ngx_http_upstream_keepalive_close(ngx_connection_t *c); | 
|  |  | 
|  |  | 
|  | #if (NGX_HTTP_SSL) | 
|  | static ngx_int_t ngx_http_upstream_keepalive_set_session( | 
|  | ngx_peer_connection_t *pc, void *data); | 
|  | static void ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc, | 
|  | void *data); | 
|  | #endif | 
|  |  | 
|  | static void *ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf); | 
|  | static char *ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, | 
|  | void *conf); | 
|  |  | 
|  |  | 
|  | static ngx_command_t  ngx_http_upstream_keepalive_commands[] = { | 
|  |  | 
|  | { ngx_string("keepalive"), | 
|  | NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12, | 
|  | ngx_http_upstream_keepalive, | 
|  | NGX_HTTP_SRV_CONF_OFFSET, | 
|  | 0, | 
|  | NULL }, | 
|  |  | 
|  | ngx_null_command | 
|  | }; | 
|  |  | 
|  |  | 
|  | static ngx_http_module_t  ngx_http_upstream_keepalive_module_ctx = { | 
|  | NULL,                                  /* preconfiguration */ | 
|  | NULL,                                  /* postconfiguration */ | 
|  |  | 
|  | NULL,                                  /* create main configuration */ | 
|  | NULL,                                  /* init main configuration */ | 
|  |  | 
|  | ngx_http_upstream_keepalive_create_conf, /* create server configuration */ | 
|  | NULL,                                  /* merge server configuration */ | 
|  |  | 
|  | NULL,                                  /* create location configuration */ | 
|  | NULL                                   /* merge location configuration */ | 
|  | }; | 
|  |  | 
|  |  | 
|  | ngx_module_t  ngx_http_upstream_keepalive_module = { | 
|  | NGX_MODULE_V1, | 
|  | &ngx_http_upstream_keepalive_module_ctx, /* module context */ | 
|  | ngx_http_upstream_keepalive_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 | 
|  | }; | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_upstream_init_keepalive(ngx_conf_t *cf, | 
|  | ngx_http_upstream_srv_conf_t *us) | 
|  | { | 
|  | ngx_uint_t                               i; | 
|  | ngx_http_upstream_keepalive_srv_conf_t  *kcf; | 
|  | ngx_http_upstream_keepalive_cache_t     *cached; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, | 
|  | "init keepalive"); | 
|  |  | 
|  | kcf = ngx_http_conf_upstream_srv_conf(us, | 
|  | ngx_http_upstream_keepalive_module); | 
|  |  | 
|  | if (kcf->original_init_upstream(cf, us) != NGX_OK) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | kcf->original_init_peer = us->peer.init; | 
|  |  | 
|  | us->peer.init = ngx_http_upstream_init_keepalive_peer; | 
|  |  | 
|  | /* allocate cache items and add to free queue */ | 
|  |  | 
|  | cached = ngx_pcalloc(cf->pool, | 
|  | sizeof(ngx_http_upstream_keepalive_cache_t) * kcf->max_cached); | 
|  | if (cached == NULL) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | ngx_queue_init(&kcf->cache); | 
|  | ngx_queue_init(&kcf->free); | 
|  |  | 
|  | for (i = 0; i < kcf->max_cached; i++) { | 
|  | ngx_queue_insert_head(&kcf->free, &cached[i].queue); | 
|  | cached[i].conf = kcf; | 
|  | } | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r, | 
|  | ngx_http_upstream_srv_conf_t *us) | 
|  | { | 
|  | ngx_http_upstream_keepalive_peer_data_t  *kp; | 
|  | ngx_http_upstream_keepalive_srv_conf_t   *kcf; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "init keepalive peer"); | 
|  |  | 
|  | kcf = ngx_http_conf_upstream_srv_conf(us, | 
|  | ngx_http_upstream_keepalive_module); | 
|  |  | 
|  | kp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_keepalive_peer_data_t)); | 
|  | if (kp == NULL) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | if (kcf->original_init_peer(r, us) != NGX_OK) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | kp->conf = kcf; | 
|  | kp->upstream = r->upstream; | 
|  | kp->data = r->upstream->peer.data; | 
|  | kp->original_get_peer = r->upstream->peer.get; | 
|  | kp->original_free_peer = r->upstream->peer.free; | 
|  |  | 
|  | r->upstream->peer.data = kp; | 
|  | r->upstream->peer.get = ngx_http_upstream_get_keepalive_peer; | 
|  | r->upstream->peer.free = ngx_http_upstream_free_keepalive_peer; | 
|  |  | 
|  | #if (NGX_HTTP_SSL) | 
|  | kp->original_set_session = r->upstream->peer.set_session; | 
|  | kp->original_save_session = r->upstream->peer.save_session; | 
|  | r->upstream->peer.set_session = ngx_http_upstream_keepalive_set_session; | 
|  | r->upstream->peer.save_session = ngx_http_upstream_keepalive_save_session; | 
|  | #endif | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, void *data) | 
|  | { | 
|  | ngx_http_upstream_keepalive_peer_data_t  *kp = data; | 
|  | ngx_http_upstream_keepalive_cache_t      *item; | 
|  |  | 
|  | ngx_int_t          rc; | 
|  | ngx_queue_t       *q, *cache; | 
|  | ngx_connection_t  *c; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, | 
|  | "get keepalive peer"); | 
|  |  | 
|  | /* ask balancer */ | 
|  |  | 
|  | rc = kp->original_get_peer(pc, kp->data); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* search cache for suitable connection */ | 
|  |  | 
|  | cache = &kp->conf->cache; | 
|  |  | 
|  | for (q = ngx_queue_head(cache); | 
|  | q != ngx_queue_sentinel(cache); | 
|  | q = ngx_queue_next(q)) | 
|  | { | 
|  | item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue); | 
|  | c = item->connection; | 
|  |  | 
|  | if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr, | 
|  | item->socklen, pc->socklen) | 
|  | == 0) | 
|  | { | 
|  | ngx_queue_remove(q); | 
|  | ngx_queue_insert_head(&kp->conf->free, q); | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, | 
|  | "get keepalive peer: using connection %p", c); | 
|  |  | 
|  | c->idle = 0; | 
|  | c->log = pc->log; | 
|  | c->read->log = pc->log; | 
|  | c->write->log = pc->log; | 
|  | c->pool->log = pc->log; | 
|  |  | 
|  | pc->connection = c; | 
|  | pc->cached = 1; | 
|  |  | 
|  | return NGX_DONE; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data, | 
|  | ngx_uint_t state) | 
|  | { | 
|  | ngx_http_upstream_keepalive_peer_data_t  *kp = data; | 
|  | ngx_http_upstream_keepalive_cache_t      *item; | 
|  |  | 
|  | ngx_queue_t          *q; | 
|  | ngx_connection_t     *c; | 
|  | ngx_http_upstream_t  *u; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, | 
|  | "free keepalive peer"); | 
|  |  | 
|  | /* cache valid connections */ | 
|  |  | 
|  | u = kp->upstream; | 
|  | c = pc->connection; | 
|  |  | 
|  | if (state & NGX_PEER_FAILED | 
|  | || c == NULL | 
|  | || c->read->eof | 
|  | || c->read->error | 
|  | || c->read->timedout | 
|  | || c->write->error | 
|  | || c->write->timedout) | 
|  | { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | if (!u->keepalive) { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | if (ngx_handle_read_event(c->read, 0) != NGX_OK) { | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, | 
|  | "free keepalive peer: saving connection %p", c); | 
|  |  | 
|  | if (ngx_queue_empty(&kp->conf->free)) { | 
|  |  | 
|  | q = ngx_queue_last(&kp->conf->cache); | 
|  | ngx_queue_remove(q); | 
|  |  | 
|  | item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue); | 
|  |  | 
|  | ngx_http_upstream_keepalive_close(item->connection); | 
|  |  | 
|  | } else { | 
|  | q = ngx_queue_head(&kp->conf->free); | 
|  | ngx_queue_remove(q); | 
|  |  | 
|  | item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue); | 
|  | } | 
|  |  | 
|  | item->connection = c; | 
|  | ngx_queue_insert_head(&kp->conf->cache, q); | 
|  |  | 
|  | pc->connection = NULL; | 
|  |  | 
|  | if (c->read->timer_set) { | 
|  | ngx_del_timer(c->read); | 
|  | } | 
|  | if (c->write->timer_set) { | 
|  | ngx_del_timer(c->write); | 
|  | } | 
|  |  | 
|  | c->write->handler = ngx_http_upstream_keepalive_dummy_handler; | 
|  | c->read->handler = ngx_http_upstream_keepalive_close_handler; | 
|  |  | 
|  | c->data = item; | 
|  | c->idle = 1; | 
|  | c->log = ngx_cycle->log; | 
|  | c->read->log = ngx_cycle->log; | 
|  | c->write->log = ngx_cycle->log; | 
|  | c->pool->log = ngx_cycle->log; | 
|  |  | 
|  | item->socklen = pc->socklen; | 
|  | ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen); | 
|  |  | 
|  | if (c->read->ready) { | 
|  | ngx_http_upstream_keepalive_close_handler(c->read); | 
|  | } | 
|  |  | 
|  | invalid: | 
|  |  | 
|  | kp->original_free_peer(pc, kp->data, state); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev) | 
|  | { | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, | 
|  | "keepalive dummy handler"); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev) | 
|  | { | 
|  | ngx_http_upstream_keepalive_srv_conf_t  *conf; | 
|  | ngx_http_upstream_keepalive_cache_t     *item; | 
|  |  | 
|  | int                n; | 
|  | char               buf[1]; | 
|  | ngx_connection_t  *c; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, | 
|  | "keepalive close handler"); | 
|  |  | 
|  | c = ev->data; | 
|  |  | 
|  | if (c->close) { | 
|  | goto close; | 
|  | } | 
|  |  | 
|  | n = recv(c->fd, buf, 1, MSG_PEEK); | 
|  |  | 
|  | if (n == -1 && ngx_socket_errno == NGX_EAGAIN) { | 
|  | /* stale event */ | 
|  |  | 
|  | if (ngx_handle_read_event(c->read, 0) != NGX_OK) { | 
|  | goto close; | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | close: | 
|  |  | 
|  | item = c->data; | 
|  | conf = item->conf; | 
|  |  | 
|  | ngx_http_upstream_keepalive_close(c); | 
|  |  | 
|  | ngx_queue_remove(&item->queue); | 
|  | ngx_queue_insert_head(&conf->free, &item->queue); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_upstream_keepalive_close(ngx_connection_t *c) | 
|  | { | 
|  |  | 
|  | #if (NGX_HTTP_SSL) | 
|  |  | 
|  | if (c->ssl) { | 
|  | c->ssl->no_wait_shutdown = 1; | 
|  | c->ssl->no_send_shutdown = 1; | 
|  |  | 
|  | if (ngx_ssl_shutdown(c) == NGX_AGAIN) { | 
|  | c->ssl->handler = ngx_http_upstream_keepalive_close; | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | ngx_destroy_pool(c->pool); | 
|  | ngx_close_connection(c); | 
|  | } | 
|  |  | 
|  |  | 
|  | #if (NGX_HTTP_SSL) | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_upstream_keepalive_set_session(ngx_peer_connection_t *pc, void *data) | 
|  | { | 
|  | ngx_http_upstream_keepalive_peer_data_t  *kp = data; | 
|  |  | 
|  | return kp->original_set_session(pc, kp->data); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc, void *data) | 
|  | { | 
|  | ngx_http_upstream_keepalive_peer_data_t  *kp = data; | 
|  |  | 
|  | kp->original_save_session(pc, kp->data); | 
|  | return; | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  |  | 
|  | static void * | 
|  | ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf) | 
|  | { | 
|  | ngx_http_upstream_keepalive_srv_conf_t  *conf; | 
|  |  | 
|  | conf = ngx_pcalloc(cf->pool, | 
|  | sizeof(ngx_http_upstream_keepalive_srv_conf_t)); | 
|  | if (conf == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * set by ngx_pcalloc(): | 
|  | * | 
|  | *     conf->original_init_upstream = NULL; | 
|  | *     conf->original_init_peer = NULL; | 
|  | */ | 
|  |  | 
|  | conf->max_cached = 1; | 
|  |  | 
|  | return conf; | 
|  | } | 
|  |  | 
|  |  | 
|  | static char * | 
|  | ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | 
|  | { | 
|  | ngx_http_upstream_srv_conf_t            *uscf; | 
|  | ngx_http_upstream_keepalive_srv_conf_t  *kcf = conf; | 
|  |  | 
|  | ngx_int_t    n; | 
|  | ngx_str_t   *value; | 
|  | ngx_uint_t   i; | 
|  |  | 
|  | uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); | 
|  |  | 
|  | if (kcf->original_init_upstream) { | 
|  | return "is duplicate"; | 
|  | } | 
|  |  | 
|  | kcf->original_init_upstream = uscf->peer.init_upstream | 
|  | ? uscf->peer.init_upstream | 
|  | : ngx_http_upstream_init_round_robin; | 
|  |  | 
|  | uscf->peer.init_upstream = ngx_http_upstream_init_keepalive; | 
|  |  | 
|  | /* read options */ | 
|  |  | 
|  | value = cf->args->elts; | 
|  |  | 
|  | n = ngx_atoi(value[1].data, value[1].len); | 
|  |  | 
|  | if (n == NGX_ERROR || n == 0) { | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "invalid value \"%V\" in \"%V\" directive", | 
|  | &value[1], &cmd->name); | 
|  | return NGX_CONF_ERROR; | 
|  | } | 
|  |  | 
|  | kcf->max_cached = n; | 
|  |  | 
|  | for (i = 2; i < cf->args->nelts; i++) { | 
|  |  | 
|  | if (ngx_strcmp(value[i].data, "single") == 0) { | 
|  | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, | 
|  | "the \"single\" parameter is deprecated"); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | goto invalid; | 
|  | } | 
|  |  | 
|  | return NGX_CONF_OK; | 
|  |  | 
|  | invalid: | 
|  |  | 
|  | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
|  | "invalid parameter \"%V\"", &value[i]); | 
|  |  | 
|  | return NGX_CONF_ERROR; | 
|  | } |