blob: 7ff7cec5e145726d5e21d7f3b369e4daa8a77c65 [file] [log] [blame]
William Lallemand41db4602017-10-30 11:15:51 +01001/*
2 * Cache management
3 *
4 * Copyright 2017 HAProxy Technologies
5 * William Lallemand <wlallemand@haproxy.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 */
12
William Lallemand41db4602017-10-30 11:15:51 +010013#include <eb32tree.h>
14
15#include <proto/channel.h>
16#include <proto/proxy.h>
17#include <proto/hdr_idx.h>
18#include <proto/filters.h>
19#include <proto/proto_http.h>
20#include <proto/log.h>
21#include <proto/stream.h>
22#include <proto/stream_interface.h>
23#include <proto/shctx.h>
24
25#include <types/action.h>
26#include <types/filters.h>
27#include <types/proxy.h>
28#include <types/shctx.h>
29
30#include <common/cfgparse.h>
31#include <common/hash.h>
32
33/* flt_cache_store */
34
35static const char *cache_store_flt_id = "cache store filter";
36
William Lallemand4da3f8a2017-10-31 14:33:34 +010037static struct pool_head *pool2_cache_st = NULL;
38
William Lallemand41db4602017-10-30 11:15:51 +010039struct applet http_cache_applet;
40
41struct flt_ops cache_ops;
42
43struct cache {
44 char id[33]; /* cache name */
45 unsigned int maxage; /* max-age */
46 unsigned int maxblocks;
47 struct list list; /* cache linked list */
48 struct eb_root entries; /* head of cache entries based on keys */
49};
50
51/*
52 * cache ctx for filters
53 */
54struct cache_st {
55 int hdrs_len;
56 struct shared_block *first_block;
57};
58
59struct cache_entry {
60 unsigned int latest_validation; /* latest validation date */
61 unsigned int expire; /* expiration date */
62 struct eb32_node eb; /* ebtree node used to hold the cache object */
63 unsigned char data[0];
64};
65
66#define CACHE_BLOCKSIZE 1024
67
68static struct list caches = LIST_HEAD_INIT(caches);
69static struct cache *tmp_cache_config = NULL;
70
William Lallemand4da3f8a2017-10-31 14:33:34 +010071struct cache_entry *entry_exist(struct cache *cache, struct cache_entry *new_entry)
72{
73 struct eb32_node *node;
74 struct cache_entry *entry;
75
76 node = eb32_lookup(&cache->entries, new_entry->eb.key);
77 if (!node)
78 return NULL;
79
80 entry = eb32_entry(node, struct cache_entry, eb);
81 if (entry->expire > now.tv_sec)
82 return entry;
83 else
84 eb32_delete(node);
85 return NULL;
86
87}
88
89static inline struct shared_context *shctx_ptr(struct cache *cache)
90{
91 return (struct shared_context *)((unsigned char *)cache - ((struct shared_context *)NULL)->data);
92}
93
William Lallemand41db4602017-10-30 11:15:51 +010094static int
95cache_store_init(struct proxy *px, struct flt_conf *f1conf)
96{
97 return 0;
98}
99
William Lallemand4da3f8a2017-10-31 14:33:34 +0100100static int
101cache_store_chn_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
102{
103 if (!(chn->flags & CF_ISRESP))
104 return 1;
105
106 if (filter->ctx == NULL) {
107 struct cache_st *st;
108
109 st = pool_alloc_dirty(pool2_cache_st);
110 if (st == NULL)
111 return -1;
112
113 st->hdrs_len = 0;
114 st->first_block = NULL;
115 filter->ctx = st;
116 }
117
118 register_data_filter(s, chn, filter);
119
120 return 1;
121}
122
123static int
124cache_store_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
125{
126 struct cache_st *st = filter->ctx;
127
128 /* end of headers, exclude the final \r\n allow to forward the final
129 * \r\n in the data filter */
130 if (!(msg->chn->flags & CF_ISRESP) || !st)
131 return 1;
132
133 st->hdrs_len = msg->eoh;
134
135 return 1;
136}
137
138static int
139cache_store_http_forward_data(struct stream *s, struct filter *filter,
140 struct http_msg *msg, unsigned int len)
141{
142 struct cache_st *st = filter->ctx;
143 struct shared_context *shctx = shctx_ptr((struct cache *)filter->config->conf);
144 int ret;
145
146 /*
147 * We need to skip the HTTP headers first, because we saved them in the
148 * http-response action.
149 */
150 if (!(msg->chn->flags & CF_ISRESP) || !st)
151 return len;
152
153 if (!len) {
154 /* Nothing to foward */
155 ret = len;
156 }
157 else if (st->hdrs_len > len) {
158 /* Forward part of headers */
159 ret = len;
160 st->hdrs_len -= len;
161 }
162 else if (st->hdrs_len > 0) {
163 /* Forward remaining headers */
164 ret = st->hdrs_len;
165 st->hdrs_len = 0;
166 }
167 else {
168 /* Forward trailers data */
169 if (len) {
170 if (filter->ctx && st->first_block) {
171 /* disable buffering if too much data (never greater than a buffer size */
172 if (len > global.tune.bufsize - global.tune.maxrewrite - st->first_block->len) {
173 filter->ctx = NULL; /* disable cache */
174 shctx_lock(shctx);
175 shctx_row_dec_hot(shctx, st->first_block);
176 shctx_unlock(shctx);
177 pool_free2(pool2_cache_st, st);
178 ret = 0;
179 } else {
180
181 int blen;
182 blen = shctx_row_data_append(shctx,
183 st->first_block,
184 (unsigned char *)bi_ptr(msg->chn->buf),
185 MIN(bi_contig_data(msg->chn->buf), len));
186
187 ret = MIN(bi_contig_data(msg->chn->buf), len) + blen;
188 }
189 } else {
190 ret = len;
191 }
192 }
193 }
194
195 if ((ret != len) ||
196 (FLT_NXT(filter, msg->chn) != FLT_FWD(filter, msg->chn) + ret))
197 task_wakeup(s->task, TASK_WOKEN_MSG);
198
199 return ret;
200}
201
202static int
203cache_store_http_end(struct stream *s, struct filter *filter,
204 struct http_msg *msg)
205{
206 struct cache_st *st = filter->ctx;
207 struct cache *cache = filter->config->conf;
208 struct shared_context *shctx = shctx_ptr(cache);
209 struct cache_entry *object;
210
211 if (!(msg->chn->flags & CF_ISRESP))
212 return 1;
213
214 if (st && st->first_block) {
215
216 object = (struct cache_entry *)st->first_block->data;
217
218 /* does not need to test if the insertion worked, if it
219 * doesn't, the blocks will be reused anyway */
220
221 shctx_lock(shctx);
222 eb32_insert(&cache->entries, &object->eb);
223 shctx_unlock(shctx);
224
225 /* remove from the hotlist */
226 shctx_lock(shctx);
227 shctx_row_dec_hot(shctx, st->first_block);
228 shctx_unlock(shctx);
229
230 }
231 if (st) {
232 pool_free2(pool2_cache_st, st);
233 filter->ctx = NULL;
234 }
235
236 return 1;
237}
238
239 /*
240 * This intends to be used when checking HTTP headers for some
241 * word=value directive. Return a pointer to the first character of value, if
242 * the word was not found or if there wasn't any value assigned ot it return NULL
243 */
244char *directive_value(const char *sample, int slen, const char *word, int wlen)
245{
246 int st = 0;
247
248 if (slen < wlen)
249 return 0;
250
251 while (wlen) {
252 char c = *sample ^ *word;
253 if (c && c != ('A' ^ 'a'))
254 return NULL;
255 sample++;
256 word++;
257 slen--;
258 wlen--;
259 }
260
261 while (slen) {
262 if (st == 0) {
263 if (*sample != '=')
264 return NULL;
265 sample++;
266 slen--;
267 st = 1;
268 continue;
269 } else {
270 return (char *)sample;
271 }
272 }
273
274 return NULL;
275}
276
277/*
278 * Return the maxage in seconds of an HTTP response.
279 * Compute the maxage using either:
280 * - the assigned max-age of the cache
281 * - the s-maxage directive
282 * - the max-age directive
283 * - (Expires - Data) headers
284 * - the default-max-age of the cache
285 *
286 */
287int http_calc_maxage(struct stream *s)
288{
289 struct http_txn *txn = s->txn;
290 struct hdr_ctx ctx;
291
292 int smaxage = -1;
293 int maxage = -1;
294
295
296 /* TODO: forced maxage configuration */
297
298 ctx.idx = 0;
299
300 /* loop on the Cache-Control values */
301 while (http_find_header2("Cache-Control", 13, s->res.buf->p, &txn->hdr_idx, &ctx)) {
302 char *directive = ctx.line + ctx.val;
303 char *value;
304
305 value = directive_value(directive, ctx.vlen, "s-maxage", 8);
306 if (value) {
307 struct chunk *chk = get_trash_chunk();
308
309 chunk_strncat(chk, value, ctx.vlen - 8 + 1);
310 chunk_strncat(chk, "", 1);
311 maxage = atoi(chk->str);
312 }
313
314 value = directive_value(ctx.line + ctx.val, ctx.vlen, "max-age", 7);
315 if (value) {
316 struct chunk *chk = get_trash_chunk();
317
318 chunk_strncat(chk, value, ctx.vlen - 7 + 1);
319 chunk_strncat(chk, "", 1);
320 smaxage = atoi(chk->str);
321 }
322 }
323
324 /* TODO: Expires - Data */
325
326
327 if (smaxage > 0)
328 return smaxage;
329
330 if (maxage > 0)
331 return maxage;
332
333 /* TODO: return default value */
334
335 return 60;
336
337}
338
339
William Lallemand41db4602017-10-30 11:15:51 +0100340/*
341 * This fonction will store the headers of the response in a buffer and then
342 * register a filter to store the data
343 */
344enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
345 struct session *sess, struct stream *s, int flags)
346{
William Lallemand4da3f8a2017-10-31 14:33:34 +0100347 struct http_txn *txn = s->txn;
348 struct http_msg *msg = &txn->rsp;
349 struct filter *filter;
350 struct hdr_ctx ctx;
351 struct shared_block *first = NULL;
352 struct shared_context *shctx = shctx_ptr((struct cache *)rule->arg.act.p[0]);
353 struct cache_entry *object;
354
355
356 /* Don't cache if the response came from a cache */
357 if ((obj_type(s->target) == OBJ_TYPE_APPLET) &&
358 s->target == &http_cache_applet.obj_type) {
359 goto out;
360 }
361
362 /* cache only HTTP/1.1 */
363 if (!(txn->req.flags & HTTP_MSGF_VER_11))
364 goto out;
365
366 /* cache only GET method */
367 if (txn->meth != HTTP_METH_GET)
368 goto out;
369
370 /* cache only 200 status code */
371 if (txn->status != 200)
372 goto out;
373
374 /* Does not manage Vary at the moment. We will need a secondary key later for that */
375 ctx.idx = 0;
376 if (http_find_header2("Vary", 4, txn->rsp.chn->buf->p, &txn->hdr_idx, &ctx))
377 goto out;
378
379 /* we need to put this flag before using check_response_for_cacheability */
380 txn->flags |= TX_CACHEABLE;
381
382 if (txn->status != 101)
383 check_response_for_cacheability(s, &s->res);
384
385 if (!(txn->flags & TX_CACHEABLE))
386 goto out;
387
388 if ((msg->eoh + msg->body_len) > (global.tune.bufsize - global.tune.maxrewrite))
389 goto out;
390
391 shctx_lock(shctx);
392
393 first = shctx_row_reserve_hot(shctx, sizeof(struct cache_entry) + msg->eoh + msg->body_len);
394 if (!first) {
395 shctx_unlock(shctx);
396 goto out;
397 }
398 shctx_unlock(shctx);
399
400 /* reserve space for the cache_entry structure */
401 first->len = sizeof(struct cache_entry);
402
403 /* cache the headers in a http action because it allows to chose what
404 * to cache, for example you might want to cache a response before
405 * modifying some HTTP headers, or on the contrary after modifying
406 * those headers.
407 */
408
409 /* does not need to be locked because it's in the "hot" list,
410 * copy the headers */
411 if (shctx_row_data_append(shctx, first, (unsigned char *)s->res.buf->p, msg->eoh) < 0)
412 goto out;
413
414 /* register the buffer in the filter ctx for filling it with data*/
415 if (!LIST_ISEMPTY(&s->strm_flt.filters)) {
416 list_for_each_entry(filter, &s->strm_flt.filters, list) {
417 if (filter->config->id == cache_store_flt_id &&
418 filter->config->conf == rule->arg.act.p[0]) {
419 if (filter->ctx) {
420 struct cache_st *cache_ctx = filter->ctx;
421
422 cache_ctx->first_block = first;
423 object = (struct cache_entry *)first->data;
424
425 object->eb.key = hash_djb2(txn->uri, strlen(txn->uri));
426 /* Insert the node later on caching success */
427
428 shctx_lock(shctx);
429 if (entry_exist((struct cache *)rule->arg.act.p[0], object)) {
430 shctx_unlock(shctx);
431 if (filter->ctx) {
432 pool_free2(pool2_cache_st, filter->ctx);
433 filter->ctx = NULL;
434 }
435 goto out;
436 }
437 shctx_unlock(shctx);
438
439 /* store latest value and expiration time */
440 object->latest_validation = now.tv_sec;
441 object->expire = now.tv_sec + http_calc_maxage(s);
442
443 }
444 return ACT_RET_CONT;
445 }
446 }
447 }
448
449out:
450 /* if does not cache */
451 if (first) {
452 shctx_lock(shctx);
453 shctx_row_dec_hot(shctx, first);
454 shctx_unlock(shctx);
455 }
456
William Lallemand41db4602017-10-30 11:15:51 +0100457 return ACT_RET_CONT;
458}
459
460enum act_parse_ret parse_cache_store(const char **args, int *orig_arg, struct proxy *proxy,
461 struct act_rule *rule, char **err)
462{
463 struct flt_conf *fconf;
464 int cur_arg = *orig_arg;
465 rule->action = ACT_CUSTOM;
466 rule->action_ptr = http_action_store_cache;
467
468 if (!*args[cur_arg] || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
469 memprintf(err, "expects a cache name");
470 return ACT_RET_PRS_ERR;
471 }
472
473 /* check if a cache filter was already registered with this cache
474 * name, if that's the case, must use it. */
475 list_for_each_entry(fconf, &proxy->filter_configs, list) {
476 if (fconf->id == cache_store_flt_id && !strcmp((char *)fconf->conf, args[cur_arg])) {
477 rule->arg.act.p[0] = fconf->conf;
478 (*orig_arg)++;
479 /* filter already registered */
480 return ACT_RET_PRS_OK;
481 }
482 }
483
484 rule->arg.act.p[0] = strdup(args[cur_arg]);
485 if (!rule->arg.act.p[0]) {
486 Alert("config: %s '%s': out of memory\n", proxy_type_str(proxy), proxy->id);
487 err++;
488 goto err;
489 }
490 /* register a filter to fill the cache buffer */
491 fconf = calloc(1, sizeof(*fconf));
492 if (!fconf) {
493 Alert("config: %s '%s': out of memory\n",
494 proxy_type_str(proxy), proxy->id);
495 err++;
496 goto err;
497 }
498 fconf->id = cache_store_flt_id;
499 fconf->conf = rule->arg.act.p[0]; /* store the proxy name */
500 fconf->ops = &cache_ops;
501 LIST_ADDQ(&proxy->filter_configs, &fconf->list);
502
503 (*orig_arg)++;
504
505 return ACT_RET_PRS_OK;
506
507err:
508 return ACT_RET_ERR;
509}
510
511
512enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *px,
513 struct session *sess, struct stream *s, int flags)
514{
515 return ACT_RET_PRS_OK;
516}
517
518
519enum act_parse_ret parse_cache_use(const char **args, int *orig_arg, struct proxy *proxy,
520 struct act_rule *rule, char **err)
521{
522 int cur_arg = *orig_arg;
523
524 rule->action = ACT_CUSTOM;
525 rule->action_ptr = http_action_req_cache_use;
526
527 if (!*args[cur_arg] || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
528 memprintf(err, "expects a cache name");
529 return ACT_RET_PRS_ERR;
530 }
531
532 rule->arg.act.p[0] = strdup(args[cur_arg]);
533 if (!rule->arg.act.p[0]) {
534 Alert("config: %s '%s': out of memory\n", proxy_type_str(proxy), proxy->id);
535 err++;
536 goto err;
537 }
538
539 (*orig_arg)++;
540 return ACT_RET_PRS_OK;
541
542err:
543 return ACT_RET_ERR;
544
545}
546
547int cfg_parse_cache(const char *file, int linenum, char **args, int kwm)
548{
549 int err_code = 0;
550
551 if (strcmp(args[0], "cache") == 0) { /* new cache section */
552
553 if (!*args[1]) {
554 Alert("parsing [%s:%d] : '%s' expects an <id> argument\n",
555 file, linenum, args[0]);
556 err_code |= ERR_ALERT | ERR_ABORT;
557 goto out;
558 }
559
560 if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
561 err_code |= ERR_ABORT;
562 goto out;
563 }
564
565 if (tmp_cache_config == NULL) {
566 tmp_cache_config = calloc(1, sizeof(*tmp_cache_config));
567 if (!tmp_cache_config) {
568 Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
569 err_code |= ERR_ALERT | ERR_ABORT;
570 goto out;
571 }
572
573 strlcpy2(tmp_cache_config->id, args[1], 33);
574 if (strlen(args[1]) > 32) {
575 Warning("parsing [%s:%d]: cache id is limited to 32 characters, truncate to '%s'.\n",
576 file, linenum, tmp_cache_config->id);
577 err_code |= ERR_WARN;
578 }
579
580 tmp_cache_config->maxblocks = 0;
581 }
582 } else if (strcmp(args[0], "total-max-size") == 0) {
583 int maxsize;
584
585 if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
586 err_code |= ERR_ABORT;
587 goto out;
588 }
589
590 /* size in megabytes */
591 maxsize = atoi(args[1]) * 1024 * 1024 / CACHE_BLOCKSIZE;
592 tmp_cache_config->maxblocks = maxsize;
593
594 } else if (*args[0] != 0) {
595 Alert("parsing [%s:%d] : unknown keyword '%s' in 'cache' section\n", file, linenum, args[0]);
596 err_code |= ERR_ALERT | ERR_FATAL;
597 goto out;
598 }
599out:
600 return err_code;
601}
602
603/* once the cache section is parsed */
604
605int cfg_post_parse_section_cache()
606{
607 struct shared_context *shctx;
608 int err_code = 0;
609 int ret_shctx;
610
611 if (tmp_cache_config) {
612 struct cache *cache;
613
614 if (tmp_cache_config->maxblocks <= 0) {
615 Alert("Size not specified for cache '%s'\n", tmp_cache_config->id);
616 err_code |= ERR_FATAL | ERR_ALERT;
617 goto out;
618 }
619
620 ret_shctx = shctx_init(&shctx, tmp_cache_config->maxblocks, CACHE_BLOCKSIZE, sizeof(struct cache), 1);
William Lallemand4da3f8a2017-10-31 14:33:34 +0100621
William Lallemand41db4602017-10-30 11:15:51 +0100622 if (ret_shctx < 0) {
623 if (ret_shctx == SHCTX_E_INIT_LOCK)
624 Alert("Unable to initialize the lock for the cache.\n");
625 else
626 Alert("Unable to allocate cache.\n");
627
628 err_code |= ERR_FATAL | ERR_ALERT;
629 goto out;
630 }
William Lallemand4da3f8a2017-10-31 14:33:34 +0100631
William Lallemand41db4602017-10-30 11:15:51 +0100632 memcpy(shctx->data, tmp_cache_config, sizeof(struct cache));
633 cache = (struct cache *)shctx->data;
634 cache->entries = EB_ROOT_UNIQUE;
William Lallemand41db4602017-10-30 11:15:51 +0100635 LIST_ADDQ(&caches, &cache->list);
636 }
637out:
638 free(tmp_cache_config);
639 tmp_cache_config = NULL;
640 return err_code;
641
642}
643
644/*
645 * Resolve the cache name to a pointer once the file is completely read.
646 */
647int cfg_cache_postparser()
648{
649 struct act_rule *hresrule, *hrqrule;
650 void *cache_ptr;
651 struct cache *cache;
652 struct proxy *curproxy = NULL;
653 int err = 0;
654 struct flt_conf *fconf;
655
656 for (curproxy = proxy; curproxy; curproxy = curproxy->next) {
657
658 /* resolve the http response cache name to a ptr in the action rule */
659 list_for_each_entry(hresrule, &curproxy->http_res_rules, list) {
660 if (hresrule->action != ACT_CUSTOM ||
661 hresrule->action_ptr != http_action_store_cache)
662 continue;
663
664 cache_ptr = hresrule->arg.act.p[0];
665
666 list_for_each_entry(cache, &caches, list) {
667 if (!strcmp(cache->id, cache_ptr)) {
668 /* don't free there, it's still used in the filter conf */
669 cache_ptr = cache;
670 break;
671 }
672 }
673
674 if (cache_ptr == hresrule->arg.act.p[0]) {
675 Alert("Proxy '%s': unable to find the cache '%s' referenced by http-response cache-store rule.\n",
676 curproxy->id, (char *)hresrule->arg.act.p[0]);
677 err++;
678 }
679
680 hresrule->arg.act.p[0] = cache_ptr;
681 }
682
683 /* resolve the http request cache name to a ptr in the action rule */
684 list_for_each_entry(hrqrule, &curproxy->http_req_rules, list) {
685 if (hrqrule->action != ACT_CUSTOM ||
686 hrqrule->action_ptr != http_action_req_cache_use)
687 continue;
688
689 cache_ptr = hrqrule->arg.act.p[0];
690
691 list_for_each_entry(cache, &caches, list) {
692 if (!strcmp(cache->id, cache_ptr)) {
693 free(cache_ptr);
694 cache_ptr = cache;
695 break;
696 }
697 }
698
699 if (cache_ptr == hrqrule->arg.act.p[0]) {
700 Alert("Proxy '%s': unable to find the cache '%s' referenced by http-request cache-use rule.\n",
701 curproxy->id, (char *)hrqrule->arg.act.p[0]);
702 err++;
703 }
704
705 hrqrule->arg.act.p[0] = cache_ptr;
706 }
707
708 /* resolve the cache name to a ptr in the filter config */
709 list_for_each_entry(fconf, &curproxy->filter_configs, list) {
710
711 cache_ptr = fconf->conf;
712
713 list_for_each_entry(cache, &caches, list) {
714 if (!strcmp(cache->id, cache_ptr)) {
715 /* there can be only one filter per cache, so we free it there */
716 free(cache_ptr);
717 cache_ptr = cache;
718 break;
719 }
720 }
721
722 if (cache_ptr == fconf->conf) {
723 Alert("Proxy '%s': unable to find the cache '%s' referenced by the filter 'cache'.\n",
724 curproxy->id, (char *)fconf->conf);
725 err++;
726 }
727 fconf->conf = cache_ptr;
728 }
729 }
730 return err;
731}
732
733
734struct flt_ops cache_ops = {
735 .init = cache_store_init,
736
William Lallemand4da3f8a2017-10-31 14:33:34 +0100737 /* Handle channels activity */
738 .channel_start_analyze = cache_store_chn_start_analyze,
739
740 /* Filter HTTP requests and responses */
741 .http_headers = cache_store_http_headers,
742 .http_end = cache_store_http_end,
743
744 .http_forward_data = cache_store_http_forward_data,
745
William Lallemand41db4602017-10-30 11:15:51 +0100746};
747
748static struct action_kw_list http_res_actions = {
749 .kw = {
750 { "cache-store", parse_cache_store },
751 { NULL, NULL }
752 }
753};
754
755static struct action_kw_list http_req_actions = {
756 .kw = {
757 { "cache-use", parse_cache_use },
758 { NULL, NULL }
759 }
760};
761
762struct applet http_cache_applet = {
763 .obj_type = OBJ_TYPE_APPLET,
764 .name = "<CACHE>", /* used for logging */
765 .fct = NULL,
766 .release = NULL,
767};
768
769__attribute__((constructor))
770static void __cache_init(void)
771{
772 cfg_register_section("cache", cfg_parse_cache, cfg_post_parse_section_cache);
773 cfg_register_postparser("cache", cfg_cache_postparser);
774 http_res_keywords_register(&http_res_actions);
775 http_req_keywords_register(&http_req_actions);
William Lallemand4da3f8a2017-10-31 14:33:34 +0100776 pool2_cache_st = create_pool("cache_st", sizeof(struct cache_st), MEM_F_SHARED);
William Lallemand41db4602017-10-30 11:15:51 +0100777}
778