HRW4U DSL

HRW4U is a next-generation rule language for the existing ATS Header Rewrite Plugin plugin. It introduces clearer syntax, formal grammar, full condition nesting, semantic validation, and extensible condition/operator support.

Overview

HRW4U replaces the free-form text parsing of header_rewrite with a formally defined grammar using ANTLR. This makes HRW4U easier to parse, validate, and extend.

Rather than repeating header_rewrite documentation, please refer to:
  • Header Rewrite Plugin for feature behavior and semantics

  • This page focuses on syntax and behavior differences in HRW4U

Why HRW4U?

Over time, the header_rewrite format has become unwieldy and difficult to use. HRW4U aims to improve the following:

  • Structured grammar and parser

  • Better error diagnostics (line/col, filename, hints)

  • Proper nested condition support using if (…) and { … } blocks

  • Symbol tables for variable declarations and usage

  • Static validation of operand types and value ranges

  • Explicit VARS declarations with typed variables (bool, int8, int16)

  • Optional debug output to trace logic evaluation

Building

Currently, the HRW4U compiler is not built as part of the ATS build process. You need to build it separately using Python 3.10+ and pyenv environments. There’s a bootstrap.sh script in the tools/hrw4u directory that helps with the setup process.

Once set up, simply run:

make
make package

This will produce a PIP package in the dist directory. You can install it in a virtualenv or system-wide using:

pipx install dist/hrw4u-1.4.0-py3-none-any.whl

Using

Once installed, you will have a hrw4u command available. You can run it as follows to produce the help output:

hrw4u --help

Basic Usage

Compile a single file to stdout:

hrw4u some_file.hrw4u

Compile from stdin:

cat some_file.hrw4u | hrw4u

Compile multiple files to stdout (separated by # ---):

hrw4u file1.hrw4u file2.hrw4u file3.hrw4u

Bulk Compilation

For bulk compilation, use the input:output format to compile multiple files to their respective output files in a single command:

hrw4u file1.hrw4u:file1.conf file2.hrw4u:file2.conf file3.hrw4u:file3.conf

This is particularly useful for build systems or when processing many configuration files at once. All files are processed in a single invocation, improving performance for large batches of files.

Reverse Tool (u4wrh)

In addition to hrw4u, you also have the reverse tool, converting existing header_rewrite configurations to hrw4u. This tool is named u4wrh and supports the same usage patterns:

# Convert single file to stdout
u4wrh existing_config.conf

# Bulk conversion
u4wrh file1.conf:file1.hrw4u file2.conf:file2.hrw4u

For people using IDEs, the package also provides an LSP for this language, named hrw4u-lsp.

Syntax Differences

The basic structure consists of a section name defining the part of the transaction to run, followed by conditionals and operators. It uses if … {…} elif … {…} else {…} conditional syntax with && , || and ==. Conditionals support parenthesized grouping for complex expressions (if (…) {…}) and can be nested up to a maximum depth of 10 levels. Conditions and operators generally follow function() or object.style grammar. Operator lines are terminated with ; and whitespace is still insignificant. For instance:

VARS {
  Foo: bool;
  Bar: int8;
}

REMAP {
  if inbound.status == 403 {
    inbound.req.X-Fail = "1";
  } elif inbound.status == 404 {
    inbound.req.X-NotFound = "1";
  } else {
    no-op();
  }
}

READ_RESPONSE {
  if inbound.status > 399 {
    if inbound.status < 500 {
      # Client error - check specific cases
      if inbound.status == 404 {
        counter("errors.not_found");
      } elif inbound.status == 403 {
        counter("errors.forbidden");
      }
    } else {
      # Server error
      counter("errors.server");
    }
  }
}

There is no cond or set-header syntax — those are implied by context.

Header Presence

In header_rewrite, to test if a header is not present, you must test the value against an empty string (and then negate it to test if the header exists). For instance:

# header_rewrite - test if header is NOT present
cond %{HEADER:Strict-Transport-Security} =""

# header_rewrite - test if header IS present
cond %{HEADER:Strict-Transport-Security} ="" [NOT]

HRW4U uses a more conventional approach:

# HRW4U - test if header is NOT present
if !inbound.req.Strict-Transport-Security {
    ...
}

# HRW4U - test if header IS present
if inbound.req.Strict-Transport-Security {
    ...
}

Condition & Operator Mapping

Key Differences from header_rewrite

Feature

header_rewrite

HRW4U

Context

Context sensitive

Explicit, e.g. inbound and outbound

Syntax

Free-form

Structured if (…) { … }

Conditions

cond %{…}

Implicit in if (…)

Operators

Raw text (set-header)

Structured assignments or statements

Grouping

GROUP / GROUP:END

Use () inside if expressions

Else branches

else with indented ops

elif … { … } and else { … } with full blocks

Debugging

Manual with logs

Built-in debug tracing (–debug)

Quotes

Quoted strings optional

Quoted strings required / encouraged

Validation

Runtime

Static during parsing and symbol resolution

Note

The logical operator precedence is the same as in header_rewrite: which is left to right! This differs from most programming languages where && has higher precedence than ||. The implication being that you may have to use parentheses to get the desired precedence.

Conditions

Below is a partial mapping of header_rewrite condition symbols to their HRW4U equivalents:

Header Rewrite

HRW4U

Description

cond %{ACCESS:/path}

access(“/path”)

File exists at “/path” and is accessible by ATS

cond %{CACHE} =hit-fresh

cache() == “hit-fresh”

Cache lookup result status

cond %{CIDR:24,48} =ip

cidr(24,48) == “ip”

Match masked client IP address

cond %{CLIENT-HEADER:X} =foo

inbound.req.X == “foo”

Original client request header

cond %{CLIENT-URL:<C>} =bar

inbound.url.<C> == “bar”

URL component match, <C> is host, path etc.

cond %{CLIENT-URL:QUERY:<P>} =bar

inbound.url.query.<P> == “bar”

Extract specific query parameter P from URL

cond %{COOKIE:foo} =bar

{in,out}bound.cookie.foo == “bar”

Check a cookie value

cond %{FROM-URL:<C>} =bar

from.url.<C> == “bar”

Remap From URL component match, <C> is host etc.

cond %{FROM-URL:QUERY:<P>} =bar

from.url.query.<P> == “bar”

Extract specific query parameter P from remap From URL

cond %{HEADER:X} =fo

{in,out}bound.{req,resp}.X == “fo”

Context sensitive header conditions

cond %{ID:UNIQUE} =…

id.UNIQUE == “…”

(Unique/request/process) transaction identifier

cond %{INTERNAL-TRANSACTION}

internal()

Check if transaction is internally generated

cond %{INBOUND:CLIENT-CERT:<X>}

inbound.client-cert.<X>

Access the mTLS / client certificate details, on the inbound (client) connection

cond %{INBOUND:SERVER-CERT:<X>}

inbound.client-cert.<X>

Access the server (handshake) certificate details, on the inbound connection

cond %{IP:CLIENT} =”…”

inbound.ip == “…”

Client’s IP address. Same as inbound.REMOTE_ADDR

cond %{IP:INBOUND} =”…”

inbound.server == “…”

ATS’s IP address to which the client connected

cond %{IP:SERVER} =”…”

outbound.ip == “…”

Upstream (next-hop) server IP address

cond %{IP:OUTBOUND} =”…”

outbound.server == “…”

ATS’s outbound IP address, connecting upstream

cond %{LAST-CAPTURE:<#>} =”…”

capture.<#> == “…”

Last capture group from regex match (range: 0-9)

cond %{METHOD} =GET

inbound.method == “GET”

HTTP method match

cond %{NEXT-HOP:<C>} =”bar”

nexthop.<C> == “bar”

Next-hop destination, <C> is host, port, or strategy

cond %{NOW:<U>} =”…”

now.<U> == “…”

Current date/time in format, <U> selects time unit

cond %{OUTBOUND:CLIENT-CERT:<X>}

outbound.client-cert.<X>

Access the mTLS / client certificate details, on the outbound (upstream) connection

cond %{OUTbOUND:SERVER-CERT:<X>}

outbound.client-cert.<X>

Access the server (handshake) certificate details, on the outbound connection

cond %{RANDOM:500} >250

random(500) > 250

Random number between 0 and the specified range

cond %{SERVER-HEADER:X} =foo

outbound.req.X == “foo”

Server request header (sent to origin)

cond %{SERVER-URL:<C>} =bar

outbound.url.<C> == “bar”

Server request URL component (sent to origin)

cond %{SSN-TXN-COUNT} >10

ssn-txn-count() > 10

Number of transactions on server connection

cond %{TO-URL:<C>} =bar

to.url.<C> == “bar”

Remap To URL component match, <C> is host etc.

cond %{TO-URL:QUERY:<P>} =bar

to.url.query.<P> == “bar”

Extract specific query parameter P from remap To URL

cond %{TXN-COUNT} >10

txn-count() > 10

Number of transactions on client connection

cond %{URL:<C> =bar

{in,out}bound.url.<C> == “bar”

Context aware URL component match

cond %{GEO:<C>} =bar

geo.<C> == “bar”

IP to Geo mapping. <C> is country, asn, etc.

cond %{STATUS} =200

inbound.status ==200

Origin http status code

cond %{TCP-INFO}

tcp.info

TCP Info struct field values

cond %{HTTP-CNTL:<C>}

http.cntl.<C>

Check the state of the <C> HTTP control

cond %{INBOUND:<C>}

{in,out}bound.conn.<c>

inbound (client, user agent) connection to ATS

Note

Header and URL prefix summary:

  • inbound.req.<header>CLIENT-HEADER - Headers from the client request

  • outbound.req.<header>SERVER-HEADER - Headers in the request sent to origin

  • inbound.url.<part>CLIENT-URL - URL from the original client request

  • outbound.url.<part>SERVER-URL - URL in the request sent to origin (after remapping)

  • nexthop.<field>NEXT-HOP - Network destination info (host, port, strategy)

The distinction between outbound.url and nexthop is important:

  • outbound.url is the HTTP request URL (what’s in the request line/Host header)

  • nexthop is the network destination (where ATS connects, may be a parent proxy)

The conditions operating on headers and URLs are also available as operators. E.g.:

if inbound.req.X == "foo" {
  inbound.req.X = "bar";
}

In general, where it makes sense for the condition to be used as an operator, it is available as an operator. The rule of thumb is the conditional is an operator if the value is mutable.

Note

Each parenthesis group in a conditional will produce a GROUP. Thus, the original header_rewrite config will be more readable with if ... than if (…).

Operators

Operators in header_rewrite map to HRW4U as a mix of assignments and function calls. The preference is the assignment style when appropriate.

Header Rewrite

HRW4U

Description

add-header X-bar foo

inbound.{req,resp}.x-Bar += “bar”

Add the header to (possibly) an existing header

counter my_stat

counter(“my_stat”)

Increment internal counter

rm-client-header X-Foo

inbound.req.X-Foo = “”

Remove a client request header

rm-cookie foo

{in,out}bound.cookie.foo = “”

Remove the cookie named foo

rm-destination <C>

inbound.url.<C> = “”

Remove an URL component, C is path, query etc.

rm-header X-Foo

{in,out}bound.req.X-Foo = “”

Context sensitive header removal

rm-destination QUERY …

remove_query(“foo,bar”)

Remove specified query keys

rm-destination QUERY … [I]

keep_query(“foo,bar”)

Keep only specified query keys

run-plugin foo.so “args”

run-plugin(“foo.so”, “arg1”, …)

Run an external remap plugin

set-body “foo”

inbound.resp.body = “foo”

Set the response body

set-body-from “https://…”

set-body-from(”https://…”)

Set the response body from a URL

set-config <name> 12

set-config(“name”, 17)

Set a configuration variable to a value

set-conn-dscp 8

inbound.conn.dscp = 8

Set the DSCP value for the connection

set-conn-mark 17

inbound.conn.mark = 17

Set the MARK value for the connection

set-cookie foo bar

{in,out}bound.cookie.foo = “bar”

Set a request/response cookie named foo

set-destination <C> bar

{in,out}bound.url.<C> = “bar”

Set a URL component, <C> is path, query etc.

set-header X-Bar foo

inbound.{req,resp}.X-Bar = “foo”

Assign a client request/origin response header

set-plugin-cntl <C> <T>

set-plugin-cntl(<C>) = <T>

Set the plugin control <C> to <T>, see <C>

set-redirect <Code> <URL>

set-redirect(302, “https://…”)

Set a redirect response

set-status 404

http.status = 404

Set the response status code

set-status-reason “No”

http.status.reason = “no”

Set the response status reason

set-http-cntl

http.cntl.<C> = bool

Turn on/off <C> controllers

Adding Headers with the += Operator

HRW4U provides a special += operator for adding headers:

REMAP {
    # Using += to add a header (maps to add-header)
    inbound.req.X-Custom-Header += "new-value";
}

The += operator only works with the following pre-defined symbols:

  • inbound.req.<header> - Client request headers (maps to CLIENT-HEADER)

  • inbound.resp.<header> - Origin response headers

  • outbound.req.<header> - Server request headers (maps to SERVER-HEADER)

  • outbound.resp.<header> - Outbound response headers (context-restricted)

Note

The += operator differs from = in that = will replace/set the header value (mapping to set-header), while += will add a new instance of the header (mapping to add-header). This is important for headers that can have multiple values, such as Set-Cookie or custom headers.

In addition to those operators above, HRW4U supports the following special operators without arguments:

Header Rewrite

HRW4U Syntax

Description

no-op

no-op();

Explicit no-op statement

no-op [L]

break;

Exit current section early (last rule)

set-debug

set-debug()

Enables ATS txn debug

skip-remap

skip-remap()

Skip remap processing (open proxy)

String concatenations

You can concatenate values using strings, condition values, and variable expansions on the same line in operators. For instance, outbound.req.CustomHeader = "Hello from {inbound.ip}:{inbound.port}". As a result, the set-redirect’s [QSA] flag would be implemented as set-redirect(302, "https://...?{inbound.url.query}"). Note the presence of the ? – the url.query doesn’t include it.

Semantics

Sections

All HRW4U sections start with a label such as REMAP, READ_RESPONSE, etc. These map directly to header_rewrite hook conditions:

Header Rewrite Hook

HRW4U Section Name

Description

TXN_START_HOOK

TXN_START

Start of transaction

READ_REQUEST_PRE_REMAP_HOOK

PRE_REMAP

Before remap processing

REMAP_PSEUDO_HOOK

REMAP

Default remap hook

READ_REQUEST_HDR_HOOK

READ_REQUEST

After reading request from client

SEND_REQUEST_HDR_HOOK

SEND_REQUEST

Before contacting origin

READ_RESPONSE_HDR_HOOK

READ_RESPONSE

After receiving response from origin

SEND_RESPONSE_HDR_HOOK

SEND_RESPONSE

Before sending response to client

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.

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.

Procedures

Procedures allow you to define reusable blocks of rules that can be called from multiple sections or files. A procedure is a named, parameterized block of conditions and operators that expands inline at the call site.

Defining Procedures

Procedures are declared with the procedure keyword and must use a qualified name with the :: namespace separator:

procedure local::add-debug-header($tag) {
    inbound.req.X-Debug = "$tag";
}

The namespace prefix (local:: in this example) groups related procedures. Parameters are prefixed with $ and substituted at the call site.

Procedures may be defined in the same file as the sections that use them, or in separate .hrw4u files loaded with the use directive. Procedure declarations must appear before any section blocks.

Using Procedures

Call a procedure from any section by its qualified name:

procedure local::set-cache-headers($ttl) {
    outbound.resp.Cache-Control = "max-age=$ttl";
    outbound.resp.X-Cache-TTL = "$ttl";
}

READ_RESPONSE {
    local::set-cache-headers("3600");
}

SEND_RESPONSE {
    local::set-cache-headers("0");
}

The procedure body is expanded inline — each section gets its own copy with the correct hook context.

Procedure Files and use

For larger projects, procedures can be organized into separate files and loaded with the use directive. The use spec maps to a file path: use Acme::Common loads Acme/Common.hrw4u from the procedures search path.

The --procedures-path flag specifies where to search:

hrw4u --procedures-path /etc/trafficserver/procedures rules.hrw4u

Given this file structure:

/etc/trafficserver/procedures/
└── Acme/
    └── Common.hrw4u

Where Acme/Common.hrw4u contains:

procedure Acme::add-security-headers() {
    outbound.resp.X-Frame-Options = "DENY";
    outbound.resp.X-Content-Type-Options = "nosniff";
}

Then in rules.hrw4u:

use Acme::Common

READ_RESPONSE {
    Acme::add-security-headers();
}

The use directive enforces namespace consistency: all procedures in a file loaded via use Acme::Common must use the Acme:: namespace prefix.

Parameters and Defaults

Procedures support positional parameters with optional defaults:

procedure local::tag-request($env, $version = "v1") {
    inbound.req.X-Env = "$env";
    inbound.req.X-Version = "$version";
}

REMAP {
    local::tag-request("prod");
    # $version defaults to "v1"
}

Groups

header_rewrite uses GROUP and GROUP:END, whereas HRW4U uses (…) expressions:

# header_rewrite
cond %{GROUP}
  cond A [AND]
  cond B
cond %{GROUP:END}

# HRW4U
if (A && B) {
  ...
}

Control Flow

HRW4U conditionals use if, elif, and else blocks. Each branch takes a condition expression followed by a { ... } body of statements:

if condition {
  statement;
} elif other-condition {
  statement;
} else {
  statement;
}

elif and else are optional and can be chained. Branches can be nested to arbitrary depth:

REMAP {
  if inbound.status > 399 {
    if inbound.status < 500 {
      if inbound.status == 404 {
        inbound.resp.X-Error = "not-found";
      } elif inbound.status == 403 {
        inbound.resp.X-Error = "forbidden";
      }
    } else {
      inbound.resp.X-Error = "server-error";
    }
  }
}

The break; statement exits the current section immediately, skipping any remaining statements and branches:

REMAP {
  if inbound.req.X-Internal != "1" {
    break;
  }
  # Only reached for internal requests
  inbound.req.X-Debug = "on";
}

Condition operators

HRW4U supports the following condition operators, which are used in if expressions:

Operator

HRW4U Syntax

Description

==

foo == “bar”

String or numeric equality

!=

foo != “bar”

String or numeric inequality

>

foo > 100

Numeric greater than

<

foo < 100

Numeric less than

~

foo ~ /pattern/

Regular expression match

!~

foo !~ /pattern/

Regular expression non-match

in […]

foo in [“a”, “b”]

Membership in a list of values

!in […]

foo !in [“a”, “b”]

Negated membership in a list of values

Modifiers

HRW4U supports the following modifiers for the string conditions:

Modifier

Description

EXT

Match extension after last dot (e.g. in a path)

MID

Match substring

SUF

Match the end of a string

PRE

Match the beginning of a string

NOCASE

Case insensitive match

These can be used with both sets and equality checks, using the with keyword:

if inbound.req.X == "foo" with MID,NOCASE {
  ...
}

if inbound.url.path in ["mp3", "mp4"] with EXT,NOCASE {
  ...
}

Running and Debugging

To run HRW4U, just install and run the hrw4u compiler:

hrw4u /path/to/rules.hrw4u

Run with –debug all to trace:

  • Lexer, parser, visitor behavior

  • Condition evaluations

  • State and output emission

Sandbox Policy Enforcement

Organizations deploying HRW4U across teams can restrict which language features are permitted using a sandbox configuration file. Features can be denied (compilation fails with an error) or warned (compilation succeeds but a warning is emitted). Both modes support the same feature categories.

Pass the sandbox file with --sandbox:

hrw4u --sandbox /etc/trafficserver/hrw4u-sandbox.yaml rules.hrw4u

The sandbox file is YAML with a single top-level sandbox key. A JSON Schema for editor validation and autocomplete is provided at tools/hrw4u/schema/sandbox.schema.json.

sandbox:
  message: |      # optional: shown once after all errors/warnings
    ...
  deny:
    sections:    [ ... ]   # section names, e.g. TXN_START
    functions:   [ ... ]   # function names, e.g. run-plugin
    conditions:  [ ... ]   # condition keys, e.g. geo.
    operators:   [ ... ]   # operator keys, e.g. inbound.conn.dscp
    language:    [ ... ]   # break, variables, in, else, elif
  warn:
    functions:   [ ... ]   # same categories as deny
    conditions:  [ ... ]

All lists are optional. If --sandbox is omitted, all features are permitted. When a sandbox file is provided it must contain a top-level sandbox: key; an empty policy can be expressed as sandbox: {}. A feature may not appear in both deny and warn.

Denied Sections

The sections list accepts any of the HRW4U section names listed in the Sections table, plus VARS to deny the variable declaration block. A denied section causes the entire block to be rejected; the body is not validated.

Functions

The functions list accepts any of the statement-function names used in HRW4U source. The complete set of deniable functions is:

Function

Description

add-header

Add a header (+= operator equivalent)

counter

Increment an ATS statistics counter

keep_query

Keep only specified query parameters

no-op

Explicit no-op statement

remove_query

Remove specified query parameters

run-plugin

Invoke an external remap plugin

set-body-from

Set response body from a URL

set-config

Override an ATS configuration variable

set-debug

Enable per-transaction ATS debug logging

set-plugin-cntl

Set a plugin control flag

set-redirect

Issue an HTTP redirect response

skip-remap

Skip remap processing (open proxy)

Conditions and Operators

The conditions and operators lists use the same dot-notation keys shown in the Conditions and Operators tables above (e.g. inbound.req., geo., outbound.conn.).

Entries ending with . use prefix matchinggeo. denies all geo.* lookups (geo.city, geo.ASN, etc.). Entries without a trailing . are matched exactly, which allows fine-grained control over sub-values:

sandbox:
  deny:
    operators:
      - http.cntl.SKIP_REMAP        # deny just this sub-value
    conditions:
      - geo.ASN                      # deny ASN lookups specifically
  warn:
    operators:
      - http.cntl.                   # warn on all other http.cntl.* usage
    conditions:
      - geo.                         # warn on remaining geo.* lookups

Prefix entries and exact entries can be combined across deny and warn to create graduated policies — deny the dangerous sub-values while warning on the rest.

Language Constructs

The language list accepts a fixed set of constructs:

Construct

What it controls

break

The break; statement (early section exit)

variables

The entire VARS section and all variable usage

else

The else { ... } branch of conditionals

elif

The elif ... { ... } branch of conditionals

in

The in [...] and !in [...] set membership operators

Output

When a denied feature is used the error output looks like:

rules.hrw4u:3:4: error: 'set-debug' is denied by sandbox policy (function)

This feature is restricted by CDN-SRE policy.
Contact cdn-sre@example.com for exceptions.

When a warned feature is used the compiler emits a warning but succeeds:

rules.hrw4u:5:4: warning: 'set-config' is warned by sandbox policy (function)

This feature is restricted by CDN-SRE policy.
Contact cdn-sre@example.com for exceptions.

The sandbox message is shown once at the end of the output, regardless of how many denial errors or warnings were found. Warnings alone do not cause a non-zero exit code.

Example Configuration

A typical policy for a CDN team where remap plugin authors should not have access to low-level or dangerous features, with transitional warnings for features being phased out:

sandbox:
  message: |
    This feature is not permitted by CDN-SRE policy.
    To request an exception, file a ticket at https://help.example.com/cdn

  deny:
    # Disallow hooks that run outside the normal remap context
    sections:
      - TXN_START
      - TXN_CLOSE
      - PRE_REMAP

    # Disallow functions that affect ATS internals or load arbitrary code
    functions:
      - run-plugin
      - skip-remap

    # Deny a specific dangerous sub-value
    operators:
      - http.cntl.SKIP_REMAP

  warn:
    # These functions will be denied in a future release
    functions:
      - set-debug
      - set-config

    # Warn on all remaining http.cntl usage
    operators:
      - http.cntl.

Examples

The examples section in Header Rewrite are translated below into HRW4U with their original descriptions. These are also part of the testing suite.

Remove Origin Authentication Headers

The following ruleset removes any authentication headers from the origin response before caching it or returning it to the client. This is accomplished by setting the hook context and then removing the cookie and basic authentication headers.:

READ_RESPONSE {
    outbound.resp.Set-Cookie = "";
    outbound.resp.WWW-Authenticate = "";
}

Count Teapots

Maintains a counter statistic, which is incremented every time an origin server has decided to be funny by returning HTTP 418:

SEND_RESPONSE {
    if outbound.status == 418 {
        counter("plugin.header_rewrite.teapots");
    }
}

Normalize Statuses

For client-facing purposes only (because we set the hook context to just prior to delivering the response back to the client, but after all processing and possible cache updates have occurred), replaces all 4xx HTTP status codes from the origin server with 404:

SEND_RESPONSE {
    if inbound.status > 399 && inbound.status < 500 {
        inbound.status = 404;
    }
}

Remove Cache Control to Origins

Removes cache control related headers from requests before sending them to an origin server:

SEND_REQUEST {
   outbound.req.Cache-Control = "";
   outbound.req.Pragma = "";
}

Enable Debugging Per-Request

Turns on Traffic Server debugging statements for a transaction, but only when a special header is present in the client request:

READ_REQUEST {
   if inbound.req.X-Debug == "supersekret" {
       set-debug();
   }
}

Remove Internal Headers

Removes special internal/development headers from origin responses, unless the client request included a special debug header:

READ_RESPONSE {
   if inbound.req.X-Debug != "keep" {
       outbound.resp.X-Debug-Foo = "";
       outbound.resp.X-Debug-Bar = "";
   }
}

Return Original Method in Response Header

This rule copies the original HTTP method that was used by the client into a custom response header:

SEND_RESPONSE {
    inbound.resp.X-Original-Method = "{inbound.method}";
}

Useless Example From Purpose

Even that useless example from Purpose in the beginning of this document is possible to accomplish:

READ_RESPONSE {
    if inbound.url.port == 8090 && inbound.method == "HEAD" &&
      inbound.req.Accept-Language !~ /es-py/ && outbound.status == 304 ||
      random(500) > 200 {
    outbound.status = 403;
    }
}

Add Cache Control Headers Based on Origin Path

This rule adds cache control headers to CDN responses based on matching the origin path. One provides a max age and the other provides a “no-cache” statement to two different file paths.

READ_RESPONSE {
   if inbound.url.path ~ /examplepath1/ {
       outbound.resp.Cache-Control = "max-age=3600";
       break;
   }
}

READ_RESPONSE {
   if inbound.url.path ~ /examplepath2\/examplepath3\/.*/ {
       outbound.resp.Cache-Control = "no-cache";
       break;
   }
}

Redirect when the Origin Server Times Out

This rule sends a 302 redirect to the client with the requested URI’s Path and Query string when the Origin server times out or the connection is refused:

READ_RESPONSE {
    if outbound.status in [502, 504] {
        set-redirect(302, "http://different_origin.example.com/{inbound.url.path}?{inbound.url.query}");
    }
}

Flag Unrecognized ASNs

This rule flags requests whose origin ASN is not in a known allowlist, using the negated membership operator !in:

REMAP {
    if geo.ASN !in ["64496", "64511"] {
        inbound.req.X-Known-ASN = "false";
    }
}

Restrict to Internal Networks

This rule rejects requests that do not originate from known internal IP ranges, using !in with an IP range:

REMAP {
    if inbound.ip !in {192.168.0.0/16, 10.0.0.0/8} {
        http.status = 403;
    }
}

Check for existence of a header

This rule will modify the Cache-Control header, but only if it is not already set to some value, and the status code is a 2xx:

READ_RESPONSE {
   if outbound.resp.Cache-Control == "" && outbound.status > 199 && outbound.status < 300 {
       outbound.resp.Cache-Control = "max-age=600, public";
    }
}

Add HSTS

Add the HTTP Strict Transport Security (HSTS) header if it does not exist and the inbound connection is TLS:

READ_RESPONSE {
   if outbound.resp.Strict-Transport-Security == "" && inbound.conn.TLS != "" {
       outbound.resp.Strict-Transport-Security  = "max-age=63072000; includeSubDomains; preload";
   }
}

This is mostly used by being attached to a remap rule that maps to a host known to support TLS. If the parallel OUTBOUND supported is added then this could be done by checking for inbound TLS both outbound TLS in the SEND_REQUEST_HDR_HOOK. However this technique may be used for a non-TLS upstream if the goal is to require the user agent to connect to Traffic Server over TLS.

Close Connections for draining

When a healthcheck file is missing (in this example, /path/to/the/healthcheck/file.txt), add a Connection: close header to have clients drop their connection, allowing the server to drain. Although the Connection header is only available on HTTP/1.1 in terms of protocols, this also works for HTTP/2 connections because the header triggers HTTP/2 graceful shutdown. This should be a global configuration.:

SEND_RESPONSE {
   if !access("/path/to/the/healthcheck/file.txt") {
       inbound.resp.Connection = "close";
   }
}

Use Internal header to pass data

In Traffic Server, a header that begins with @ does not leave Traffic Server. Thus, you can use this to pass data to different Traffic Server systems. For instance, a series of remap rules could each be tagged with a consistent name to make finding logs easier.:

REMAP {
    inbound.req.@PropertyName = "someproperty";
}

(Then in logging.yaml, log %<{@PropertyName}cqh>)

Note

Remember using the variables in HRW4u can often be more efficient than using these internal headers.

Remove Client Query Parameters

The following ruleset removes any query parameters set by the client.:

REMAP {
    inbound.url.query = "";
}

Remove only a few select query parameters:

REMAP {
    remove_query("foo,bar");
}

Keep only a few select query parameters – removing the rest:

REMAP {
    keep_query("foo,bar");
}

Mimic X-Debug Plugin’s X-Cache Header

This rule can mimic X-Debug plugin’s X-Cache header by accumulating the CACHE condition results to a header.:

SEND_RESPONSE {
    if inbound.resp.All-Cache != "" {
        inbound.resp.All-Cache = "{inbound.resp.All-Cache}, {cache()}";
    }
    if inbound.resp.All-Cache == "" {
        inbound.resp.All-Cache = "{cache()}";
    }
}

And finally, a much more efficient solution, using the else clause.:

SEND_RESPONSE {
    if inbound.resp.All-Cache == "" {
        inbound.resp.All-Cache = "{cache()}";
    } else {
        inbound.resp.All-Cache = "{inbound.resp.All-Cache}, {cache()}";
    }
}

Add Identifier from Server with Data

This rule adds a unique identifier from the server if the data is fresh from the cache or if the identifier has not been generated yet. This will inform the client where the requested data was served from.:

SEND_RESPONSE {
    if inbound.resp.ATS-SRVR-UUID == "" || cache() == "hit-fresh" {
        inbound.resp.ATS-SRVR-UUID = "{id(UNIQUE)}";
    }
}

Apply rate limiting for some select requests

This rule will conditionally, based on the client request headers, apply rate limiting to the request.:

REMAP {
    if inbound.req.Some-Special-Header == "yes" {
        run-plugin("rate_limit.so", "--limit=300", "--error=429");
    }
}

Route Based on Query Parameter Value

This rule extracts a specific query parameter value and uses it to make routing decisions or set custom headers. The query.<param_name> syntax allows extracting individual query parameter values:

REMAP {
    if inbound.url.query.version == "v2" {
        inbound.req.X-API-Version = "v2";
    }
}

SEND_RESPONSE {
    inbound.resp.X-Debug-Param = "{inbound.url.query.debug}";
}

Note

Query parameter names are case-sensitive and matched as-is without URL decoding. For example, inbound.url.query.my%20param matches the literal parameter name my%20param, not my param.

References