Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions doc/admin-guide/configuration/hrw4u.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,29 @@ TXN_CLOSE_HOOK TXN_CLOSE End of transaction
A special section `VARS` is used to declare variables. There is no equivalent in
`header_rewrite`, where you managed the variables manually.

.. note::
The section name is always required in HRW4U, there are no implicit or default hooks. There
can be several if/else block per section block.
Variables and State Slots
^^^^^^^^^^^^^^^^^^^^^^^^^^

Each variable type has a limited number of slots available:

- ``bool`` - 16 slots (0-15)
- ``int8`` - 4 slots (0-3)
- ``int16`` - 1 slot (0)

By default, slots are assigned automatically in declaration order. You can explicitly assign
a slot number using the ``@`` syntax::

VARS {
priority: bool @7; # Explicitly use slot 7
active: bool; # Auto-assigned to slot 0
config: bool @12; # Explicitly use slot 12
counter: int8 @2; # Explicitly use int8 slot 2
}

Explicit slot assignment is useful when you need predictable slot numbers across configurations
or when integrating with existing header_rewrite rules that reference specific slot numbers. In
addition, a remap configuration can use ``@PPARAM`` to set one of these slot variables explicitly
as part of the configuration.

Groups
------
Expand Down
144 changes: 135 additions & 9 deletions doc/admin-guide/plugins/header_rewrite.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,19 @@ like the following::
Which converts any 4xx HTTP status code from the origin server to a 404. A
response from the origin with a status of 200 would be unaffected by this rule.

Advanced Conditionals
---------------------

The header_rewrite plugin supports advanced conditional logic that allows
for more sophisticated rule construction, including branching logic, nested
conditionals, and complex boolean expressions.

else and elif Clauses
~~~~~~~~~~~~~~~~~~~~~

An optional ``else`` clause may be specified, which will be executed if the
conditions are not met. The ``else`` clause is specified by starting a new line
with the word ``else``. The following example illustrates this::
conditions are not met. The ``else`` clause is specified by starting a new
line with the word ``else``. The following example illustrates this::

cond %{STATUS} >399 [AND]
cond %{STATUS} <500
Expand All @@ -164,10 +174,12 @@ with the word ``else``. The following example illustrates this::
set-status 503

The ``else`` clause is not a condition, and does not take any flags, it is
of course optional, but when specified must be followed by at least one operator.
of course optional, but when specified must be followed by at least one
operator.

You can also do an ``elif`` (else if) clause, which is specified by starting a new line
with the word ``elif``. The following example illustrates this::
You can also do an ``elif`` (else if) clause, which is specified by
starting a new line with the word ``elif``. The following example
illustrates this::

cond %{STATUS} >399 [AND]
cond %{STATUS} <500
Expand All @@ -178,14 +190,105 @@ with the word ``elif``. The following example illustrates this::
else
set-status 503

Keep in mind that nesting the ``else`` and ``elif`` clauses is not allowed, but any
number of ``elif`` clauses can be specified. We can consider these clauses are more
powerful and flexible ``switch`` statement. In an ``if-elif-else`` rule, only one
will evaluate its operators.
Any number of ``elif`` clauses can be specified. We can consider these
clauses are more powerful and flexible ``switch`` statement. In an
``if-elif-else`` rule, only one will evaluate its operators.

Note that while ``else`` and ``elif`` themselves cannot be directly nested,
you can use ``if``/``endif`` blocks within ``else`` or ``elif`` operator
sections to achieve nested conditional logic (see `Nested Conditionals with
if/endif`_).

Similarly, each ``else`` and ``elif`` have the same implied
:ref:`Hook Condition <hook_conditions>` as the initial condition.

Nested Conditionals with if/endif
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For more complex logic requiring nested conditionals, the ``if`` and
``endif`` pseudo-operators can be used. While ``else`` and ``elif``
themselves cannot be directly nested, you can use ``if``/``endif`` blocks
within any operator section (including inside ``else`` or ``elif`` blocks)
to achieve arbitrary nesting depth.

The ``if`` operator starts a new conditional block, and ``endif`` closes
it. Each ``if`` must have a matching ``endif``. Here's an example::

cond %{READ_RESPONSE_HDR_HOOK} [AND]
cond %{STATUS} >399
if
cond %{HEADER:X-Custom-Error} ="true"
set-header X-Error-Handled "yes"
else
set-header X-Error-Handled "no"
endif
set-status 500

In this example, the nested ``if``/``endif`` block is only evaluated when
the status is greater than 399. The nested block itself can contain
``else`` or ``elif`` clauses, and you can nest multiple levels deep::

cond %{READ_RESPONSE_HDR_HOOK}
if
cond %{STATUS} =404
if
cond %{CLIENT-HEADER:User-Agent} /mobile/
set-header X-Error-Type "mobile-404"
else
set-header X-Error-Type "desktop-404"
endif
elif
cond %{STATUS} =500
set-header X-Error-Type "server-error"
endif

GROUP Conditions in Advanced Conditionals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The `GROUP`_ condition can be combined with advanced conditionals to
create very sophisticated boolean expressions. ``GROUP`` conditions act as
parentheses in your conditional logic, allowing you to mix AND, OR, and NOT
operators in complex ways.

Here's an example combining ``GROUP`` with ``if``/``endif``::

cond %{READ_RESPONSE_HDR_HOOK} [AND]
cond %{STATUS} >399
if
cond %{GROUP} [OR]
cond %{CLIENT-HEADER:X-Retry} ="true" [AND]
cond %{METHOD} =GET
cond %{GROUP:END}
cond %{CLIENT-HEADER:X-Force-Cache} ="" [NOT]
set-header X-Can-Retry "yes"
else
set-header X-Can-Retry "no"
endif
set-status 500

This creates the logic: if error status, then set retry header when
``((X-Retry=true AND METHOD=GET) OR X-Force-Cache header exists)``.
The GROUP is necessary here to properly combine the two conditions with OR.

You can also use ``GROUP`` with ``else`` and ``elif`` inside nested conditionals::

cond %{SEND_RESPONSE_HDR_HOOK} [AND]
cond %{STATUS} >399
if
cond %{GROUP} [OR]
cond %{HEADER:X-Custom} ="retry" [AND]
cond %{METHOD} =POST
cond %{GROUP:END}
cond %{HEADER:Content-Type} /json/
set-header X-Error-Handler "json-retry"
elif
cond %{METHOD} =GET
set-header X-Error-Handler "get-error"
else
set-header X-Error-Handler "standard"
endif
set-status 500

State variables
---------------

Expand Down Expand Up @@ -923,6 +1026,29 @@ no facility to increment by other amounts, nor is it possible to initialize the
counter with any value other than ``0``. Additionally, the counter will reset
whenever |TS| is restarted.

if
~~
::

if
<conditions>
<operators>
endif

This is a pseudo-operator that enables nested conditional blocks within
the operator section of a rule. While ``else`` and ``elif`` themselves
cannot be directly nested, you can use ``if``/``endif`` blocks within any
operator section (including inside ``else`` or ``elif`` blocks) to create
arbitrary nesting depth for complex conditional logic.

The ``if`` operator must be preceded by conditions and followed by at
least one condition or operator. Each ``if`` must have a matching
``endif`` to close the block. Within an ``if``/``endif`` block, you can
use regular conditions, operators, and even ``else`` and ``elif`` clauses.

For detailed usage and examples, see `Nested Conditionals with if/endif`_
in the `Advanced Conditionals`_ section.

no-op
~~~~~
::
Expand Down
1 change: 1 addition & 0 deletions plugins/header_rewrite/factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ operator_factory(const std::string &op)
} else if (op == "set-next-hop-strategy") {
o = new OperatorSetNextHopStrategy();
} else {
// Note that we don't support the OperatorIf() pseudo-operator here!
TSError("[%s] Unknown operator: %s", PLUGIN_NAME, op.c_str());
return nullptr;
}
Expand Down
Loading