upstream nginx-0.7.31
[nginx.git] / nginx / src / http / modules / ngx_http_memcached_module.c
1
2 /*
3  * Copyright (C) Igor Sysoev
4  */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10
11
12 typedef struct {
13     ngx_http_upstream_conf_t   upstream;
14     ngx_int_t                  index;
15 } ngx_http_memcached_loc_conf_t;
16
17
18 typedef struct {
19     size_t                     rest;
20     ngx_http_request_t        *request;
21     ngx_str_t                  key;
22 } ngx_http_memcached_ctx_t;
23
24
25 static ngx_int_t ngx_http_memcached_create_request(ngx_http_request_t *r);
26 static ngx_int_t ngx_http_memcached_reinit_request(ngx_http_request_t *r);
27 static ngx_int_t ngx_http_memcached_process_header(ngx_http_request_t *r);
28 static ngx_int_t ngx_http_memcached_filter_init(void *data);
29 static ngx_int_t ngx_http_memcached_filter(void *data, ssize_t bytes);
30 static void ngx_http_memcached_abort_request(ngx_http_request_t *r);
31 static void ngx_http_memcached_finalize_request(ngx_http_request_t *r,
32     ngx_int_t rc);
33
34 static void *ngx_http_memcached_create_loc_conf(ngx_conf_t *cf);
35 static char *ngx_http_memcached_merge_loc_conf(ngx_conf_t *cf,
36     void *parent, void *child);
37
38 static char *ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd,
39     void *conf);
40
41 static char *ngx_http_memcached_upstream_max_fails_unsupported(ngx_conf_t *cf,
42     ngx_command_t *cmd, void *conf);
43 static char *ngx_http_memcached_upstream_fail_timeout_unsupported(ngx_conf_t *cf,
44     ngx_command_t *cmd, void *conf);
45
46
47 static ngx_conf_bitmask_t  ngx_http_memcached_next_upstream_masks[] = {
48     { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
49     { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
50     { ngx_string("invalid_response"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
51     { ngx_string("not_found"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
52     { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
53     { ngx_null_string, 0 }
54 };
55
56
57 static ngx_command_t  ngx_http_memcached_commands[] = {
58
59     { ngx_string("memcached_pass"),
60       NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
61       ngx_http_memcached_pass,
62       NGX_HTTP_LOC_CONF_OFFSET,
63       0,
64       NULL },
65
66     { ngx_string("memcached_connect_timeout"),
67       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
68       ngx_conf_set_msec_slot,
69       NGX_HTTP_LOC_CONF_OFFSET,
70       offsetof(ngx_http_memcached_loc_conf_t, upstream.connect_timeout),
71       NULL },
72
73     { ngx_string("memcached_send_timeout"),
74       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
75       ngx_conf_set_msec_slot,
76       NGX_HTTP_LOC_CONF_OFFSET,
77       offsetof(ngx_http_memcached_loc_conf_t, upstream.send_timeout),
78       NULL },
79
80     { ngx_string("memcached_buffer_size"),
81       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
82       ngx_conf_set_size_slot,
83       NGX_HTTP_LOC_CONF_OFFSET,
84       offsetof(ngx_http_memcached_loc_conf_t, upstream.buffer_size),
85       NULL },
86
87     { ngx_string("memcached_read_timeout"),
88       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
89       ngx_conf_set_msec_slot,
90       NGX_HTTP_LOC_CONF_OFFSET,
91       offsetof(ngx_http_memcached_loc_conf_t, upstream.read_timeout),
92       NULL },
93
94     { ngx_string("memcached_next_upstream"),
95       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
96       ngx_conf_set_bitmask_slot,
97       NGX_HTTP_LOC_CONF_OFFSET,
98       offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream),
99       &ngx_http_memcached_next_upstream_masks },
100
101     { ngx_string("memcached_upstream_max_fails"),
102       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
103       ngx_http_memcached_upstream_max_fails_unsupported,
104       0,
105       0,
106       NULL },
107
108     { ngx_string("memcached_upstream_fail_timeout"),
109       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
110       ngx_http_memcached_upstream_fail_timeout_unsupported,
111       0,
112       0,
113       NULL },
114
115       ngx_null_command
116 };
117
118
119 static ngx_http_module_t  ngx_http_memcached_module_ctx = {
120     NULL,                                  /* preconfiguration */
121     NULL,                                  /* postconfiguration */
122
123     NULL,                                  /* create main configuration */
124     NULL,                                  /* init main configuration */
125
126     NULL,                                  /* create server configuration */
127     NULL,                                  /* merge server configuration */
128
129     ngx_http_memcached_create_loc_conf,    /* create location configration */
130     ngx_http_memcached_merge_loc_conf      /* merge location configration */
131 };
132
133
134 ngx_module_t  ngx_http_memcached_module = {
135     NGX_MODULE_V1,
136     &ngx_http_memcached_module_ctx,        /* module context */
137     ngx_http_memcached_commands,           /* module directives */
138     NGX_HTTP_MODULE,                       /* module type */
139     NULL,                                  /* init master */
140     NULL,                                  /* init module */
141     NULL,                                  /* init process */
142     NULL,                                  /* init thread */
143     NULL,                                  /* exit thread */
144     NULL,                                  /* exit process */
145     NULL,                                  /* exit master */
146     NGX_MODULE_V1_PADDING
147 };
148
149
150 static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
151
152
153 #define NGX_HTTP_MEMCACHED_END   (sizeof(ngx_http_memcached_end) - 1)
154 static u_char  ngx_http_memcached_end[] = CRLF "END" CRLF;
155
156
157 static ngx_int_t
158 ngx_http_memcached_handler(ngx_http_request_t *r)
159 {
160     ngx_int_t                       rc;
161     ngx_http_upstream_t            *u;
162     ngx_http_memcached_ctx_t       *ctx;
163     ngx_http_memcached_loc_conf_t  *mlcf;
164
165     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
166         return NGX_HTTP_NOT_ALLOWED;
167     }
168
169     rc = ngx_http_discard_request_body(r);
170
171     if (rc != NGX_OK) {
172         return rc;
173     }
174
175     if (ngx_http_set_content_type(r) != NGX_OK) {
176         return NGX_HTTP_INTERNAL_SERVER_ERROR;
177     }
178
179     mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
180
181     u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
182     if (u == NULL) {
183         return NGX_HTTP_INTERNAL_SERVER_ERROR;
184     }
185
186     u->schema.len = sizeof("memcached://") - 1;
187     u->schema.data = (u_char *) "memcached://";
188
189     u->peer.log = r->connection->log;
190     u->peer.log_error = NGX_ERROR_ERR;
191 #if (NGX_THREADS)
192     u->peer.lock = &r->connection->lock;
193 #endif
194
195     u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
196
197     u->conf = &mlcf->upstream;
198
199     u->create_request = ngx_http_memcached_create_request;
200     u->reinit_request = ngx_http_memcached_reinit_request;
201     u->process_header = ngx_http_memcached_process_header;
202     u->abort_request = ngx_http_memcached_abort_request;
203     u->finalize_request = ngx_http_memcached_finalize_request;
204
205     r->upstream = u;
206
207     ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
208     if (ctx == NULL) {
209         return NGX_HTTP_INTERNAL_SERVER_ERROR;
210     }
211
212     ctx->rest = NGX_HTTP_MEMCACHED_END;
213     ctx->request = r;
214
215     ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
216
217     u->input_filter_init = ngx_http_memcached_filter_init;
218     u->input_filter = ngx_http_memcached_filter;
219     u->input_filter_ctx = ctx;
220
221     ngx_http_upstream_init(r);
222
223     return NGX_DONE;
224 }
225
226
227 static ngx_int_t
228 ngx_http_memcached_create_request(ngx_http_request_t *r)
229 {
230     size_t                          len;
231     uintptr_t                       escape;
232     ngx_buf_t                      *b;
233     ngx_chain_t                    *cl;
234     ngx_http_memcached_ctx_t       *ctx;
235     ngx_http_variable_value_t      *vv;
236     ngx_http_memcached_loc_conf_t  *mlcf;
237
238     mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
239
240     vv = ngx_http_get_indexed_variable(r, mlcf->index);
241
242     if (vv == NULL || vv->not_found || vv->len == 0) {
243         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
244                       "the \"$memcached_key\" variable is not set");
245         return NGX_ERROR;
246     }
247
248     escape = 2 * ngx_escape_uri(NULL, vv->data, vv->len, NGX_ESCAPE_MEMCACHED);
249
250     len = sizeof("get ") - 1 + vv->len + escape + sizeof(CRLF) - 1;
251
252     b = ngx_create_temp_buf(r->pool, len);
253     if (b == NULL) {
254         return NGX_ERROR;
255     }
256
257     cl = ngx_alloc_chain_link(r->pool);
258     if (cl == NULL) {
259         return NGX_ERROR;
260     }
261
262     cl->buf = b;
263     cl->next = NULL;
264
265     r->upstream->request_bufs = cl;
266
267     *b->last++ = 'g'; *b->last++ = 'e'; *b->last++ = 't'; *b->last++ = ' ';
268
269     ctx = ngx_http_get_module_ctx(r, ngx_http_memcached_module);
270
271     ctx->key.data = b->last;
272
273     if (escape == 0) {
274         b->last = ngx_copy(b->last, vv->data, vv->len);
275
276     } else {
277         b->last = (u_char *) ngx_escape_uri(b->last, vv->data, vv->len,
278                                             NGX_ESCAPE_MEMCACHED);
279     }
280
281     ctx->key.len = b->last - ctx->key.data;
282
283     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
284                    "http memcached request: \"%V\"", &ctx->key);
285
286     *b->last++ = CR; *b->last++ = LF;
287
288     return NGX_OK;
289 }
290
291
292 static ngx_int_t
293 ngx_http_memcached_reinit_request(ngx_http_request_t *r)
294 {
295     return NGX_OK;
296 }
297
298
299 static ngx_int_t
300 ngx_http_memcached_process_header(ngx_http_request_t *r)
301 {
302     u_char                    *p, *len;
303     ngx_str_t                  line;
304     ngx_http_upstream_t       *u;
305     ngx_http_memcached_ctx_t  *ctx;
306
307     u = r->upstream;
308
309     for (p = u->buffer.pos; p < u->buffer.last; p++) {
310         if (*p == LF) {
311             goto found;
312         }
313     }
314
315     return NGX_AGAIN;
316
317 found:
318
319     *p = '\0';
320
321     line.len = p - u->buffer.pos - 1;
322     line.data = u->buffer.pos;
323
324     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
325                    "memcached: \"%V\"", &line);
326
327     p = u->buffer.pos;
328
329     ctx = ngx_http_get_module_ctx(r, ngx_http_memcached_module);
330
331     if (ngx_strncmp(p, "VALUE ", sizeof("VALUE ") - 1) == 0) {
332
333         p += sizeof("VALUE ") - 1;
334
335         if (ngx_strncmp(p, ctx->key.data, ctx->key.len) != 0) {
336             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
337                           "memcached sent invalid key in response \"%V\" "
338                           "for key \"%V\"",
339                           &line, &ctx->key);
340
341             return NGX_HTTP_UPSTREAM_INVALID_HEADER;
342         }
343
344         p += ctx->key.len;
345
346         if (*p++ != ' ') {
347             goto no_valid;
348         }
349
350         /* skip flags */
351
352         while (*p) {
353             if (*p++ == ' ') {
354                 goto length;
355             }
356         }
357
358         goto no_valid;
359
360     length:
361
362         len = p;
363
364         while (*p && *p++ != CR) { /* void */ }
365
366         r->headers_out.content_length_n = ngx_atoof(len, p - len - 1);
367         if (r->headers_out.content_length_n == -1) {
368             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
369                           "memcached sent invalid length in response \"%V\" "
370                           "for key \"%V\"",
371                           &line, &ctx->key);
372             return NGX_HTTP_UPSTREAM_INVALID_HEADER;
373         }
374
375         u->headers_in.status_n = 200;
376         u->state->status = 200;
377         u->buffer.pos = p + 1;
378
379         return NGX_OK;
380     }
381
382     if (ngx_strcmp(p, "END\x0d") == 0) {
383         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
384                       "key: \"%V\" was not found by memcached", &ctx->key);
385
386         u->headers_in.status_n = 404;
387         u->state->status = 404;
388
389         return NGX_OK;
390     }
391
392 no_valid:
393
394     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
395                   "memcached sent invalid response: \"%V\"", &line);
396
397     return NGX_HTTP_UPSTREAM_INVALID_HEADER;
398 }
399
400
401 static ngx_int_t
402 ngx_http_memcached_filter_init(void *data)
403 {
404     ngx_http_memcached_ctx_t  *ctx = data;
405
406     ngx_http_upstream_t  *u;
407
408     u = ctx->request->upstream;
409
410     u->length += NGX_HTTP_MEMCACHED_END;
411
412     return NGX_OK;
413 }
414
415
416 static ngx_int_t
417 ngx_http_memcached_filter(void *data, ssize_t bytes)
418 {
419     ngx_http_memcached_ctx_t  *ctx = data;
420
421     u_char               *last;
422     ngx_buf_t            *b;
423     ngx_chain_t          *cl, **ll;
424     ngx_http_upstream_t  *u;
425
426     u = ctx->request->upstream;
427     b = &u->buffer;
428
429     if (u->length == ctx->rest) {
430
431         if (ngx_strncmp(b->last,
432                    ngx_http_memcached_end + NGX_HTTP_MEMCACHED_END - ctx->rest,
433                    ctx->rest)
434             != 0)
435         {
436             ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
437                           "memcached sent invalid trailer");
438         }
439
440         u->length = 0;
441         ctx->rest = 0;
442
443         return NGX_OK;
444     }
445
446     for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
447         ll = &cl->next;
448     }
449
450     cl = ngx_chain_get_free_buf(ctx->request->pool, &u->free_bufs);
451     if (cl == NULL) {
452         return NGX_ERROR;
453     }
454
455     cl->buf->flush = 1;
456     cl->buf->memory = 1;
457
458     *ll = cl;
459
460     last = b->last;
461     cl->buf->pos = last;
462     b->last += bytes;
463     cl->buf->last = b->last;
464     cl->buf->tag = u->output.tag;
465
466     ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
467                    "memcached filter bytes:%z size:%z length:%z rest:%z",
468                    bytes, b->last - b->pos, u->length, ctx->rest);
469
470     if (bytes <= (ssize_t) (u->length - NGX_HTTP_MEMCACHED_END)) {
471         u->length -= bytes;
472         return NGX_OK;
473     }
474
475     last += u->length - NGX_HTTP_MEMCACHED_END;
476
477     if (ngx_strncmp(last, ngx_http_memcached_end, b->last - last) != 0) {
478         ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
479                       "memcached sent invalid trailer");
480     }
481
482     ctx->rest -= b->last - last;
483     b->last = last;
484     cl->buf->last = last;
485     u->length = ctx->rest;
486
487     return NGX_OK;
488 }
489
490
491 static void
492 ngx_http_memcached_abort_request(ngx_http_request_t *r)
493 {
494     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
495                    "abort http memcached request");
496     return;
497 }
498
499
500 static void
501 ngx_http_memcached_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
502 {
503     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
504                    "finalize http memcached request");
505     return;
506 }
507
508
509 static void *
510 ngx_http_memcached_create_loc_conf(ngx_conf_t *cf)
511 {
512     ngx_http_memcached_loc_conf_t  *conf;
513
514     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_memcached_loc_conf_t));
515     if (conf == NULL) {
516         return NGX_CONF_ERROR;
517     }
518
519     /*
520      * set by ngx_pcalloc():
521      *
522      *     conf->upstream.bufs.num = 0;
523      *     conf->upstream.next_upstream = 0;
524      *     conf->upstream.temp_path = NULL;
525      *     conf->upstream.uri = { 0, NULL };
526      *     conf->upstream.location = NULL;
527      */
528
529     conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
530     conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
531     conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
532
533     conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
534
535     /* the hardcoded values */
536     conf->upstream.cyclic_temp_file = 0;
537     conf->upstream.buffering = 0;
538     conf->upstream.ignore_client_abort = 0;
539     conf->upstream.send_lowat = 0;
540     conf->upstream.bufs.num = 0;
541     conf->upstream.busy_buffers_size = 0;
542     conf->upstream.max_temp_file_size = 0;
543     conf->upstream.temp_file_write_size = 0;
544     conf->upstream.intercept_errors = 1;
545     conf->upstream.intercept_404 = 1;
546     conf->upstream.pass_request_headers = 0;
547     conf->upstream.pass_request_body = 0;
548
549     conf->index = NGX_CONF_UNSET;
550
551     return conf;
552 }
553
554
555 static char *
556 ngx_http_memcached_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
557 {
558     ngx_http_memcached_loc_conf_t *prev = parent;
559     ngx_http_memcached_loc_conf_t *conf = child;
560
561     ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
562                               prev->upstream.connect_timeout, 60000);
563
564     ngx_conf_merge_msec_value(conf->upstream.send_timeout,
565                               prev->upstream.send_timeout, 60000);
566
567     ngx_conf_merge_msec_value(conf->upstream.read_timeout,
568                               prev->upstream.read_timeout, 60000);
569
570     ngx_conf_merge_size_value(conf->upstream.buffer_size,
571                               prev->upstream.buffer_size,
572                               (size_t) ngx_pagesize);
573
574     ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
575                               prev->upstream.next_upstream,
576                               (NGX_CONF_BITMASK_SET
577                                |NGX_HTTP_UPSTREAM_FT_ERROR
578                                |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
579
580     if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
581         conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
582                                        |NGX_HTTP_UPSTREAM_FT_OFF;
583     }
584
585     if (conf->upstream.upstream == NULL) {
586         conf->upstream.upstream = prev->upstream.upstream;
587     }
588
589     if (conf->index == NGX_CONF_UNSET) {
590         conf->index = prev->index;
591     }
592
593     return NGX_CONF_OK;
594 }
595
596
597 static char *
598 ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
599 {
600     ngx_http_memcached_loc_conf_t *mlcf = conf;
601
602     ngx_str_t                 *value;
603     ngx_url_t                  u;
604     ngx_http_core_loc_conf_t  *clcf;
605
606     if (mlcf->upstream.upstream) {
607         return "is duplicate";
608     }
609
610     value = cf->args->elts;
611
612     ngx_memzero(&u, sizeof(ngx_url_t));
613
614     u.url = value[1];
615     u.no_resolve = 1;
616
617     mlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
618     if (mlcf->upstream.upstream == NULL) {
619         return NGX_CONF_ERROR;
620     }
621
622     clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
623
624     clcf->handler = ngx_http_memcached_handler;
625
626     if (clcf->name.data[clcf->name.len - 1] == '/') {
627         clcf->auto_redirect = 1;
628     }
629
630     mlcf->index = ngx_http_get_variable_index(cf, &ngx_http_memcached_key);
631
632     if (mlcf->index == NGX_ERROR) {
633         return NGX_CONF_ERROR;
634     }
635
636     return NGX_CONF_OK;
637 }
638
639
640 static char *
641 ngx_http_memcached_upstream_max_fails_unsupported(ngx_conf_t *cf,
642     ngx_command_t *cmd, void *conf)
643 {
644     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
645          "\"memcached_upstream_max_fails\" is not supported, "
646          "use the \"max_fails\" parameter of the \"server\" directive ",
647          "inside the \"upstream\" block");
648
649     return NGX_CONF_ERROR;
650 }
651
652
653 static char *
654 ngx_http_memcached_upstream_fail_timeout_unsupported(ngx_conf_t *cf,
655     ngx_command_t *cmd, void *conf)
656 {
657     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
658          "\"memcached_upstream_fail_timeout\" is not supported, "
659          "use the \"fail_timeout\" parameter of the \"server\" directive ",
660          "inside the \"upstream\" block");
661
662     return NGX_CONF_ERROR;
663 }