2017-10-30 06:15:51 -04:00
|
|
|
/*
|
|
|
|
|
* Cache management
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2017 HAProxy Technologies
|
|
|
|
|
* William Lallemand <wlallemand@haproxy.com>
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
|
* as published by the Free Software Foundation; either version
|
|
|
|
|
* 2 of the License, or (at your option) any later version.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <eb32tree.h>
|
2017-11-23 13:43:17 -05:00
|
|
|
#include <import/sha1.h>
|
2017-10-30 06:15:51 -04:00
|
|
|
|
2017-11-21 14:01:24 -05:00
|
|
|
#include <types/action.h>
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
#include <types/cli.h>
|
2017-11-21 14:01:24 -05:00
|
|
|
#include <types/filters.h>
|
|
|
|
|
#include <types/proxy.h>
|
|
|
|
|
#include <types/shctx.h>
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
#include <proto/channel.h>
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
#include <proto/cli.h>
|
2017-10-30 06:15:51 -04:00
|
|
|
#include <proto/proxy.h>
|
|
|
|
|
#include <proto/hdr_idx.h>
|
|
|
|
|
#include <proto/filters.h>
|
2018-10-02 10:43:32 -04:00
|
|
|
#include <proto/http_rules.h>
|
2017-10-30 06:15:51 -04:00
|
|
|
#include <proto/proto_http.h>
|
|
|
|
|
#include <proto/log.h>
|
|
|
|
|
#include <proto/stream.h>
|
|
|
|
|
#include <proto/stream_interface.h>
|
|
|
|
|
#include <proto/shctx.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <common/cfgparse.h>
|
|
|
|
|
#include <common/hash.h>
|
|
|
|
|
|
|
|
|
|
/* flt_cache_store */
|
|
|
|
|
|
|
|
|
|
static const char *cache_store_flt_id = "cache store filter";
|
|
|
|
|
|
2017-11-24 11:34:44 -05:00
|
|
|
static struct pool_head *pool_head_cache_st = NULL;
|
2017-10-31 09:33:34 -04:00
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
struct applet http_cache_applet;
|
|
|
|
|
|
|
|
|
|
struct flt_ops cache_ops;
|
|
|
|
|
|
|
|
|
|
struct cache {
|
2017-11-26 02:54:31 -05:00
|
|
|
struct list list; /* cache linked list */
|
2017-10-30 06:15:51 -04:00
|
|
|
struct eb_root entries; /* head of cache entries based on keys */
|
2017-11-26 02:54:31 -05:00
|
|
|
unsigned int maxage; /* max-age */
|
|
|
|
|
unsigned int maxblocks;
|
2018-10-25 14:29:31 -04:00
|
|
|
unsigned int maxobjsz; /* max-object-size (in bytes) */
|
2017-11-26 02:54:31 -05:00
|
|
|
char id[33]; /* cache name */
|
2017-10-30 06:15:51 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* cache ctx for filters
|
|
|
|
|
*/
|
|
|
|
|
struct cache_st {
|
|
|
|
|
int hdrs_len;
|
|
|
|
|
struct shared_block *first_block;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct cache_entry {
|
|
|
|
|
unsigned int latest_validation; /* latest validation date */
|
|
|
|
|
unsigned int expire; /* expiration date */
|
2018-10-26 08:29:22 -04:00
|
|
|
unsigned int age; /* Origin server "Age" header value */
|
|
|
|
|
unsigned int eoh; /* Origin server end of headers offset. */
|
2017-10-30 06:15:51 -04:00
|
|
|
struct eb32_node eb; /* ebtree node used to hold the cache object */
|
2017-11-23 13:43:17 -05:00
|
|
|
char hash[20];
|
2017-10-30 06:15:51 -04:00
|
|
|
unsigned char data[0];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define CACHE_BLOCKSIZE 1024
|
2018-11-11 08:00:28 -05:00
|
|
|
#define CACHE_ENTRY_MAX_AGE 2147483648U
|
2017-10-30 06:15:51 -04:00
|
|
|
|
|
|
|
|
static struct list caches = LIST_HEAD_INIT(caches);
|
|
|
|
|
static struct cache *tmp_cache_config = NULL;
|
|
|
|
|
|
2017-11-23 13:43:17 -05:00
|
|
|
struct cache_entry *entry_exist(struct cache *cache, char *hash)
|
2017-10-31 09:33:34 -04:00
|
|
|
{
|
|
|
|
|
struct eb32_node *node;
|
|
|
|
|
struct cache_entry *entry;
|
|
|
|
|
|
2017-11-23 13:43:17 -05:00
|
|
|
node = eb32_lookup(&cache->entries, (*(unsigned int *)hash));
|
2017-10-31 09:33:34 -04:00
|
|
|
if (!node)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
entry = eb32_entry(node, struct cache_entry, eb);
|
2017-11-23 13:43:17 -05:00
|
|
|
|
|
|
|
|
/* if that's not the right node */
|
|
|
|
|
if (memcmp(entry->hash, hash, sizeof(entry->hash)))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
2017-11-21 14:01:27 -05:00
|
|
|
if (entry->expire > now.tv_sec) {
|
2017-10-31 09:33:34 -04:00
|
|
|
return entry;
|
2017-11-21 14:01:27 -05:00
|
|
|
} else {
|
2017-10-31 09:33:34 -04:00
|
|
|
eb32_delete(node);
|
2017-11-21 14:01:27 -05:00
|
|
|
entry->eb.key = 0;
|
|
|
|
|
}
|
2017-10-31 09:33:34 -04:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline struct shared_context *shctx_ptr(struct cache *cache)
|
|
|
|
|
{
|
|
|
|
|
return (struct shared_context *)((unsigned char *)cache - ((struct shared_context *)NULL)->data);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 15:43:01 -04:00
|
|
|
static inline struct shared_block *block_ptr(struct cache_entry *entry)
|
|
|
|
|
{
|
|
|
|
|
return (struct shared_block *)((unsigned char *)entry - ((struct shared_block *)NULL)->data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
static int
|
|
|
|
|
cache_store_init(struct proxy *px, struct flt_conf *f1conf)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
static int
|
|
|
|
|
cache_store_chn_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
|
|
|
|
|
{
|
|
|
|
|
if (!(chn->flags & CF_ISRESP))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
if (filter->ctx == NULL) {
|
|
|
|
|
struct cache_st *st;
|
|
|
|
|
|
2017-11-24 11:34:44 -05:00
|
|
|
st = pool_alloc_dirty(pool_head_cache_st);
|
2017-10-31 09:33:34 -04:00
|
|
|
if (st == NULL)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
st->hdrs_len = 0;
|
|
|
|
|
st->first_block = NULL;
|
|
|
|
|
filter->ctx = st;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
register_data_filter(s, chn, filter);
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-24 08:33:54 -05:00
|
|
|
static int
|
|
|
|
|
cache_store_chn_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
|
|
|
|
|
{
|
|
|
|
|
struct cache_st *st = filter->ctx;
|
|
|
|
|
struct cache *cache = filter->config->conf;
|
|
|
|
|
struct shared_context *shctx = shctx_ptr(cache);
|
|
|
|
|
|
|
|
|
|
if (!(chn->flags & CF_ISRESP))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
/* Everything should be released in the http_end filter, but we need to do it
|
|
|
|
|
* there too, in case of errors */
|
|
|
|
|
|
|
|
|
|
if (st && st->first_block) {
|
|
|
|
|
|
|
|
|
|
shctx_lock(shctx);
|
|
|
|
|
shctx_row_dec_hot(shctx, st->first_block);
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (st) {
|
2017-11-24 11:34:44 -05:00
|
|
|
pool_free(pool_head_cache_st, st);
|
2017-11-24 08:33:54 -05:00
|
|
|
filter->ctx = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
static int
|
|
|
|
|
cache_store_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
|
|
|
|
|
{
|
|
|
|
|
struct cache_st *st = filter->ctx;
|
|
|
|
|
|
|
|
|
|
if (!(msg->chn->flags & CF_ISRESP) || !st)
|
|
|
|
|
return 1;
|
|
|
|
|
|
2017-11-14 08:39:22 -05:00
|
|
|
st->hdrs_len = msg->sov;
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-22 12:01:48 -04:00
|
|
|
static inline void disable_cache_entry(struct cache_st *st,
|
|
|
|
|
struct filter *filter, struct shared_context *shctx)
|
|
|
|
|
{
|
|
|
|
|
struct cache_entry *object;
|
|
|
|
|
|
|
|
|
|
object = (struct cache_entry *)st->first_block->data;
|
|
|
|
|
filter->ctx = NULL; /* disable cache */
|
|
|
|
|
shctx_lock(shctx);
|
|
|
|
|
shctx_row_dec_hot(shctx, st->first_block);
|
|
|
|
|
object->eb.key = 0;
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
pool_free(pool_head_cache_st, st);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
static int
|
|
|
|
|
cache_store_http_forward_data(struct stream *s, struct filter *filter,
|
|
|
|
|
struct http_msg *msg, unsigned int len)
|
|
|
|
|
{
|
|
|
|
|
struct cache_st *st = filter->ctx;
|
|
|
|
|
struct shared_context *shctx = shctx_ptr((struct cache *)filter->config->conf);
|
|
|
|
|
int ret;
|
|
|
|
|
|
2018-10-22 12:01:48 -04:00
|
|
|
ret = 0;
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
/*
|
|
|
|
|
* We need to skip the HTTP headers first, because we saved them in the
|
|
|
|
|
* http-response action.
|
|
|
|
|
*/
|
|
|
|
|
if (!(msg->chn->flags & CF_ISRESP) || !st)
|
|
|
|
|
return len;
|
|
|
|
|
|
|
|
|
|
if (!len) {
|
|
|
|
|
/* Nothing to foward */
|
|
|
|
|
ret = len;
|
|
|
|
|
}
|
2017-11-14 08:39:23 -05:00
|
|
|
else if (st->hdrs_len >= len) {
|
2017-10-31 09:33:34 -04:00
|
|
|
/* Forward part of headers */
|
|
|
|
|
ret = len;
|
|
|
|
|
st->hdrs_len -= len;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2017-11-14 08:39:23 -05:00
|
|
|
/* Forward data */
|
2017-11-01 08:58:21 -04:00
|
|
|
if (filter->ctx && st->first_block) {
|
2018-10-22 12:01:48 -04:00
|
|
|
int to_append, append;
|
|
|
|
|
struct shared_block *fb;
|
|
|
|
|
|
|
|
|
|
to_append = MIN(ci_contig_data(msg->chn), len - st->hdrs_len);
|
|
|
|
|
|
|
|
|
|
shctx_lock(shctx);
|
|
|
|
|
fb = shctx_row_reserve_hot(shctx, st->first_block, to_append);
|
|
|
|
|
if (!fb) {
|
2017-11-01 08:58:21 -04:00
|
|
|
shctx_unlock(shctx);
|
2018-10-22 12:01:48 -04:00
|
|
|
disable_cache_entry(st, filter, shctx);
|
|
|
|
|
return len;
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
2018-10-22 12:01:48 -04:00
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
|
2018-10-22 10:59:13 -04:00
|
|
|
/* Skip remaining headers to fill the cache */
|
|
|
|
|
c_adv(msg->chn, st->hdrs_len);
|
2018-10-22 12:01:48 -04:00
|
|
|
append = shctx_row_data_append(shctx, st->first_block, st->first_block->last_append,
|
|
|
|
|
(unsigned char *)ci_head(msg->chn), to_append);
|
|
|
|
|
ret = st->hdrs_len + to_append - append;
|
|
|
|
|
/* Rewind the buffer to forward all data */
|
|
|
|
|
c_rew(msg->chn, st->hdrs_len);
|
|
|
|
|
st->hdrs_len = 0;
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
disable_cache_entry(st, filter, shctx);
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
2018-10-22 12:01:48 -04:00
|
|
|
else
|
|
|
|
|
ret = len;
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((ret != len) ||
|
|
|
|
|
(FLT_NXT(filter, msg->chn) != FLT_FWD(filter, msg->chn) + ret))
|
|
|
|
|
task_wakeup(s->task, TASK_WOKEN_MSG);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
cache_store_http_end(struct stream *s, struct filter *filter,
|
|
|
|
|
struct http_msg *msg)
|
|
|
|
|
{
|
|
|
|
|
struct cache_st *st = filter->ctx;
|
|
|
|
|
struct cache *cache = filter->config->conf;
|
|
|
|
|
struct shared_context *shctx = shctx_ptr(cache);
|
|
|
|
|
struct cache_entry *object;
|
|
|
|
|
|
|
|
|
|
if (!(msg->chn->flags & CF_ISRESP))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
if (st && st->first_block) {
|
|
|
|
|
|
|
|
|
|
object = (struct cache_entry *)st->first_block->data;
|
|
|
|
|
|
|
|
|
|
/* does not need to test if the insertion worked, if it
|
|
|
|
|
* doesn't, the blocks will be reused anyway */
|
|
|
|
|
|
|
|
|
|
shctx_lock(shctx);
|
2017-11-21 14:01:27 -05:00
|
|
|
if (eb32_insert(&cache->entries, &object->eb) != &object->eb) {
|
|
|
|
|
object->eb.key = 0;
|
|
|
|
|
}
|
2017-10-31 09:33:34 -04:00
|
|
|
/* remove from the hotlist */
|
|
|
|
|
shctx_row_dec_hot(shctx, st->first_block);
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (st) {
|
2017-11-24 11:34:44 -05:00
|
|
|
pool_free(pool_head_cache_st, st);
|
2017-10-31 09:33:34 -04:00
|
|
|
filter->ctx = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This intends to be used when checking HTTP headers for some
|
|
|
|
|
* word=value directive. Return a pointer to the first character of value, if
|
|
|
|
|
* the word was not found or if there wasn't any value assigned ot it return NULL
|
|
|
|
|
*/
|
|
|
|
|
char *directive_value(const char *sample, int slen, const char *word, int wlen)
|
|
|
|
|
{
|
|
|
|
|
int st = 0;
|
|
|
|
|
|
|
|
|
|
if (slen < wlen)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
while (wlen) {
|
|
|
|
|
char c = *sample ^ *word;
|
|
|
|
|
if (c && c != ('A' ^ 'a'))
|
|
|
|
|
return NULL;
|
|
|
|
|
sample++;
|
|
|
|
|
word++;
|
|
|
|
|
slen--;
|
|
|
|
|
wlen--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (slen) {
|
|
|
|
|
if (st == 0) {
|
|
|
|
|
if (*sample != '=')
|
|
|
|
|
return NULL;
|
|
|
|
|
sample++;
|
|
|
|
|
slen--;
|
|
|
|
|
st = 1;
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
return (char *)sample;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Return the maxage in seconds of an HTTP response.
|
|
|
|
|
* Compute the maxage using either:
|
|
|
|
|
* - the assigned max-age of the cache
|
|
|
|
|
* - the s-maxage directive
|
|
|
|
|
* - the max-age directive
|
|
|
|
|
* - (Expires - Data) headers
|
|
|
|
|
* - the default-max-age of the cache
|
|
|
|
|
*
|
|
|
|
|
*/
|
2017-11-24 12:53:43 -05:00
|
|
|
int http_calc_maxage(struct stream *s, struct cache *cache)
|
2017-10-31 09:33:34 -04:00
|
|
|
{
|
|
|
|
|
struct http_txn *txn = s->txn;
|
|
|
|
|
struct hdr_ctx ctx;
|
|
|
|
|
|
|
|
|
|
int smaxage = -1;
|
|
|
|
|
int maxage = -1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.idx = 0;
|
|
|
|
|
|
|
|
|
|
/* loop on the Cache-Control values */
|
2018-06-19 01:13:36 -04:00
|
|
|
while (http_find_header2("Cache-Control", 13, ci_head(&s->res), &txn->hdr_idx, &ctx)) {
|
2017-10-31 09:33:34 -04:00
|
|
|
char *directive = ctx.line + ctx.val;
|
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
|
|
value = directive_value(directive, ctx.vlen, "s-maxage", 8);
|
|
|
|
|
if (value) {
|
2018-07-13 05:56:34 -04:00
|
|
|
struct buffer *chk = get_trash_chunk();
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
chunk_strncat(chk, value, ctx.vlen - 8 + 1);
|
|
|
|
|
chunk_strncat(chk, "", 1);
|
2018-07-13 04:54:26 -04:00
|
|
|
maxage = atoi(chk->area);
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value = directive_value(ctx.line + ctx.val, ctx.vlen, "max-age", 7);
|
|
|
|
|
if (value) {
|
2018-07-13 05:56:34 -04:00
|
|
|
struct buffer *chk = get_trash_chunk();
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
chunk_strncat(chk, value, ctx.vlen - 7 + 1);
|
|
|
|
|
chunk_strncat(chk, "", 1);
|
2018-07-13 04:54:26 -04:00
|
|
|
smaxage = atoi(chk->area);
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* TODO: Expires - Data */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (smaxage > 0)
|
2017-11-24 12:53:43 -05:00
|
|
|
return MIN(smaxage, cache->maxage);
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
if (maxage > 0)
|
2017-11-24 12:53:43 -05:00
|
|
|
return MIN(maxage, cache->maxage);
|
2017-10-31 09:33:34 -04:00
|
|
|
|
2017-11-24 12:53:43 -05:00
|
|
|
return cache->maxage;
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-11-20 13:13:12 -05:00
|
|
|
static void cache_free_blocks(struct shared_block *first, struct shared_block *block)
|
|
|
|
|
{
|
BUG/MAJOR: cache: fix random crashes caused by incorrect delete() on non-first blocks
Several segfaults were reported in the cache, each time in eb_delete()
called from cache_free_blocks() itself called from shctx_row_reserve_hot().
Each time the tree node was corrupted with random cached data (often JS or
HTML contents).
The problem comes from an incompatibility between the cache's expectations
and the recycling algorithm used in the shctx. The shctx allocates and
releases a chain of blocks at once. And when it needs to allocate N blocks
from the avail list while a chain of M>N is found, it picks the first N
from the list, moves them to the hot list, and marks all remaining M-N
blocks as isolated blocks (chains of 1).
For each such released block, the shctx->free_block() callback is used
and passed a pointer to the first and current block of the chain. For
the cache, it's cache_free_blocks(). What this function does is check
that the current block is the first one, and in this case delete the
object from the tree and mark it as not in tree by setting key to zero.
The problem this causes is that the tail blocks when M>N become first
blocks for the next call to shctx_row_reserve_hot(), these ones will
be passed to cache_free_blocks() as list heads, and will be sent to
eb_delete() despite containing only cached data.
The simplest solution for now is to mark each block as holding no cache
object by setting key to zero all the time. It keeps the principle used
elsewhere in the code. The SSL code is not subject to this problem
because it relies on the block's len not being null, which happens
immediately after a block was released. It was uncertain however whether
this method is suitable for the cache. It is not critical though since
this code is going to change soon in 1.9 to dynamically allocate only
the number of required blocks.
This fix must be backported to 1.8. Thanks to Thierry for providing
exploitable cores.
2018-04-04 14:17:03 -04:00
|
|
|
struct cache_entry *object = (struct cache_entry *)block->data;
|
|
|
|
|
|
|
|
|
|
if (first == block && object->eb.key)
|
|
|
|
|
eb32_delete(&object->eb);
|
|
|
|
|
object->eb.key = 0;
|
2017-11-20 13:13:12 -05:00
|
|
|
}
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
/*
|
|
|
|
|
* This fonction will store the headers of the response in a buffer and then
|
|
|
|
|
* register a filter to store the data
|
|
|
|
|
*/
|
|
|
|
|
enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2018-10-26 08:29:22 -04:00
|
|
|
unsigned int age;
|
|
|
|
|
long long hdr_age;
|
2017-10-31 09:33:34 -04:00
|
|
|
struct http_txn *txn = s->txn;
|
|
|
|
|
struct http_msg *msg = &txn->rsp;
|
|
|
|
|
struct filter *filter;
|
|
|
|
|
struct hdr_ctx ctx;
|
|
|
|
|
struct shared_block *first = NULL;
|
2017-11-24 12:53:43 -05:00
|
|
|
struct cache *cache = (struct cache *)rule->arg.act.p[0];
|
|
|
|
|
struct shared_context *shctx = shctx_ptr(cache);
|
2017-10-31 09:33:34 -04:00
|
|
|
struct cache_entry *object;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Don't cache if the response came from a cache */
|
|
|
|
|
if ((obj_type(s->target) == OBJ_TYPE_APPLET) &&
|
|
|
|
|
s->target == &http_cache_applet.obj_type) {
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* cache only HTTP/1.1 */
|
|
|
|
|
if (!(txn->req.flags & HTTP_MSGF_VER_11))
|
|
|
|
|
goto out;
|
|
|
|
|
|
2018-10-22 10:59:13 -04:00
|
|
|
/* Do not cache too big objects. */
|
|
|
|
|
if ((msg->flags & HTTP_MSGF_CNT_LEN) && shctx->max_obj_size > 0 &&
|
|
|
|
|
msg->sov + msg->body_len > shctx->max_obj_size)
|
|
|
|
|
goto out;
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
/* cache only GET method */
|
|
|
|
|
if (txn->meth != HTTP_METH_GET)
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
/* cache only 200 status code */
|
|
|
|
|
if (txn->status != 200)
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
/* Does not manage Vary at the moment. We will need a secondary key later for that */
|
|
|
|
|
ctx.idx = 0;
|
2018-06-19 01:13:36 -04:00
|
|
|
if (http_find_header2("Vary", 4, ci_head(txn->rsp.chn), &txn->hdr_idx, &ctx))
|
2017-10-31 09:33:34 -04:00
|
|
|
goto out;
|
|
|
|
|
|
2017-12-21 09:59:17 -05:00
|
|
|
check_response_for_cacheability(s, &s->res);
|
2017-10-31 09:33:34 -04:00
|
|
|
|
2017-12-22 12:03:04 -05:00
|
|
|
if (!(txn->flags & TX_CACHEABLE) || !(txn->flags & TX_CACHE_COOK))
|
2017-10-31 09:33:34 -04:00
|
|
|
goto out;
|
|
|
|
|
|
2018-10-26 08:29:22 -04:00
|
|
|
age = 0;
|
|
|
|
|
ctx.idx = 0;
|
|
|
|
|
if (http_find_header2("Age", 3, ci_head(txn->rsp.chn), &txn->hdr_idx, &ctx)) {
|
|
|
|
|
if (!strl2llrc(ctx.line + ctx.val, ctx.vlen, &hdr_age) && hdr_age > 0) {
|
|
|
|
|
if (unlikely(hdr_age > CACHE_ENTRY_MAX_AGE))
|
|
|
|
|
hdr_age = CACHE_ENTRY_MAX_AGE;
|
|
|
|
|
age = hdr_age;
|
|
|
|
|
}
|
|
|
|
|
http_remove_header2(msg, &txn->hdr_idx, &ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
shctx_lock(shctx);
|
2018-10-22 12:01:48 -04:00
|
|
|
first = shctx_row_reserve_hot(shctx, NULL, sizeof(struct cache_entry) + msg->sov);
|
2017-10-31 09:33:34 -04:00
|
|
|
if (!first) {
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
|
2018-04-06 13:02:25 -04:00
|
|
|
/* the received memory is not initialized, we need at least to mark
|
|
|
|
|
* the object as not indexed yet.
|
|
|
|
|
*/
|
|
|
|
|
object = (struct cache_entry *)first->data;
|
|
|
|
|
object->eb.node.leaf_p = NULL;
|
|
|
|
|
object->eb.key = 0;
|
2018-10-26 08:29:22 -04:00
|
|
|
object->age = age;
|
|
|
|
|
object->eoh = msg->eoh;
|
2018-04-06 13:02:25 -04:00
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
/* reserve space for the cache_entry structure */
|
|
|
|
|
first->len = sizeof(struct cache_entry);
|
2018-10-22 12:01:48 -04:00
|
|
|
first->last_append = NULL;
|
2017-10-31 09:33:34 -04:00
|
|
|
/* cache the headers in a http action because it allows to chose what
|
|
|
|
|
* to cache, for example you might want to cache a response before
|
|
|
|
|
* modifying some HTTP headers, or on the contrary after modifying
|
|
|
|
|
* those headers.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* does not need to be locked because it's in the "hot" list,
|
|
|
|
|
* copy the headers */
|
2018-10-22 11:55:57 -04:00
|
|
|
if (shctx_row_data_append(shctx, first, NULL, (unsigned char *)ci_head(&s->res), msg->sov) < 0)
|
2017-10-31 09:33:34 -04:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
/* register the buffer in the filter ctx for filling it with data*/
|
|
|
|
|
if (!LIST_ISEMPTY(&s->strm_flt.filters)) {
|
|
|
|
|
list_for_each_entry(filter, &s->strm_flt.filters, list) {
|
|
|
|
|
if (filter->config->id == cache_store_flt_id &&
|
|
|
|
|
filter->config->conf == rule->arg.act.p[0]) {
|
|
|
|
|
if (filter->ctx) {
|
|
|
|
|
struct cache_st *cache_ctx = filter->ctx;
|
2017-12-22 11:42:46 -05:00
|
|
|
struct cache_entry *old;
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
cache_ctx->first_block = first;
|
|
|
|
|
|
2017-11-23 13:43:17 -05:00
|
|
|
object->eb.key = (*(unsigned int *)&txn->cache_hash);
|
|
|
|
|
memcpy(object->hash, txn->cache_hash, sizeof(object->hash));
|
2017-10-31 09:33:34 -04:00
|
|
|
/* Insert the node later on caching success */
|
|
|
|
|
|
|
|
|
|
shctx_lock(shctx);
|
2017-12-22 11:42:46 -05:00
|
|
|
|
|
|
|
|
old = entry_exist(cache, txn->cache_hash);
|
|
|
|
|
if (old) {
|
|
|
|
|
eb32_delete(&old->eb);
|
|
|
|
|
old->eb.key = 0;
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
|
|
|
|
|
/* store latest value and expiration time */
|
|
|
|
|
object->latest_validation = now.tv_sec;
|
2017-11-24 12:53:43 -05:00
|
|
|
object->expire = now.tv_sec + http_calc_maxage(s, cache);
|
2017-10-31 09:33:34 -04:00
|
|
|
}
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
/* if does not cache */
|
|
|
|
|
if (first) {
|
|
|
|
|
shctx_lock(shctx);
|
2017-11-21 14:01:27 -05:00
|
|
|
first->len = 0;
|
|
|
|
|
object->eb.key = 0;
|
2017-10-31 09:33:34 -04:00
|
|
|
shctx_row_dec_hot(shctx, first);
|
|
|
|
|
shctx_unlock(shctx);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-26 08:29:22 -04:00
|
|
|
#define HTTP_CACHE_INIT 0 /* Initial state. */
|
|
|
|
|
#define HTTP_CACHE_HEADER 1 /* Cache entry headers forwarded. */
|
|
|
|
|
#define HTTP_CACHE_FWD 2 /* Cache entry completely forwarded. */
|
|
|
|
|
#define HTTP_CACHE_END 3 /* Cache entry treatment terminated. */
|
2017-10-31 15:43:01 -04:00
|
|
|
|
2017-11-24 08:33:55 -05:00
|
|
|
static void http_cache_applet_release(struct appctx *appctx)
|
|
|
|
|
{
|
|
|
|
|
struct cache *cache = (struct cache *)appctx->rule->arg.act.p[0];
|
|
|
|
|
struct cache_entry *cache_ptr = appctx->ctx.cache.entry;
|
|
|
|
|
struct shared_block *first = block_ptr(cache_ptr);
|
|
|
|
|
|
|
|
|
|
shctx_lock(shctx_ptr(cache));
|
|
|
|
|
shctx_row_dec_hot(shctx_ptr(cache), first);
|
|
|
|
|
shctx_unlock(shctx_ptr(cache));
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-26 08:29:22 -04:00
|
|
|
/*
|
|
|
|
|
* Append an "Age" header into <chn> channel for this <ce> cache entry.
|
|
|
|
|
* This is the responsability of the caller to insure there is enough
|
|
|
|
|
* data in the channel.
|
|
|
|
|
*
|
|
|
|
|
* Returns the number of bytes inserted if succeeded, 0 if failed.
|
|
|
|
|
*/
|
|
|
|
|
static int cache_channel_append_age_header(struct cache_entry *ce, struct channel *chn)
|
|
|
|
|
{
|
|
|
|
|
unsigned int age;
|
|
|
|
|
|
|
|
|
|
age = MAX(0, (int)(now.tv_sec - ce->latest_validation)) + ce->age;
|
|
|
|
|
if (unlikely(age > CACHE_ENTRY_MAX_AGE))
|
|
|
|
|
age = CACHE_ENTRY_MAX_AGE;
|
|
|
|
|
|
|
|
|
|
chunk_reset(&trash);
|
|
|
|
|
chunk_printf(&trash, "Age: %u", age);
|
|
|
|
|
|
|
|
|
|
return ci_insert_line2(chn, ce->eoh, trash.area, trash.data);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-22 12:01:48 -04:00
|
|
|
static int cache_channel_row_data_get(struct appctx *appctx, int len)
|
2017-10-31 15:43:01 -04:00
|
|
|
{
|
2018-10-22 12:01:48 -04:00
|
|
|
int ret, total;
|
2017-10-31 15:43:01 -04:00
|
|
|
struct stream_interface *si = appctx->owner;
|
|
|
|
|
struct channel *res = si_ic(si);
|
|
|
|
|
struct cache *cache = (struct cache *)appctx->rule->arg.act.p[0];
|
|
|
|
|
struct shared_context *shctx = shctx_ptr(cache);
|
2018-10-22 12:01:48 -04:00
|
|
|
struct cache_entry *cache_ptr = appctx->ctx.cache.entry;
|
|
|
|
|
struct shared_block *blk, *next = appctx->ctx.cache.next;
|
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
|
offset = 0;
|
|
|
|
|
|
|
|
|
|
if (!next) {
|
|
|
|
|
offset = sizeof(struct cache_entry);
|
|
|
|
|
next = block_ptr(cache_ptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blk = next;
|
|
|
|
|
list_for_each_entry_from(blk, &shctx->hot, list) {
|
|
|
|
|
int sz;
|
|
|
|
|
|
|
|
|
|
if (len <= 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
sz = MIN(len, shctx->block_size - offset);
|
|
|
|
|
|
|
|
|
|
ret = ci_putblk(res, (const char *)blk->data + offset, sz);
|
|
|
|
|
if (unlikely(offset))
|
|
|
|
|
offset = 0;
|
|
|
|
|
if (ret <= 0) {
|
|
|
|
|
if (ret == -3 || ret == -1) {
|
2018-11-06 12:46:37 -05:00
|
|
|
si_cant_put(si);
|
2018-10-22 12:01:48 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total += sz;
|
|
|
|
|
len -= sz;
|
|
|
|
|
}
|
|
|
|
|
appctx->ctx.cache.next = blk;
|
|
|
|
|
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void http_cache_io_handler(struct appctx *appctx)
|
|
|
|
|
{
|
|
|
|
|
struct stream_interface *si = appctx->owner;
|
|
|
|
|
struct channel *res = si_ic(si);
|
|
|
|
|
struct cache_entry *cache_ptr = appctx->ctx.cache.entry;
|
2017-10-31 15:43:01 -04:00
|
|
|
struct shared_block *first = block_ptr(cache_ptr);
|
2018-10-22 12:01:48 -04:00
|
|
|
int *sent = &appctx->ctx.cache.sent;
|
2017-10-31 15:43:01 -04:00
|
|
|
|
|
|
|
|
if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
/* Check if the input buffer is avalaible. */
|
2018-07-10 11:43:27 -04:00
|
|
|
if (res->buf.size == 0) {
|
2018-11-15 05:03:21 -05:00
|
|
|
/* buf.size==0 means we failed to get a buffer and were
|
|
|
|
|
* already subscribed to a wait list to get a buffer.
|
|
|
|
|
*/
|
2017-10-31 15:43:01 -04:00
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res->flags & (CF_SHUTW|CF_SHUTW_NOW))
|
|
|
|
|
appctx->st0 = HTTP_CACHE_END;
|
|
|
|
|
|
|
|
|
|
/* buffer are aligned there, should be fine */
|
2018-10-26 08:29:22 -04:00
|
|
|
if (appctx->st0 == HTTP_CACHE_HEADER || appctx->st0 == HTTP_CACHE_INIT) {
|
2018-10-22 12:01:48 -04:00
|
|
|
int len = first->len - *sent - sizeof(struct cache_entry);
|
|
|
|
|
|
|
|
|
|
if (len > 0) {
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
ret = cache_channel_row_data_get(appctx, len);
|
|
|
|
|
if (ret == -1)
|
|
|
|
|
appctx->st0 = HTTP_CACHE_END;
|
|
|
|
|
else
|
|
|
|
|
*sent += ret;
|
2018-10-26 08:29:22 -04:00
|
|
|
if (appctx->st0 == HTTP_CACHE_INIT && *sent > cache_ptr->eoh &&
|
|
|
|
|
cache_channel_append_age_header(cache_ptr, res))
|
|
|
|
|
appctx->st0 = HTTP_CACHE_HEADER;
|
2018-10-22 12:01:48 -04:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*sent = 0;
|
|
|
|
|
appctx->st0 = HTTP_CACHE_FWD;
|
2017-10-31 15:43:01 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (appctx->st0 == HTTP_CACHE_FWD) {
|
|
|
|
|
/* eat the whole request */
|
2018-06-19 01:13:36 -04:00
|
|
|
co_skip(si_oc(si), co_data(si_oc(si))); // NOTE: when disabled does not repport the correct status code
|
2017-10-31 15:43:01 -04:00
|
|
|
res->flags |= CF_READ_NULL;
|
|
|
|
|
si_shutr(si);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
|
|
|
|
|
si_shutw(si);
|
|
|
|
|
out:
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
enum act_parse_ret parse_cache_store(const char **args, int *orig_arg, struct proxy *proxy,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
struct flt_conf *fconf;
|
|
|
|
|
int cur_arg = *orig_arg;
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_store_cache;
|
|
|
|
|
|
|
|
|
|
if (!*args[cur_arg] || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
|
|
|
|
|
memprintf(err, "expects a cache name");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* check if a cache filter was already registered with this cache
|
|
|
|
|
* name, if that's the case, must use it. */
|
|
|
|
|
list_for_each_entry(fconf, &proxy->filter_configs, list) {
|
|
|
|
|
if (fconf->id == cache_store_flt_id && !strcmp((char *)fconf->conf, args[cur_arg])) {
|
|
|
|
|
rule->arg.act.p[0] = fconf->conf;
|
|
|
|
|
(*orig_arg)++;
|
|
|
|
|
/* filter already registered */
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rule->arg.act.p[0] = strdup(args[cur_arg]);
|
|
|
|
|
if (!rule->arg.act.p[0]) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("config: %s '%s': out of memory\n", proxy_type_str(proxy), proxy->id);
|
2017-10-30 06:15:51 -04:00
|
|
|
err++;
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
/* register a filter to fill the cache buffer */
|
|
|
|
|
fconf = calloc(1, sizeof(*fconf));
|
|
|
|
|
if (!fconf) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("config: %s '%s': out of memory\n",
|
|
|
|
|
proxy_type_str(proxy), proxy->id);
|
2017-10-30 06:15:51 -04:00
|
|
|
err++;
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
fconf->id = cache_store_flt_id;
|
|
|
|
|
fconf->conf = rule->arg.act.p[0]; /* store the proxy name */
|
|
|
|
|
fconf->ops = &cache_ops;
|
|
|
|
|
LIST_ADDQ(&proxy->filter_configs, &fconf->list);
|
|
|
|
|
|
|
|
|
|
(*orig_arg)++;
|
|
|
|
|
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
|
|
|
|
|
err:
|
2017-11-01 09:04:02 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
2017-10-30 06:15:51 -04:00
|
|
|
}
|
|
|
|
|
|
2017-11-23 13:43:17 -05:00
|
|
|
/* This produces a sha1 hash of the concatenation of the first
|
|
|
|
|
* occurrence of the Host header followed by the path component if it
|
|
|
|
|
* begins with a slash ('/'). */
|
|
|
|
|
int sha1_hosturi(struct http_txn *txn)
|
|
|
|
|
{
|
|
|
|
|
struct hdr_ctx ctx;
|
|
|
|
|
|
|
|
|
|
blk_SHA_CTX sha1_ctx;
|
2018-07-13 05:56:34 -04:00
|
|
|
struct buffer *trash;
|
2017-11-23 13:43:17 -05:00
|
|
|
char *path;
|
|
|
|
|
char *end;
|
|
|
|
|
trash = get_trash_chunk();
|
|
|
|
|
|
|
|
|
|
/* retrive the host */
|
|
|
|
|
ctx.idx = 0;
|
2018-06-19 01:13:36 -04:00
|
|
|
if (!http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx))
|
2017-11-23 13:43:17 -05:00
|
|
|
return 0;
|
|
|
|
|
chunk_strncat(trash, ctx.line + ctx.val, ctx.vlen);
|
|
|
|
|
|
|
|
|
|
/* now retrieve the path */
|
2018-06-19 01:13:36 -04:00
|
|
|
end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
|
2018-09-10 11:45:34 -04:00
|
|
|
path = http_txn_get_path(txn);
|
2017-11-23 13:43:17 -05:00
|
|
|
if (!path)
|
|
|
|
|
return 0;
|
|
|
|
|
chunk_strncat(trash, path, end - path);
|
|
|
|
|
|
|
|
|
|
/* hash everything */
|
|
|
|
|
blk_SHA1_Init(&sha1_ctx);
|
2018-07-13 04:54:26 -04:00
|
|
|
blk_SHA1_Update(&sha1_ctx, trash->area, trash->data);
|
2017-11-23 13:43:17 -05:00
|
|
|
blk_SHA1_Final((unsigned char *)txn->cache_hash, &sha1_ctx);
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
|
|
|
|
|
enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *px,
|
|
|
|
|
struct session *sess, struct stream *s, int flags)
|
|
|
|
|
{
|
2017-10-31 15:43:01 -04:00
|
|
|
|
|
|
|
|
struct cache_entry *res;
|
|
|
|
|
struct cache *cache = (struct cache *)rule->arg.act.p[0];
|
|
|
|
|
|
2017-12-22 11:47:35 -05:00
|
|
|
check_request_for_cacheability(s, &s->req);
|
|
|
|
|
if ((s->txn->flags & (TX_CACHE_IGNORE|TX_CACHEABLE)) == TX_CACHE_IGNORE)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
2017-12-22 10:32:43 -05:00
|
|
|
if (!sha1_hosturi(s->txn))
|
|
|
|
|
return ACT_RET_CONT;
|
2017-11-23 13:43:17 -05:00
|
|
|
|
2017-12-22 11:47:35 -05:00
|
|
|
if (s->txn->flags & TX_CACHE_IGNORE)
|
|
|
|
|
return ACT_RET_CONT;
|
|
|
|
|
|
2017-11-20 13:13:12 -05:00
|
|
|
shctx_lock(shctx_ptr(cache));
|
2017-11-23 13:43:17 -05:00
|
|
|
res = entry_exist(cache, s->txn->cache_hash);
|
2017-10-31 15:43:01 -04:00
|
|
|
if (res) {
|
|
|
|
|
struct appctx *appctx;
|
2017-11-20 13:13:12 -05:00
|
|
|
shctx_row_inc_hot(shctx_ptr(cache), block_ptr(res));
|
|
|
|
|
shctx_unlock(shctx_ptr(cache));
|
2017-10-31 15:43:01 -04:00
|
|
|
s->target = &http_cache_applet.obj_type;
|
|
|
|
|
if ((appctx = stream_int_register_handler(&s->si[1], objt_applet(s->target)))) {
|
|
|
|
|
appctx->st0 = HTTP_CACHE_INIT;
|
|
|
|
|
appctx->rule = rule;
|
|
|
|
|
appctx->ctx.cache.entry = res;
|
2018-10-22 12:01:48 -04:00
|
|
|
appctx->ctx.cache.next = NULL;
|
|
|
|
|
appctx->ctx.cache.sent = 0;
|
2017-11-01 09:04:02 -04:00
|
|
|
return ACT_RET_CONT;
|
2017-10-31 15:43:01 -04:00
|
|
|
} else {
|
2017-11-21 14:01:28 -05:00
|
|
|
shctx_lock(shctx_ptr(cache));
|
|
|
|
|
shctx_row_dec_hot(shctx_ptr(cache), block_ptr(res));
|
|
|
|
|
shctx_unlock(shctx_ptr(cache));
|
2017-11-01 09:04:02 -04:00
|
|
|
return ACT_RET_YIELD;
|
2017-10-31 15:43:01 -04:00
|
|
|
}
|
|
|
|
|
}
|
2017-11-20 13:13:12 -05:00
|
|
|
shctx_unlock(shctx_ptr(cache));
|
2017-11-01 09:04:02 -04:00
|
|
|
return ACT_RET_CONT;
|
2017-10-30 06:15:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum act_parse_ret parse_cache_use(const char **args, int *orig_arg, struct proxy *proxy,
|
|
|
|
|
struct act_rule *rule, char **err)
|
|
|
|
|
{
|
|
|
|
|
int cur_arg = *orig_arg;
|
|
|
|
|
|
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
|
|
|
rule->action_ptr = http_action_req_cache_use;
|
|
|
|
|
|
|
|
|
|
if (!*args[cur_arg] || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
|
|
|
|
|
memprintf(err, "expects a cache name");
|
|
|
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rule->arg.act.p[0] = strdup(args[cur_arg]);
|
|
|
|
|
if (!rule->arg.act.p[0]) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("config: %s '%s': out of memory\n", proxy_type_str(proxy), proxy->id);
|
2017-10-30 06:15:51 -04:00
|
|
|
err++;
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(*orig_arg)++;
|
|
|
|
|
return ACT_RET_PRS_OK;
|
|
|
|
|
|
|
|
|
|
err:
|
2017-11-01 09:04:02 -04:00
|
|
|
return ACT_RET_PRS_ERR;
|
2017-10-30 06:15:51 -04:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cfg_parse_cache(const char *file, int linenum, char **args, int kwm)
|
|
|
|
|
{
|
|
|
|
|
int err_code = 0;
|
|
|
|
|
|
|
|
|
|
if (strcmp(args[0], "cache") == 0) { /* new cache section */
|
|
|
|
|
|
|
|
|
|
if (!*args[1]) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("parsing [%s:%d] : '%s' expects an <id> argument\n",
|
|
|
|
|
file, linenum, args[0]);
|
2017-10-30 06:15:51 -04:00
|
|
|
err_code |= ERR_ALERT | ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tmp_cache_config == NULL) {
|
|
|
|
|
tmp_cache_config = calloc(1, sizeof(*tmp_cache_config));
|
|
|
|
|
if (!tmp_cache_config) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
|
2017-10-30 06:15:51 -04:00
|
|
|
err_code |= ERR_ALERT | ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strlcpy2(tmp_cache_config->id, args[1], 33);
|
|
|
|
|
if (strlen(args[1]) > 32) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_warning("parsing [%s:%d]: cache id is limited to 32 characters, truncate to '%s'.\n",
|
|
|
|
|
file, linenum, tmp_cache_config->id);
|
2017-10-30 06:15:51 -04:00
|
|
|
err_code |= ERR_WARN;
|
|
|
|
|
}
|
2017-11-24 12:53:43 -05:00
|
|
|
tmp_cache_config->maxage = 60;
|
2017-10-30 06:15:51 -04:00
|
|
|
tmp_cache_config->maxblocks = 0;
|
2018-10-22 10:59:13 -04:00
|
|
|
tmp_cache_config->maxobjsz = 0;
|
2017-10-30 06:15:51 -04:00
|
|
|
}
|
|
|
|
|
} else if (strcmp(args[0], "total-max-size") == 0) {
|
2018-10-25 14:17:45 -04:00
|
|
|
unsigned long int maxsize;
|
|
|
|
|
char *err;
|
2017-10-30 06:15:51 -04:00
|
|
|
|
|
|
|
|
if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-25 14:17:45 -04:00
|
|
|
maxsize = strtoul(args[1], &err, 10);
|
|
|
|
|
if (err == args[1] || *err != '\0') {
|
|
|
|
|
ha_warning("parsing [%s:%d]: total-max-size wrong value '%s'\n",
|
|
|
|
|
file, linenum, args[1]);
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (maxsize > (UINT_MAX >> 20)) {
|
|
|
|
|
ha_warning("parsing [%s:%d]: \"total-max-size\" (%s) must not be greater than %u\n",
|
|
|
|
|
file, linenum, args[1], UINT_MAX >> 20);
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
/* size in megabytes */
|
2018-10-25 14:17:45 -04:00
|
|
|
maxsize *= 1024 * 1024 / CACHE_BLOCKSIZE;
|
2017-10-30 06:15:51 -04:00
|
|
|
tmp_cache_config->maxblocks = maxsize;
|
2017-11-24 12:53:43 -05:00
|
|
|
} else if (strcmp(args[0], "max-age") == 0) {
|
|
|
|
|
if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!*args[1]) {
|
|
|
|
|
ha_warning("parsing [%s:%d]: '%s' expects an age parameter in seconds.\n",
|
|
|
|
|
file, linenum, args[0]);
|
|
|
|
|
err_code |= ERR_WARN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmp_cache_config->maxage = atoi(args[1]);
|
2018-10-22 10:59:13 -04:00
|
|
|
} else if (strcmp(args[0], "max-object-size") == 0) {
|
2018-10-25 14:29:31 -04:00
|
|
|
unsigned int maxobjsz;
|
|
|
|
|
char *err;
|
|
|
|
|
|
2018-10-22 10:59:13 -04:00
|
|
|
if (alertif_too_many_args(1, file, linenum, args, &err_code)) {
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!*args[1]) {
|
|
|
|
|
ha_warning("parsing [%s:%d]: '%s' expects a maximum file size parameter in bytes.\n",
|
|
|
|
|
file, linenum, args[0]);
|
|
|
|
|
err_code |= ERR_WARN;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-25 14:29:31 -04:00
|
|
|
maxobjsz = strtoul(args[1], &err, 10);
|
|
|
|
|
if (err == args[1] || *err != '\0') {
|
|
|
|
|
ha_warning("parsing [%s:%d]: max-object-size wrong value '%s'\n",
|
|
|
|
|
file, linenum, args[1]);
|
|
|
|
|
err_code |= ERR_ABORT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
tmp_cache_config->maxobjsz = maxobjsz;
|
2018-10-22 10:59:13 -04:00
|
|
|
}
|
|
|
|
|
else if (*args[0] != 0) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("parsing [%s:%d] : unknown keyword '%s' in 'cache' section\n", file, linenum, args[0]);
|
2017-10-30 06:15:51 -04:00
|
|
|
err_code |= ERR_ALERT | ERR_FATAL;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
out:
|
|
|
|
|
return err_code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* once the cache section is parsed */
|
|
|
|
|
|
|
|
|
|
int cfg_post_parse_section_cache()
|
|
|
|
|
{
|
|
|
|
|
struct shared_context *shctx;
|
|
|
|
|
int err_code = 0;
|
|
|
|
|
int ret_shctx;
|
|
|
|
|
|
|
|
|
|
if (tmp_cache_config) {
|
|
|
|
|
struct cache *cache;
|
|
|
|
|
|
|
|
|
|
if (tmp_cache_config->maxblocks <= 0) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("Size not specified for cache '%s'\n", tmp_cache_config->id);
|
2017-10-30 06:15:51 -04:00
|
|
|
err_code |= ERR_FATAL | ERR_ALERT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-25 14:29:31 -04:00
|
|
|
if (!tmp_cache_config->maxobjsz) {
|
2018-10-22 10:59:13 -04:00
|
|
|
/* Default max. file size is a 256th of the cache size. */
|
|
|
|
|
tmp_cache_config->maxobjsz =
|
|
|
|
|
(tmp_cache_config->maxblocks * CACHE_BLOCKSIZE) >> 8;
|
2018-10-25 14:29:31 -04:00
|
|
|
}
|
|
|
|
|
else if (tmp_cache_config->maxobjsz > tmp_cache_config->maxblocks * CACHE_BLOCKSIZE / 2) {
|
|
|
|
|
ha_alert("\"max-object-size\" is limited to an half of \"total-max-size\" => %u\n", tmp_cache_config->maxblocks * CACHE_BLOCKSIZE / 2);
|
|
|
|
|
err_code |= ERR_FATAL | ERR_ALERT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2018-10-22 10:59:13 -04:00
|
|
|
|
|
|
|
|
ret_shctx = shctx_init(&shctx, tmp_cache_config->maxblocks, CACHE_BLOCKSIZE,
|
|
|
|
|
tmp_cache_config->maxobjsz, sizeof(struct cache), 1);
|
2017-10-31 09:33:34 -04:00
|
|
|
|
2018-10-25 14:18:59 -04:00
|
|
|
if (ret_shctx <= 0) {
|
2017-10-30 06:15:51 -04:00
|
|
|
if (ret_shctx == SHCTX_E_INIT_LOCK)
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("Unable to initialize the lock for the cache.\n");
|
2017-10-30 06:15:51 -04:00
|
|
|
else
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("Unable to allocate cache.\n");
|
2017-10-30 06:15:51 -04:00
|
|
|
|
|
|
|
|
err_code |= ERR_FATAL | ERR_ALERT;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2017-11-20 13:13:12 -05:00
|
|
|
shctx->free_block = cache_free_blocks;
|
2017-10-30 06:15:51 -04:00
|
|
|
memcpy(shctx->data, tmp_cache_config, sizeof(struct cache));
|
|
|
|
|
cache = (struct cache *)shctx->data;
|
|
|
|
|
cache->entries = EB_ROOT_UNIQUE;
|
|
|
|
|
LIST_ADDQ(&caches, &cache->list);
|
|
|
|
|
}
|
|
|
|
|
out:
|
|
|
|
|
free(tmp_cache_config);
|
|
|
|
|
tmp_cache_config = NULL;
|
|
|
|
|
return err_code;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Resolve the cache name to a pointer once the file is completely read.
|
|
|
|
|
*/
|
|
|
|
|
int cfg_cache_postparser()
|
|
|
|
|
{
|
|
|
|
|
struct act_rule *hresrule, *hrqrule;
|
|
|
|
|
void *cache_ptr;
|
|
|
|
|
struct cache *cache;
|
|
|
|
|
struct proxy *curproxy = NULL;
|
|
|
|
|
int err = 0;
|
|
|
|
|
struct flt_conf *fconf;
|
|
|
|
|
|
2017-11-24 10:54:05 -05:00
|
|
|
for (curproxy = proxies_list; curproxy; curproxy = curproxy->next) {
|
2017-10-30 06:15:51 -04:00
|
|
|
|
|
|
|
|
/* resolve the http response cache name to a ptr in the action rule */
|
|
|
|
|
list_for_each_entry(hresrule, &curproxy->http_res_rules, list) {
|
|
|
|
|
if (hresrule->action != ACT_CUSTOM ||
|
|
|
|
|
hresrule->action_ptr != http_action_store_cache)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
cache_ptr = hresrule->arg.act.p[0];
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(cache, &caches, list) {
|
|
|
|
|
if (!strcmp(cache->id, cache_ptr)) {
|
|
|
|
|
/* don't free there, it's still used in the filter conf */
|
|
|
|
|
cache_ptr = cache;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cache_ptr == hresrule->arg.act.p[0]) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("Proxy '%s': unable to find the cache '%s' referenced by http-response cache-store rule.\n",
|
|
|
|
|
curproxy->id, (char *)hresrule->arg.act.p[0]);
|
2017-10-30 06:15:51 -04:00
|
|
|
err++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hresrule->arg.act.p[0] = cache_ptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* resolve the http request cache name to a ptr in the action rule */
|
|
|
|
|
list_for_each_entry(hrqrule, &curproxy->http_req_rules, list) {
|
|
|
|
|
if (hrqrule->action != ACT_CUSTOM ||
|
|
|
|
|
hrqrule->action_ptr != http_action_req_cache_use)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
cache_ptr = hrqrule->arg.act.p[0];
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(cache, &caches, list) {
|
|
|
|
|
if (!strcmp(cache->id, cache_ptr)) {
|
|
|
|
|
free(cache_ptr);
|
|
|
|
|
cache_ptr = cache;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cache_ptr == hrqrule->arg.act.p[0]) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("Proxy '%s': unable to find the cache '%s' referenced by http-request cache-use rule.\n",
|
|
|
|
|
curproxy->id, (char *)hrqrule->arg.act.p[0]);
|
2017-10-30 06:15:51 -04:00
|
|
|
err++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hrqrule->arg.act.p[0] = cache_ptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* resolve the cache name to a ptr in the filter config */
|
|
|
|
|
list_for_each_entry(fconf, &curproxy->filter_configs, list) {
|
|
|
|
|
|
2017-11-02 11:38:42 -04:00
|
|
|
if (fconf->id != cache_store_flt_id)
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
cache_ptr = fconf->conf;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(cache, &caches, list) {
|
|
|
|
|
if (!strcmp(cache->id, cache_ptr)) {
|
|
|
|
|
/* there can be only one filter per cache, so we free it there */
|
|
|
|
|
free(cache_ptr);
|
|
|
|
|
cache_ptr = cache;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cache_ptr == fconf->conf) {
|
2017-11-24 10:50:31 -05:00
|
|
|
ha_alert("Proxy '%s': unable to find the cache '%s' referenced by the filter 'cache'.\n",
|
|
|
|
|
curproxy->id, (char *)fconf->conf);
|
2017-10-30 06:15:51 -04:00
|
|
|
err++;
|
|
|
|
|
}
|
|
|
|
|
fconf->conf = cache_ptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct flt_ops cache_ops = {
|
|
|
|
|
.init = cache_store_init,
|
|
|
|
|
|
2017-10-31 09:33:34 -04:00
|
|
|
/* Handle channels activity */
|
|
|
|
|
.channel_start_analyze = cache_store_chn_start_analyze,
|
2017-11-24 08:33:54 -05:00
|
|
|
.channel_end_analyze = cache_store_chn_end_analyze,
|
2017-10-31 09:33:34 -04:00
|
|
|
|
|
|
|
|
/* Filter HTTP requests and responses */
|
|
|
|
|
.http_headers = cache_store_http_headers,
|
|
|
|
|
.http_end = cache_store_http_end,
|
|
|
|
|
|
|
|
|
|
.http_forward_data = cache_store_http_forward_data,
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
};
|
|
|
|
|
|
2018-04-18 07:26:46 -04:00
|
|
|
static int cli_parse_show_cache(char **args, char *payload, struct appctx *appctx, void *private)
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
{
|
|
|
|
|
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int cli_io_handler_show_cache(struct appctx *appctx)
|
|
|
|
|
{
|
|
|
|
|
struct cache* cache = appctx->ctx.cli.p0;
|
|
|
|
|
struct stream_interface *si = appctx->owner;
|
|
|
|
|
|
|
|
|
|
if (cache == NULL) {
|
|
|
|
|
cache = LIST_ELEM((caches).n, typeof(struct cache *), list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_for_each_entry_from(cache, &caches, list) {
|
|
|
|
|
struct eb32_node *node = NULL;
|
|
|
|
|
unsigned int next_key;
|
|
|
|
|
struct cache_entry *entry;
|
|
|
|
|
|
|
|
|
|
next_key = appctx->ctx.cli.i0;
|
2018-04-04 05:56:43 -04:00
|
|
|
if (!next_key) {
|
|
|
|
|
chunk_printf(&trash, "%p: %s (shctx:%p, available blocks:%d)\n", cache, cache->id, shctx_ptr(cache), shctx_ptr(cache)->nbav);
|
|
|
|
|
if (ci_putchk(si_ic(si), &trash) == -1) {
|
2018-11-06 12:46:37 -05:00
|
|
|
si_cant_put(si);
|
2018-04-04 05:56:43 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
|
|
|
|
|
appctx->ctx.cli.p0 = cache;
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
|
|
|
|
|
shctx_lock(shctx_ptr(cache));
|
|
|
|
|
node = eb32_lookup_ge(&cache->entries, next_key);
|
|
|
|
|
if (!node) {
|
|
|
|
|
shctx_unlock(shctx_ptr(cache));
|
2018-04-04 05:56:43 -04:00
|
|
|
appctx->ctx.cli.i0 = 0;
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry = container_of(node, struct cache_entry, eb);
|
2018-04-04 05:56:43 -04:00
|
|
|
chunk_printf(&trash, "%p hash:%u size:%u (%u blocks), refcount:%u, expire:%d\n", entry, (*(unsigned int *)entry->hash), block_ptr(entry)->len, block_ptr(entry)->block_count, block_ptr(entry)->refcount, entry->expire - (int)now.tv_sec);
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
|
|
|
|
|
next_key = node->key + 1;
|
|
|
|
|
appctx->ctx.cli.i0 = next_key;
|
|
|
|
|
|
|
|
|
|
shctx_unlock(shctx_ptr(cache));
|
|
|
|
|
|
|
|
|
|
if (ci_putchk(si_ic(si), &trash) == -1) {
|
2018-11-06 12:46:37 -05:00
|
|
|
si_cant_put(si);
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct cli_kw_list cli_kws = {{},{
|
2017-11-22 10:41:26 -05:00
|
|
|
{ { "show", "cache", NULL }, "show cache : show cache status", cli_parse_show_cache, cli_io_handler_show_cache, NULL, NULL },
|
|
|
|
|
{{},}
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
}};
|
|
|
|
|
|
|
|
|
|
|
2017-10-30 06:15:51 -04:00
|
|
|
static struct action_kw_list http_res_actions = {
|
|
|
|
|
.kw = {
|
|
|
|
|
{ "cache-store", parse_cache_store },
|
|
|
|
|
{ NULL, NULL }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct action_kw_list http_req_actions = {
|
|
|
|
|
.kw = {
|
|
|
|
|
{ "cache-use", parse_cache_use },
|
|
|
|
|
{ NULL, NULL }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct applet http_cache_applet = {
|
|
|
|
|
.obj_type = OBJ_TYPE_APPLET,
|
|
|
|
|
.name = "<CACHE>", /* used for logging */
|
2017-10-31 15:43:01 -04:00
|
|
|
.fct = http_cache_io_handler,
|
2017-11-24 08:33:55 -05:00
|
|
|
.release = http_cache_applet_release,
|
2017-10-30 06:15:51 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
__attribute__((constructor))
|
|
|
|
|
static void __cache_init(void)
|
|
|
|
|
{
|
|
|
|
|
cfg_register_section("cache", cfg_parse_cache, cfg_post_parse_section_cache);
|
|
|
|
|
cfg_register_postparser("cache", cfg_cache_postparser);
|
MEDIUM: cache: "show cache" on the cli
The cli command "show cache" displays the status of the cache, the first
displayed line is the shctx informations with how much blocks available
blocks it contains (blocks are 1k by default).
The next lines are the objects stored in the cache tree, the pointer,
the size of the object and how much blocks it uses, a refcount for the
number of users of the object, and the remaining expiration time (which
can be negative if expired)
Example:
$ echo "show cache" | socat - /run/haproxy.sock
0x7fa54e9ab03a: foobar (shctx:0x7fa54e9ab000, available blocks:3921)
0x7fa54ed65b8c (size: 43190 (43 blocks), refcount:2, expire: 2)
0x7fa54ecf1b4c (size: 45238 (45 blocks), refcount:0, expire: 2)
0x7fa54ed70cec (size: 61622 (61 blocks), refcount:0, expire: 2)
0x7fa54ecdbcac (size: 42166 (42 blocks), refcount:1, expire: 2)
0x7fa54ec9736c (size: 44214 (44 blocks), refcount:2, expire: 2)
0x7fa54eca28ec (size: 46262 (46 blocks), refcount:2, expire: -2)
2017-11-21 14:01:26 -05:00
|
|
|
cli_register_kw(&cli_kws);
|
2017-10-30 06:15:51 -04:00
|
|
|
http_res_keywords_register(&http_res_actions);
|
|
|
|
|
http_req_keywords_register(&http_req_actions);
|
2017-11-24 11:34:44 -05:00
|
|
|
pool_head_cache_st = create_pool("cache_st", sizeof(struct cache_st), MEM_F_SHARED);
|
2017-10-30 06:15:51 -04:00
|
|
|
}
|
|
|
|
|
|