Hi Hyeonggeun,
On Tue, Jan 13, 2026 at 07:29:29PM +0100, Willy Tarreau wrote:
> On Wed, Jan 14, 2026 at 03:20:27AM +0900, Hyeonggeun Oh wrote:
> > ---
> >
> > Hi Willy,
> >
> > This is a fix of segmentation fault error (20260113):
> >
> > The root cause was identified as a call to var_set() in the head of
> > action_store() function which passed an unintialized smp structure. It led
> > to
> > var_set() accessing invalid mem addresses, resulting in a crash. I checked
> > that this var_set() line is unnessary since there was confusion during the
> > initial stages of code modification, and this confusion was not subsequently
> > resolved.
> (...)
>
> OK thank you, will try it again tomorrow and hopefully finish the merge
> this time!
So now it looks better and I could even start to test it. However I'm
now seeing this:
[ALERT] (10478) : config : parsing [test-set-var3.cfg:36] : error detected
in frontend 'fe1' while parsing 'http-request return' rule : failed to parse
sample expression <dump_all_vars()]]> : invalid args in fetch method
'dump_all_vars' : invalid scope '', must be one of: sess, txn, req, res, proc.
for a config containing:
http-request return status 200 hdr x-var "... all=%[dump_all_vars()]"
But the doc says:
dump_all_vars([<scope>][,<prefix>][,<delimiter>]) : string
...
Arguments:
- <scope> (optional): sess, txn, req, res, or proc. If omitted, the scope
is determined by context (txn for streams, sess for sessions, proc
otherwise).
This makes me think that while the empty choice doesn't work in the
config parser, it's probably more confusing than anything else: most
users don't know where a stream is present (tcp-request content,
tcp-response, http-request, http-response, redirect etc) or when only
a session is present (tcp-request connection, tcp-request session, ...).
In addition, those where neither are present probably only concern
"setenv" directives in the global section. And having a stream (i.e.
being in the process of handling a request) doesn't suddenly make
session-level variables uninteresting. Thus in my opinion, either we
consider than an empty scope requires to iterate over all scopes, or
we make the scope mandatory and then it's just part of the prefix in
the end (though there may be some value in keeping it separate so as
to permit to check its validity).
Since I had performed some minor edits (dropped remaining lone spaces
left on some lines by your editor, and properly wrapped the commit
messages), I'm attaching the current series for you to restart from
(you can apply the patches using "git am 000*.patch"). If you prefer
I've also pushed it as branch 20260116-dump-vars-3.
Thanks,
Willy
>From 4984f839d82319ac67e48064831c93029a2924fa Mon Sep 17 00:00:00 2001
From: Hyeonggeun Oh <[email protected]>
Date: Tue, 13 Jan 2026 03:07:15 +0900
Subject: MINOR: tools: add chunk_escape_string() helper function
This function takes a string appends it to a buffer in a format
compatible with most languages (double-quoted, with special characters
escaped). It handles standard escape sequences like \n, \r, \", \\.
This generic utility is desined to be used for logging or debugging
purposes where arbitrary string data needs to be safely emitted without
breaking the output format. It will be primarily used by the upcoming
dump_all_vars() sample fetch to dump variable contents safely.
---
include/haproxy/tools.h | 7 ++++++
src/tools.c | 54 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
diff --git a/include/haproxy/tools.h b/include/haproxy/tools.h
index f181a76014..f84f02d7d6 100644
--- a/include/haproxy/tools.h
+++ b/include/haproxy/tools.h
@@ -466,6 +466,13 @@ char *escape_string(char *start, char *stop,
const char escape, const long *map,
const char *string, const char *string_stop);
+/*
+ * Appends a quoted and escaped string to a chunk buffer. The string is
+ * enclosed in double quotes and special characters are escaped with backslash.
+ * Returns 0 on success, -1 if the buffer is too small (output is rolled back).
+ */
+int chunk_escape_string(struct buffer *chunk, const char *str, size_t len);
+
/* Below are RFC8949 compliant cbor encode helper functions, see source
* file for functions descriptions
*/
diff --git a/src/tools.c b/src/tools.c
index 460a34e18f..9a4e1bf688 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -2129,6 +2129,60 @@ char *escape_string(char *start, char *stop,
return NULL;
}
+/*
+ * Appends a quoted and escaped string to a chunk buffer. The string is
+ * enclosed in double quotes and special characters are escaped with backslash:
+ * ", \, \r, \n, \b, \0
+ * Returns 0 on success, -1 if the buffer is too small (output is rolled back).
+ */
+int chunk_escape_string(struct buffer *chunk, const char *str, size_t len)
+{
+ size_t initial_data = chunk->data;
+ size_t i;
+
+ /* Opening quote */
+ if (chunk->data + 1 >= chunk->size)
+ return -1;
+ chunk->area[chunk->data++] = '"';
+
+ /* Escape and append each character */
+ for (i = 0; i < len; i++) {
+ unsigned char c = str[i];
+ const char *esc = NULL;
+
+ if (c == '"') esc = "\\\"";
+ else if (c == '\\') esc = "\\\\";
+ else if (c == '\r') esc = "\\r";
+ else if (c == '\n') esc = "\\n";
+ else if (c == '\b') esc = "\\b";
+ else if (c == '\0') esc = "\\0";
+
+ if (esc) {
+ if (chunk->data + 2 >= chunk->size) {
+ chunk->data = initial_data;
+ return -1;
+ }
+ chunk->area[chunk->data++] = esc[0];
+ chunk->area[chunk->data++] = esc[1];
+ } else {
+ if (chunk->data + 1 >= chunk->size) {
+ chunk->data = initial_data;
+ return -1;
+ }
+ chunk->area[chunk->data++] = c;
+ }
+ }
+
+ /* Closing quote */
+ if (chunk->data + 1 >= chunk->size) {
+ chunk->data = initial_data;
+ return -1;
+ }
+ chunk->area[chunk->data++] = '"';
+
+ return 0;
+}
+
/* CBOR helper to encode an uint64 value with prefix (3bits MAJOR type)
* according to RFC8949
*
--
2.35.3
>From 78b858f43c0a04ae3c1c792fd0c0e60ea64d3f1b Mon Sep 17 00:00:00 2001
From: Hyeonggeun Oh <[email protected]>
Date: Wed, 14 Jan 2026 03:20:27 +0900
Subject: MINOR: vars: store variable names for runtime access
Currently, variable names are only used during parsing and are not
stored at runtime. This makes it impossible to iterate through
variables and retrieve their names.
This patch adds infrastructure to store variable names:
- Add 'name' and 'name_len' fields to var_desc structure
- Add 'name' field to var structure
- Add VDF_NAME_ALLOCATED flag to track memory ownership
- Store names in vars_fill_desc(), var_set(), vars_check_arg(),
and parse_store()
- Free names in var_clear() and release_store_rule()
This prepares the ground for implementing dump_all_vars() in the
next commit.
---
include/haproxy/vars-t.h | 4 ++++
src/vars.c | 47 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/include/haproxy/vars-t.h b/include/haproxy/vars-t.h
index 105506977c..24f6297435 100644
--- a/include/haproxy/vars-t.h
+++ b/include/haproxy/vars-t.h
@@ -57,12 +57,15 @@ struct vars {
};
#define VDF_PARENT_CTX 0x00000001 // Set if the variable is related to
the parent stream
+#define VDF_NAME_ALLOCATED 0x00000002 // Set if name was allocated and
must be freed
/* This struct describes a variable as found in an arg_data */
struct var_desc {
uint64_t name_hash;
enum vars_scope scope;
uint flags; /*VDF_* */
+ const char *name; /* variable name (not owned) */
+ size_t name_len; /* variable name length */
};
struct var {
@@ -70,6 +73,7 @@ struct var {
uint64_t name_hash; /* XXH3() of the variable's name, indexed by
<name_node> */
uint flags; // VF_*
/* 32-bit hole here */
+ char *name; /* variable name (allocated) */
struct sample_data data; /* data storage. */
};
diff --git a/src/vars.c b/src/vars.c
index 1755bf8ad1..3e46340cd4 100644
--- a/src/vars.c
+++ b/src/vars.c
@@ -178,6 +178,8 @@ unsigned int var_clear(struct vars *vars, struct var *var,
int force)
{
unsigned int size = 0;
+ ha_free(&var->name);
+
if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
ha_free(&var->data.u.str.area);
size += var->data.u.str.data;
@@ -322,6 +324,8 @@ static int vars_fill_desc(const char *name, int len, struct
var_desc *desc, char
}
desc->name_hash = XXH3(name, len, var_name_hash_seed);
+ desc->name = name;
+ desc->name_len = len;
return 1;
}
@@ -431,6 +435,16 @@ int var_set(const struct var_desc *desc, struct sample
*smp, uint flags)
goto unlock;
var->name_hash = desc->name_hash;
var->flags = flags & VF_PERMANENT;
+
+ /* Save variable name */
+ var->name = NULL;
+ if (desc->name && desc->name_len > 0) {
+ var->name = my_strndup(desc->name, desc->name_len);
+ if (!var->name) {
+ pool_free(var_pool, var);
+ goto unlock;
+ }
+ }
var->data.type = SMP_T_ANY;
cebu64_item_insert(&vars->name_root[var->name_hash %
VAR_NAME_ROOTS], name_node, name_hash, var);
}
@@ -623,6 +637,7 @@ int vars_check_arg(struct arg *arg, char **err)
{
struct sample empty_smp = { };
struct var_desc desc;
+ char *saved_name = NULL;
/* Check arg type. */
if (arg->type != ARGT_STR) {
@@ -634,15 +649,27 @@ int vars_check_arg(struct arg *arg, char **err)
if (!vars_fill_desc(arg->data.str.area, arg->data.str.data, &desc, err))
return 0;
- if (desc.scope == SCOPE_PROC && !var_set(&desc, &empty_smp,
VF_CREATEONLY|VF_PERMANENT))
+ /* Save variable name before destroying the chunk */
+ if (desc.name && desc.name_len > 0) {
+ saved_name = my_strndup(desc.name, desc.name_len);
+ if (!saved_name) {
+ memprintf(err, "out of memory");
+ return 0;
+ }
+ }
+
+ if (desc.scope == SCOPE_PROC && !var_set(&desc, &empty_smp,
VF_CREATEONLY|VF_PERMANENT)) {
+ if (desc.flags & VDF_NAME_ALLOCATED)
+ ha_free(&saved_name);
return 0;
+ }
/* properly destroy the chunk */
chunk_destroy(&arg->data.str);
/* Use the global variable name pointer. */
arg->type = ARGT_VAR;
- arg->data.var = desc;
+ arg->data.var = desc; /* desc.name already points to saved_name */
return 1;
}
@@ -868,6 +895,10 @@ static void release_store_rule(struct act_rule *rule)
lf_expr_deinit(&rule->arg.vars.fmt);
release_sample_expr(rule->arg.vars.expr);
+
+ /* Free variable name if allocated */
+ if (rule->arg.vars.desc.flags & VDF_NAME_ALLOCATED)
+ ha_free((char **)&rule->arg.vars.desc.name);
}
/* This two function checks the variable name and replace the
@@ -919,6 +950,7 @@ static enum act_parse_ret parse_store(const char **args,
int *arg, struct proxy
struct ist condition = IST_NULL;
struct ist var = IST_NULL;
struct ist varname_ist = IST_NULL;
+ char *saved_name = NULL;
if (strncmp(var_name, "set-var-fmt", 11) == 0) {
var_name += 11;
@@ -973,6 +1005,17 @@ static enum act_parse_ret parse_store(const char **args,
int *arg, struct proxy
if (!vars_fill_desc(var_name, var_len, &rule->arg.vars.desc, err))
return ACT_RET_PRS_ERR;
+ /* Save variable name for runtime use */
+ if (rule->arg.vars.desc.name && rule->arg.vars.desc.name_len > 0) {
+ saved_name = my_strndup(rule->arg.vars.desc.name,
rule->arg.vars.desc.name_len);
+ if (!saved_name) {
+ memprintf(err, "out of memory");
+ return ACT_RET_PRS_ERR;
+ }
+ rule->arg.vars.desc.name = saved_name;
+ rule->arg.vars.desc.flags |= VDF_NAME_ALLOCATED;
+ }
+
if (rule->arg.vars.desc.scope == SCOPE_PROC &&
!var_set(&rule->arg.vars.desc, &empty_smp,
VF_CREATEONLY|VF_PERMANENT))
return 0;
--
2.35.3
>From f7f1f83bda7968040ebe19334f4589ddf0369b17 Mon Sep 17 00:00:00 2001
From: Hyeonggeun Oh <[email protected]>
Date: Tue, 13 Jan 2026 03:07:16 +0900
Subject: MINOR: vars: implement dump_all_vars() sample fetch
This patch implements dump_all_vars([scope],[prefix]) sample fetch
function that dumps all variables in a given scope, optionally
filtered by name prefix.
Output format: var1=value1, var2=value2, ...
- String values are quoted and escaped (", , \r, \n, \b, \0)
- All sample types are supported via sample_convert()
- Scope can be: sess, txn, req, res, proc
- Prefix filtering is optional
Example usage:
http-request return string %[dump_all_vars(txn)]
http-request return string %[dump_all_vars(txn,user)]
This addresses GitHub issue #1623.
---
src/vars.c | 230 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 230 insertions(+)
diff --git a/src/vars.c b/src/vars.c
index 3e46340cd4..1a0c2b8f4c 100644
--- a/src/vars.c
+++ b/src/vars.c
@@ -58,6 +58,16 @@ static struct var_set_condition conditions_array[] = {
{ NULL, 0 }
};
+/* Variable scope names with their prefixes for output */
+static const char *var_scope_names[] = {
+ [SCOPE_SESS] = "sess.",
+ [SCOPE_TXN] = "txn.",
+ [SCOPE_REQ] = "req.",
+ [SCOPE_RES] = "res.",
+ [SCOPE_PROC] = "proc.",
+ [SCOPE_CHECK] = "check.",
+};
+
/* returns the struct vars pointer for a session, stream and scope, or NULL if
* it does not exist.
*/
@@ -352,6 +362,171 @@ static int smp_fetch_var(const struct arg *args, struct
sample *smp, const char
return vars_get_by_desc(var_desc, smp, def);
}
+/* Dumps all variables in the specified scope, optionally filtered by prefix.
+ * Output format: var1=value1, var2=value2, ...
+ * String values are quoted and escaped, binary values are hex-encoded (x...).
+ * Returns 1 on success, 0 on failure (buffer too small).
+ * Note: When using prefix filtering, all variables are still visited, so this
+ * should not be used with configs involving thousands of variables.
+ */
+static int smp_fetch_dump_all_vars(const struct arg *args, struct sample *smp,
const char *kw, void *private)
+{
+ struct buffer *output;
+ struct vars *vars;
+ struct var *var;
+ struct var_desc desc;
+ const char *prefix = NULL;
+ size_t prefix_len = 0;
+ const char *delim = ", ";
+ size_t delim_len = 2;
+ int first = 1;
+ int i;
+
+ /* Get output buffer */
+ output = get_trash_chunk();
+ chunk_reset(output);
+
+ /* Parse arguments */
+ if (args[0].type == ARGT_SINT) {
+ desc.scope = args[0].data.sint;
+ } else {
+ /* Auto-detect scope from context*/
+ if (smp->strm)
+ desc.scope = SCOPE_TXN;
+ else if (smp->sess)
+ desc.scope = SCOPE_SESS;
+ else
+ desc.scope = SCOPE_PROC;
+ }
+
+ /* Optional prefix filter */
+ if (args[1].type == ARGT_STR) {
+ prefix = args[1].data.str.area;
+ prefix_len = args[1].data.str.data;
+ }
+
+ /* Optional delimiter */
+ if (args[2].type == ARGT_STR) {
+ delim = args[2].data.str.area;
+ delim_len = args[2].data.str.data;
+ }
+
+ desc.flags = 0;
+ desc.name_hash = 0;
+
+ vars = get_vars(smp->sess, smp->strm, &desc);
+ if (!vars || vars->scope != desc.scope)
+ return 0;
+
+ vars_rdlock(vars);
+
+ /* Iterate through all variable roots */
+ for (i = 0; i < VAR_NAME_ROOTS; i++) {
+ var = cebu64_item_first(&vars->name_root[i], name_node,
name_hash, struct var);
+
+ while (var) {
+ const char *scope_prefix;
+
+ /* Check prefix filter */
+ if (prefix) {
+ if (!var->name || strncmp(var->name, prefix,
prefix_len) != 0) {
+ var =
cebu64_item_next(&vars->name_root[i], name_node, name_hash, var);
+ continue;
+ }
+ }
+
+ /* Add delimiter */
+ if (!first) {
+ if (output->data + delim_len >= output->size)
+ goto fail_unlock;
+ chunk_memcat(output, delim, delim_len);
+ }
+ first = 0;
+
+ /* Add variable name with scope prefix */
+ scope_prefix = var_scope_names[desc.scope];
+ if (var->name) {
+ if (chunk_appendf(output, "%s%s=",
scope_prefix, var->name) < 0)
+ goto fail_unlock;
+ } else {
+ if (chunk_appendf(output, "var_%016llx=",
(unsigned long long)var->name_hash) < 0)
+ goto fail_unlock;
+ }
+
+ /* Convert value based on type */
+ if (var->data.type == SMP_T_STR) {
+ /* String: quote and escape */
+ if (chunk_escape_string(output,
var->data.u.str.area, var->data.u.str.data) < 0)
+ goto fail_unlock;
+
+ } else if (var-> data.type == SMP_T_BIN) {
+ /* Binary: hex encode */
+ if (dump_binary(output, var->data.u.str.area,
var->data.u.str.data) != var->data.u.str.data)
+ goto fail_unlock;
+ } else if (var->data.type == SMP_T_SINT) {
+ /* Integer */
+ if (chunk_appendf(output, "%lld", (long
long)var->data.u.sint) < 0)
+ goto fail_unlock;
+
+ } else if (var->data.type == SMP_T_BOOL) {
+ /* Boolean */
+ const char *bool_str = var->data.u.sint ?
"true" : "false";
+ if (chunk_appendf(output, "%s", bool_str) < 0)
+ goto fail_unlock;
+
+ } else if (var->data.type == SMP_T_IPV4 ||
var->data.type == SMP_T_IPV6) {
+ /* Address */
+ char addr_str[INET6_ADDRSTRLEN];
+ const char *res;
+
+ if (var->data.type == SMP_T_IPV4)
+ res = inet_ntop(AF_INET,
&var->data.u.ipv4, addr_str, sizeof(addr_str));
+ else
+ res = inet_ntop(AF_INET6,
&var->data.u.ipv6, addr_str, sizeof(addr_str));
+
+ if (!res) {
+ if (chunk_appendf(output, "(addr)") < 0)
+ goto fail_unlock;
+ } else {
+ if (chunk_appendf(output, "%s",
addr_str) < 0)
+ goto fail_unlock;
+ }
+ } else if (var->data.type == SMP_T_METH) {
+ /* HTTP Method */
+ if (var->data.u.meth.meth == HTTP_METH_OTHER) {
+ if (chunk_escape_string(output,
var->data.u.meth.str.area, var->data.u.meth.str.data) < 0)
+ goto fail_unlock;
+ } else {
+ const char *method_str =
http_known_methods[var->data.u.meth.meth].ptr;
+ if (chunk_appendf(output, "\"%s\"",
method_str) < 0)
+ goto fail_unlock;
+ }
+ } else {
+ /* Other types: show type number */
+ if (chunk_appendf(output, "(type:%d)",
var->data.type) < 0)
+ goto fail_unlock;
+ }
+
+ var = cebu64_item_next(&vars->name_root[i], name_node,
name_hash, var);
+ }
+ }
+
+ vars_rdunlock(vars);
+
+ /* Set output sample */
+ smp->data.type = SMP_T_STR;
+ smp->data.u.str.area = output->area;
+ smp->data.u.str.data = output->data;
+ smp->flags &= ~SMP_F_CONST;
+
+ return 1;
+
+fail_unlock:
+ vars_rdunlock(vars);
+ output->data = 0;
+ return 0;
+}
+
/*
* Clear the contents of a variable so that it can be reset directly.
* This function is used just before a variable is filled out of a sample's
@@ -914,6 +1089,60 @@ static int smp_check_var(struct arg *args, char **err)
return vars_check_arg(&args[0], err);
}
+/* This function checks all arguments for dump_all_vars()
+ * Args: [scope], [prefix], [delimiter]
+ * Both arguments are optional
+ */
+static int smp_check_dump_all_vars(struct arg *args, char **err)
+{
+ /* First argument (scope) is optional */
+
+ if (args[0].type == ARGT_STR) {
+ const char *scope = args[0].data.str.area;
+ int scope_id = -1;
+ int i;
+ char buf[16];
+
+ if (args[0].data.str.data < sizeof(buf) - 1) {
+ snprintf(buf, sizeof(buf), "%s.", scope);
+
+ for (i = 0; i <= SCOPE_CHECK; i++) {
+ if (strcmp(buf, var_scope_names[i]) == 0) {
+ scope_id = i;
+ break;
+ }
+ }
+ }
+
+ if (scope_id == -1) {
+ memprintf(err, "invalid scope '%s', must be one of:
sess, txn, req, res, proc", scope);
+ return 0;
+ }
+
+ chunk_destroy(&args[0].data.str);
+ args[0].type = ARGT_SINT;
+ args[0].data.sint = scope_id;
+ }
+ else if (args[0].type != ARGT_STOP) {
+ memprintf(err, "first argument must be a string (scope) or
omitted");
+ return 0;
+ }
+
+ /* Second argument (prefix) is optional */
+ if (args[1].type != ARGT_STR && args[1].type != ARGT_STOP) {
+ memprintf(err, "second argument must be a string (prefix) or
omitted");
+ return 0;
+ }
+
+ /* Third argument (delimiter) is optional */
+ if (args[2].type != ARGT_STR && args[2].type != ARGT_STOP) {
+ memprintf(err, "third argument must be a string (delimiter) or
omitted");
+ return 0;
+ }
+
+ return 1;
+}
+
static int conv_check_var(struct arg *args, struct sample_conv *conv,
const char *file, int line, char **err_msg)
{
@@ -1408,6 +1637,7 @@ INITCALL0(STG_PREPARE, vars_init);
static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
+ { "dump_all_vars", smp_fetch_dump_all_vars, ARG3(0,STR,STR,STR),
smp_check_dump_all_vars, SMP_T_STR, SMP_USE_CONST },
{ "var", smp_fetch_var, ARG2(1,STR,STR), smp_check_var, SMP_T_ANY,
SMP_USE_CONST },
{ /* END */ },
}};
--
2.35.3
>From 6062ed5f15d9b33cf3e3b3e152378332f42cbc90 Mon Sep 17 00:00:00 2001
From: Hyeonggeun Oh <[email protected]>
Date: Fri, 26 Dec 2025 15:57:35 +0900
Subject: DOC: vars: document dump_all_vars() sample fetch
Add documentation for the dump_all_vars() sample fetch function in the
configuration manual. This function was introduced in the previous commit
to dump all variables in a given scope with optional prefix filtering.
The documentation includes:
- Function signature and return type
- Description of output format
- Explanation of scope and prefix arguments
- Usage examples for common scenarios
This completes the implementation of GitHub issue #1623.
---
doc/configuration.txt | 60 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index fd6a68ad78..a848c05cb9 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -23546,6 +23546,66 @@ var(<var-name>[,<default>]) : undefined
return it as a string. Empty strings are permitted. See section 2.8 about
variables for details.
+dump_all_vars([<scope>][,<prefix>][,<delimiter>]) : string
+ Returns a list of all variables in the specified scope, optionally filtered
+ by name prefix and with a customizable delimiter.
+
+ Output format: var1=value1<delim>var2=value2<delim>...
+
+ Value encoding by type:
+ - Strings: quoted and escaped (", \, \r, \n, \b, \0)
+ Example: txn.name="John \"Doe\""
+ - Binary: hex-encoded with 'x' prefix, unquoted
+ Example: txn.data=x48656c6c6f
+ - Integers: unquoted decimal
+ Example: txn.count=42
+ - Booleans: unquoted "true" or "false"
+ Example: txn.active=true
+ - Addresses: unquoted IP address string
+ Example: txn.client=192.168.1.1
+ - HTTP Methods: quoted string
+ Example: req.method="GET"
+
+ Arguments:
+ - <scope> (optional): sess, txn, req, res, or proc. If omitted, the scope
+ is determined by context (txn for streams, sess for sessions, proc
+ otherwise).
+
+ - <prefix> (optional): filters variables whose names start with the
+ specified prefix (after removing the scope prefix).
+ Performance note: When using prefix filtering, all variables in the scope
+ are still visited. This should not be used with configurations involving
+ thousands of variables.
+
+ - <delimiter> (optional): string to separate variables. Defaults to ", "
+ (comma-space). Can be customized to any string.
+
+ Return value:
+ - On success: string containing all matching variables
+ - On failure: empty (sample fetch fails) if output buffer is too small.
+ The function will not truncate output; it fails completely to avoid
+ partial data.
+
+ This is particularly useful for debugging, logging, or exporting variable
+ states.
+
+ Examples:
+ # Dump all transaction variables
+ http-request return string %[dump_all_vars(txn)]
+
+ # Dump only variables starting with "user"
+ http-request set-header X-User-Vars "%[dump_all_vars(txn,user)]"
+
+ # Dump all process variables
+ http-request return string %[dump_all_vars(proc)]
+
+ # Custom delimiter (semicolon)
+ http-request set-header X-Vars "%[dump_all_vars(txn,,; )]"
+
+ # Prefix filter with custom delimiter
+ http-request set-header X-Session "%[dump_all_vars(sess,user,|)]"
+
+
wait_end : boolean
This fetch either returns true when the inspection period is over, or does
not fetch. It is only used in ACLs, in conjunction with content analysis to
--
2.35.3