1
2/*
3 * Copyright (C) Maxim Dounin
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8#include <ngx_config.h>
9#include <ngx_core.h>
10#include <ngx_http.h>
11
12
13typedef struct {
14    ngx_uint_t                         max_cached;
15
16    ngx_queue_t                        cache;
17    ngx_queue_t                        free;
18
19    ngx_http_upstream_init_pt          original_init_upstream;
20    ngx_http_upstream_init_peer_pt     original_init_peer;
21
22} ngx_http_upstream_keepalive_srv_conf_t;
23
24
25typedef struct {
26    ngx_http_upstream_keepalive_srv_conf_t  *conf;
27
28    ngx_queue_t                        queue;
29    ngx_connection_t                  *connection;
30
31    socklen_t                          socklen;
32    ngx_sockaddr_t                     sockaddr;
33
34} ngx_http_upstream_keepalive_cache_t;
35
36
37typedef struct {
38    ngx_http_upstream_keepalive_srv_conf_t  *conf;
39
40    ngx_http_upstream_t               *upstream;
41
42    void                              *data;
43
44    ngx_event_get_peer_pt              original_get_peer;
45    ngx_event_free_peer_pt             original_free_peer;
46
47#if (NGX_HTTP_SSL)
48    ngx_event_set_peer_session_pt      original_set_session;
49    ngx_event_save_peer_session_pt     original_save_session;
50#endif
51
52} ngx_http_upstream_keepalive_peer_data_t;
53
54
55static ngx_int_t ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
56    ngx_http_upstream_srv_conf_t *us);
57static ngx_int_t ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc,
58    void *data);
59static void ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc,
60    void *data, ngx_uint_t state);
61
62static void ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev);
63static void ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev);
64static void ngx_http_upstream_keepalive_close(ngx_connection_t *c);
65
66#if (NGX_HTTP_SSL)
67static ngx_int_t ngx_http_upstream_keepalive_set_session(
68    ngx_peer_connection_t *pc, void *data);
69static void ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc,
70    void *data);
71#endif
72
73static void *ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf);
74static char *ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd,
75    void *conf);
76
77
78static ngx_command_t  ngx_http_upstream_keepalive_commands[] = {
79
80    { ngx_string("keepalive"),
81      NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
82      ngx_http_upstream_keepalive,
83      NGX_HTTP_SRV_CONF_OFFSET,
84      0,
85      NULL },
86
87      ngx_null_command
88};
89
90
91static ngx_http_module_t  ngx_http_upstream_keepalive_module_ctx = {
92    NULL,                                  /* preconfiguration */
93    NULL,                                  /* postconfiguration */
94
95    NULL,                                  /* create main configuration */
96    NULL,                                  /* init main configuration */
97
98    ngx_http_upstream_keepalive_create_conf, /* create server configuration */
99    NULL,                                  /* merge server configuration */
100
101    NULL,                                  /* create location configuration */
102    NULL                                   /* merge location configuration */
103};
104
105
106ngx_module_t  ngx_http_upstream_keepalive_module = {
107    NGX_MODULE_V1,
108    &ngx_http_upstream_keepalive_module_ctx, /* module context */
109    ngx_http_upstream_keepalive_commands,    /* module directives */
110    NGX_HTTP_MODULE,                       /* module type */
111    NULL,                                  /* init master */
112    NULL,                                  /* init module */
113    NULL,                                  /* init process */
114    NULL,                                  /* init thread */
115    NULL,                                  /* exit thread */
116    NULL,                                  /* exit process */
117    NULL,                                  /* exit master */
118    NGX_MODULE_V1_PADDING
119};
120
121
122static ngx_int_t
123ngx_http_upstream_init_keepalive(ngx_conf_t *cf,
124    ngx_http_upstream_srv_conf_t *us)
125{
126    ngx_uint_t                               i;
127    ngx_http_upstream_keepalive_srv_conf_t  *kcf;
128    ngx_http_upstream_keepalive_cache_t     *cached;
129
130    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
131                   "init keepalive");
132
133    kcf = ngx_http_conf_upstream_srv_conf(us,
134                                          ngx_http_upstream_keepalive_module);
135
136    if (kcf->original_init_upstream(cf, us) != NGX_OK) {
137        return NGX_ERROR;
138    }
139
140    kcf->original_init_peer = us->peer.init;
141
142    us->peer.init = ngx_http_upstream_init_keepalive_peer;
143
144    /* allocate cache items and add to free queue */
145
146    cached = ngx_pcalloc(cf->pool,
147                sizeof(ngx_http_upstream_keepalive_cache_t) * kcf->max_cached);
148    if (cached == NULL) {
149        return NGX_ERROR;
150    }
151
152    ngx_queue_init(&kcf->cache);
153    ngx_queue_init(&kcf->free);
154
155    for (i = 0; i < kcf->max_cached; i++) {
156        ngx_queue_insert_head(&kcf->free, &cached[i].queue);
157        cached[i].conf = kcf;
158    }
159
160    return NGX_OK;
161}
162
163
164static ngx_int_t
165ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
166    ngx_http_upstream_srv_conf_t *us)
167{
168    ngx_http_upstream_keepalive_peer_data_t  *kp;
169    ngx_http_upstream_keepalive_srv_conf_t   *kcf;
170
171    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
172                   "init keepalive peer");
173
174    kcf = ngx_http_conf_upstream_srv_conf(us,
175                                          ngx_http_upstream_keepalive_module);
176
177    kp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_keepalive_peer_data_t));
178    if (kp == NULL) {
179        return NGX_ERROR;
180    }
181
182    if (kcf->original_init_peer(r, us) != NGX_OK) {
183        return NGX_ERROR;
184    }
185
186    kp->conf = kcf;
187    kp->upstream = r->upstream;
188    kp->data = r->upstream->peer.data;
189    kp->original_get_peer = r->upstream->peer.get;
190    kp->original_free_peer = r->upstream->peer.free;
191
192    r->upstream->peer.data = kp;
193    r->upstream->peer.get = ngx_http_upstream_get_keepalive_peer;
194    r->upstream->peer.free = ngx_http_upstream_free_keepalive_peer;
195
196#if (NGX_HTTP_SSL)
197    kp->original_set_session = r->upstream->peer.set_session;
198    kp->original_save_session = r->upstream->peer.save_session;
199    r->upstream->peer.set_session = ngx_http_upstream_keepalive_set_session;
200    r->upstream->peer.save_session = ngx_http_upstream_keepalive_save_session;
201#endif
202
203    return NGX_OK;
204}
205
206
207static ngx_int_t
208ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, void *data)
209{
210    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
211    ngx_http_upstream_keepalive_cache_t      *item;
212
213    ngx_int_t          rc;
214    ngx_queue_t       *q, *cache;
215    ngx_connection_t  *c;
216
217    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
218                   "get keepalive peer");
219
220    /* ask balancer */
221
222    rc = kp->original_get_peer(pc, kp->data);
223
224    if (rc != NGX_OK) {
225        return rc;
226    }
227
228    /* search cache for suitable connection */
229
230    cache = &kp->conf->cache;
231
232    for (q = ngx_queue_head(cache);
233         q != ngx_queue_sentinel(cache);
234         q = ngx_queue_next(q))
235    {
236        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
237        c = item->connection;
238
239        if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
240                         item->socklen, pc->socklen)
241            == 0)
242        {
243            ngx_queue_remove(q);
244            ngx_queue_insert_head(&kp->conf->free, q);
245
246            goto found;
247        }
248    }
249
250    return NGX_OK;
251
252found:
253
254    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
255                   "get keepalive peer: using connection %p", c);
256
257    c->idle = 0;
258    c->sent = 0;
259    c->log = pc->log;
260    c->read->log = pc->log;
261    c->write->log = pc->log;
262    c->pool->log = pc->log;
263
264    pc->connection = c;
265    pc->cached = 1;
266
267    return NGX_DONE;
268}
269
270
271static void
272ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data,
273    ngx_uint_t state)
274{
275    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
276    ngx_http_upstream_keepalive_cache_t      *item;
277
278    ngx_queue_t          *q;
279    ngx_connection_t     *c;
280    ngx_http_upstream_t  *u;
281
282    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
283                   "free keepalive peer");
284
285    /* cache valid connections */
286
287    u = kp->upstream;
288    c = pc->connection;
289
290    if (state & NGX_PEER_FAILED
291        || c == NULL
292        || c->read->eof
293        || c->read->error
294        || c->read->timedout
295        || c->write->error
296        || c->write->timedout)
297    {
298        goto invalid;
299    }
300
301    if (!u->keepalive) {
302        goto invalid;
303    }
304
305    if (!u->request_body_sent) {
306        goto invalid;
307    }
308
309    if (ngx_terminate || ngx_exiting) {
310        goto invalid;
311    }
312
313    if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
314        goto invalid;
315    }
316
317    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
318                   "free keepalive peer: saving connection %p", c);
319
320    if (ngx_queue_empty(&kp->conf->free)) {
321
322        q = ngx_queue_last(&kp->conf->cache);
323        ngx_queue_remove(q);
324
325        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
326
327        ngx_http_upstream_keepalive_close(item->connection);
328
329    } else {
330        q = ngx_queue_head(&kp->conf->free);
331        ngx_queue_remove(q);
332
333        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
334    }
335
336    ngx_queue_insert_head(&kp->conf->cache, q);
337
338    item->connection = c;
339
340    pc->connection = NULL;
341
342    if (c->read->timer_set) {
343        ngx_del_timer(c->read);
344    }
345    if (c->write->timer_set) {
346        ngx_del_timer(c->write);
347    }
348
349    c->write->handler = ngx_http_upstream_keepalive_dummy_handler;
350    c->read->handler = ngx_http_upstream_keepalive_close_handler;
351
352    c->data = item;
353    c->idle = 1;
354    c->log = ngx_cycle->log;
355    c->read->log = ngx_cycle->log;
356    c->write->log = ngx_cycle->log;
357    c->pool->log = ngx_cycle->log;
358
359    item->socklen = pc->socklen;
360    ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen);
361
362    if (c->read->ready) {
363        ngx_http_upstream_keepalive_close_handler(c->read);
364    }
365
366invalid:
367
368    kp->original_free_peer(pc, kp->data, state);
369}
370
371
372static void
373ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev)
374{
375    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
376                   "keepalive dummy handler");
377}
378
379
380static void
381ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev)
382{
383    ngx_http_upstream_keepalive_srv_conf_t  *conf;
384    ngx_http_upstream_keepalive_cache_t     *item;
385
386    int                n;
387    char               buf[1];
388    ngx_connection_t  *c;
389
390    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
391                   "keepalive close handler");
392
393    c = ev->data;
394
395    if (c->close) {
396        goto close;
397    }
398
399    n = recv(c->fd, buf, 1, MSG_PEEK);
400
401    if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
402        ev->ready = 0;
403
404        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
405            goto close;
406        }
407
408        return;
409    }
410
411close:
412
413    item = c->data;
414    conf = item->conf;
415
416    ngx_http_upstream_keepalive_close(c);
417
418    ngx_queue_remove(&item->queue);
419    ngx_queue_insert_head(&conf->free, &item->queue);
420}
421
422
423static void
424ngx_http_upstream_keepalive_close(ngx_connection_t *c)
425{
426
427#if (NGX_HTTP_SSL)
428
429    if (c->ssl) {
430        c->ssl->no_wait_shutdown = 1;
431        c->ssl->no_send_shutdown = 1;
432
433        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
434            c->ssl->handler = ngx_http_upstream_keepalive_close;
435            return;
436        }
437    }
438
439#endif
440
441    ngx_destroy_pool(c->pool);
442    ngx_close_connection(c);
443}
444
445
446#if (NGX_HTTP_SSL)
447
448static ngx_int_t
449ngx_http_upstream_keepalive_set_session(ngx_peer_connection_t *pc, void *data)
450{
451    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
452
453    return kp->original_set_session(pc, kp->data);
454}
455
456
457static void
458ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc, void *data)
459{
460    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
461
462    kp->original_save_session(pc, kp->data);
463    return;
464}
465
466#endif
467
468
469static void *
470ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf)
471{
472    ngx_http_upstream_keepalive_srv_conf_t  *conf;
473
474    conf = ngx_pcalloc(cf->pool,
475                       sizeof(ngx_http_upstream_keepalive_srv_conf_t));
476    if (conf == NULL) {
477        return NULL;
478    }
479
480    /*
481     * set by ngx_pcalloc():
482     *
483     *     conf->original_init_upstream = NULL;
484     *     conf->original_init_peer = NULL;
485     *     conf->max_cached = 0;
486     */
487
488    return conf;
489}
490
491
492static char *
493ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
494{
495    ngx_http_upstream_srv_conf_t            *uscf;
496    ngx_http_upstream_keepalive_srv_conf_t  *kcf = conf;
497
498    ngx_int_t    n;
499    ngx_str_t   *value;
500
501    if (kcf->max_cached) {
502        return "is duplicate";
503    }
504
505    /* read options */
506
507    value = cf->args->elts;
508
509    n = ngx_atoi(value[1].data, value[1].len);
510
511    if (n == NGX_ERROR || n == 0) {
512        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
513                           "invalid value \"%V\" in \"%V\" directive",
514                           &value[1], &cmd->name);
515        return NGX_CONF_ERROR;
516    }
517
518    kcf->max_cached = n;
519
520    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
521
522    kcf->original_init_upstream = uscf->peer.init_upstream
523                                  ? uscf->peer.init_upstream
524                                  : ngx_http_upstream_init_round_robin;
525
526    uscf->peer.init_upstream = ngx_http_upstream_init_keepalive;
527
528    return NGX_CONF_OK;
529}
530