Examples¶
TxnBox is a large, complex plugin and it can be challenging to get started. This section provides a number of example uses, all of which are based on actual production use of TxnBox.
Default Accept-Encoding¶
- Goal
Force all proxy requests to have a value for the "Accept-Encoding" field. If not already set, it should be set to "identity".
- when: proxy-req
do:
- proxy-req-field<Accept-Encoding>: [ proxy-req-field<Accept-Encoding> , { else: "identity" } ]
This acts on the proxy request hook. The "Accept-Encoding" field is extracted and then modified
using the else
which modifies the feature only if the feature is null or the empty string.
Traffic Ramping¶
For the purposes of this example, there is presumed to exist a remap rule for a specific externally visible host, "base.ex". A new version is being staged on the host "stage.video.ex". The goal is to redirect a fixed percentage of traffic from the existing host to the staging host, in way that is easy to change. In addition it should be easy to have multiple ramping values for different URL paths. The paths for the two hosts are identical, only the host for the request needs to be changed.
The simplest way to do this would be
- with: pre-remap-path
select:
- any-of:
- prefix: "v1/video/search/"
- prefix: "v1/video/alias/"
do:
- with: random
select:
- lt: 30
do:
- ua-req-host: "stage.video.ex"
- prefix: "v1/video/channels/"
do:
- with: random
select:
- lt: 10
do:
- ua-req-host: "stage.video.ex"
This has two buckets, the first at 30% and the second at 10%. random
is used to generate
random numbers in the range 0..99 which means the extracted value is lt: 30
roughly 30 times
out of every hundred, or 30% of the time. The buckets are selected by first checking the pre-remap
path (so that it is not affected by other plugins which may run earlier in remapping). Two paths
are in the 30% bucket and one in the 10% bucket. Adding additional paths is easy, as is changing
the percent diversion. Other buckets can be added with little effort.
This can be done in another way by generating the random value once and checking it multiple times. Given the no backtrack rule, this is challenging to do by checking the percentage first. Instead the use of tuples makes it possible to check both the value and the path together.
- with: [ random, pre-remap-path ]
select:
- as-tuple:
- lt: 30
- any-of:
- prefix: "v1/video/search/"
- prefix: "v1/video/alias/"
do:
- ua-req-host: "stage.video.ex"
- as-tuple:
- lt: 10
- prefix: "v1/video/channels/"
do:
- ua-req-host: "stage.video.ex"
The with
is provided with a tuple of size 2, the random value and the pre-remap path. Each
comparison uses as-tuple
to perform parallel comparisons on the tuple. The first comparison
is applied to the first tuple element, the value, and the second comparison to the second value, the
path. Because there is no nested with
there is no need to backtrack.
It might be reasonable to split every path in to a different bucket to make adjusting the percentage easier. In that case the previous example could be changed to look like
- with: [ random, pre-remap-path ]
select:
- any-of:
- as-tuple:
- lt: 30
- prefix: "v1/video/search/"
- as-tuple:
- lt: 30
- prefix: "v1/video/alias/"
- as-tuple:
- lt: 10
- prefix: "v1/video/channels/"
do:
- ua-req-host: "stage.video.ex"
This style presumes the bucket action is identical for all buckets. If not, the previous style would
be better. Note the do
is attached to the any-of
so that if any of the nested comparisons
succeed the action is performed.
Static File Serving¶
TxnBox enables serving defined content. This can be done with the upstream-rsp-body
directive
to replace content from the upstream with configuration specified content. This is enhanced with
"text blocks" which allow obtaining content from external files.
- Goal
Provide a default security.txt file if an upstream doesn't provide one.
Example configuration
- when: upstream-rsp
do:
- with: [ upstream-rsp-status , proxy-req-path ]
select:
- as-tuple:
- eq: 404
- match: "security.txt"
do:
- debug: "Resetting upstream response"
- upstream-rsp-status: [ 200 , "OK" ]
- upstream-rsp-body: *secure-text
- upstream-rsp-field<Cache-Control>: "max-age=3600"
This checks on the upstream response. If the status is 404 (not found) and the path is exactly "security.txt" then change the response to a 200 and provide a hard wired default for the content. The text is retrieved via a YAML reference to an anchor.
secure_text: &secure-text "# Yoyodyne uses SmackerTwo for responsible disclosure.\n\
# To report abusive behavior please visit http://yoyodyne.ex\n\
Contact: mailto:security@yoyodyne.ex\n\
"
Unfortunately this is insufficient because in some situations there will be no proxy request and therefore no upstream response. Because of limitations in the plugin API this can't be handled directly and requires an additional bit of configuration for that case. This is the reason for using the anchor and reference in the previous configuration, to make sure the exact same text is used in both cases. Note this is done during YAML parsing, not at runtime, and is identical to using literal strings in both cases.
- when: proxy-rsp
do:
# If this is checking it could be because there was an early failure. In that case the proxy
# request may never have been created and proxy-req-path will be NULL. This syntax tries
# proxy-req-path and if it is NULL, tries ua-req-path instead which will always be something.
- debug: "id {ua-req-field<UUID>} proxy-rsp-status {proxy-rsp-status} proxy-req-path {proxy-req-path}"
- with: [ proxy-rsp-status , [ proxy-req-path , { else: ua-req-path } ] ]
select:
- as-tuple:
- eq: 404
- match: "security.txt"
do:
- debug: "Resetting proxy response"
- proxy-rsp-status: [ 200 , "OK" ]
- proxy-rsp-body: [ *secure-text , "text/plain" ]
Note only one of these will trigger on a specific transaction, because if the upstream check
activates, the status will not be 404 when it is checked here. This is also a bit more complex
because in some situations for which this is checking the proxy request will not have been created,
e.g. if there is a failure to remap the request. As a result the path uses a modifier so that if
proxy-req-path
isn't available due to the proxy request not having been created, it falls back
to ua-req-path
to get the path the user agent sent. It would also be reasonable to only use
ua-req-path
in both cases so that only if the user agent specifically requested "security.txt"
would the default be used.
- Goal
Provide a default JSON web token.
The utility here is to bootstrap into an established JWT infrastructure. On a first request into a CDN the default token is provided to enable access to the resources needed to get a personalized token. For security reasons the tokens expire on a regular basis which includes the default token. It would be too expensive to restart or reload Traffic Server on every expiration. Presuming an infrastructure that pushes default tokens to the file "/var/www/jwt/default-token.jwt", a text block can be defined to load that file and check it for changes every 12 hours. If the file is missing, a special marker "N/A" that signals this problem to the upstream.
- text-block-define:
name: "default-jwt"
path: "/var/www/jwt/default-token.jwt"
text: "N/A"
duration: "12h"
To use this, the proxy request is checked for the "Author-i-tay" field. If set it is passed through on the presumption it is a valid token. If not, then the default token is added.
- when: proxy-rsp
do:
- with: proxy-req-field<Author-i-tay>
select:
- is-empty:
do:
- proxy-rsp-field<Author-i-tay>: text-block<default-jwt>
Once this is set up, pushes of new tokens to the file system on the production system takes no more than 12 hours to show up in the default tokens used.
Path Tweaking¶
This example also serves to illustrate the use of continue
in with
. The goal is to
adjust a file name in a path depending on the presence of specific query parameters, which are then
discarded. If the parameter "ma=0" is present, then the base file name must have the string "_noma"
attached. If the parameter "mc=1" is present, then the base filename must have the string "_mac"
attached. If both are present then both strings are added.
- var<path>: "boot" # base file name stem.
- with: ua-req-query # Check for ma=0
select:
- contains: "ma=0"
do:
- var<path>: "{var<path>}_noma"
continue:
- with: ua-req-query # Check for mc=1
select:
- contains: "mc=1"
do:
- var<path>: "{var<path>}_mac"
continue:
# Correct file name is assembled, update the path.
- ua-req-path: "edge/file/{var<path>}.ipxe.img"
- ua-req-query: NULL # Do not pass the query parameters upstream.
The configuration uses with
to check for the query parameters and adjust a variable
containing the file name as needed. The continue
causes the invocation to proceed past the
with
even if it matches. At the end, the path is assembled and set and the query parameters
cleared.