The xbody vmod provides access to request and response bodies.
On the response side, xbody is a streaming regular expression engine which allows
Varnish to manipulate, capture, hash, and log body text. xbody supports full PCRE regex expressions,
capture groups, and backreferences.
xbody supports two important modes: regsub and capture.
regsub performs a regular expression substitution. Any number of regsub substitutions
can be done on a response body.
capture finds a pattern and then put its substitution, or value, into a JSON object
which can then be queried out using VCL or included directly in an Edgestash template.
Any number of capture calls can be done on a response body.
When regsub and capture are combined, Varnish can convert a dynamic and personalized
response into a JSON object and Edgestash template, allowing for uncacheable content
to be cached and accelerated. Please see Edgestash for
more information on JSON based templating.
Note: Because xbody is a streaming regex parser, embedded use of anchors (^ and $) is not supported.
Anchors can only be used as the first and last characters of your regex expression.
For assistance in creating PCRE compatible regular expressions, please use regex101.com.
Click here for legacy vmod_bodyaccess.
Input (backend): This is a test!
import std;
import xbody;
sub vcl_backend_response
{
xbody.regsub("test", "new string");
xbody.regsub("\sis", " isn't");
xbody.regsub("string", "cat");
xbody.capture("name", "new (\w+)", "\1");
}
sub vcl_deliver
{
std.log("Name: " + xbody.get("name"));
std.log("Capture JSON: " + xbody.get_all());
}
Output (cache): This isn't a new cat!
Captures (xbody.get_all()):
{
"name": "cat"
}
import xbody;
sub vcl_backend_response
{
xbody.regsub("www.example.com", "test.example.com");
xbody.regsub("admin@example.com", "dev@test.example.com");
}
import crypto;
import edgestash;
import xbody;
sub vcl_backend_response{
if (beresp.http.Content-Type ~ "html") {
xbody.regsub("<script>", "<script nonce={{nonce}}>");
edgestash.parse_response();
}
}
sub vcl_deliver {
if (edgestash.is_edgestash()) {
set req.http.X-nonce = crypto.hex_encode(crypto.urandom(16));
set resp.http.Content-Security-Policy = "script-src 'nonce-" + req.http.X-nonce + "'";
edgestash.add_json({"
{
"nonce":""} + req.http.X-nonce + {""
}
"});
edgestash.execute();
}
}
Convert a JSON response into JSONP using a callback query parameter.
import urlplus;
import xbody;
sub vcl_backend_response {
# Insert the JSONP callback
if (urlplus.query_get("callback") && beresp.http.Content-Type ~ "json") {
xbody.regsub("^", urlplus.query_get("callback") + "(");
xbody.regsub("$", ");");
set beresp.http.Content-Type = regsub(beresp.http.Content-Type, "json", "javascript");
}
}
Cache PHP pages with personalized content.
Personalized content is wrapped with an edgestash-name attribute.
These attributes will be templated and delivered per session.
PHP:
<span edgestash-name="greeting">
Hello [name]!
</span>
<span edgestash-name="number-items">
5
</span>
Content ...
VCL:
import cookieplus;
import edgestash;
import xbody;
import ykey;
sub vcl_recv
{
# Purge session data
if (req.method == "POST") {
# Broadcaster can be used to send this purge to a cluster
ykey.purge(cookieplus.get("PHPSESSID"));
}
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
return(hash);
}
sub vcl_hash
{
# Add the session to xbody data
if (req.http.host ~ "^xbody\.") {
hash_data(cookieplus.get("PHPSESSID", "guest"));
}
}
sub vcl_pass
{
# When you purge, you may be forced to pass
if (req.http.host ~ "^xbody\.") {
return (restart);
}
}
sub vcl_backend_fetch
{
# Remove xbody host marker
unset bereq.http.xbody;
if (bereq.http.Host ~ "^xbody\.") {
set bereq.http.Host = regsub(bereq.http.Host, "^xbody\.", "");
set bereq.http.xbody = "true";
}
}
sub vcl_backend_response
{
if (beresp.http.Content-Type ~ "text") {
if(bereq.http.xbody) {
# Extract xbody data, add a ykey session key
xbody.capture("\2", {"(<[^>]*edgestash-name="?([^"\s]+)"?[^>]+>)([^>]*)<"}, "\3", "g");
ykey.add_key(cookieplus.get("PHPSESSID", "guest"));
set beresp.ttl = 1d;
} else {
# Edgestash template
xbody.regsub({"(<[^>]*edgestash-name="?([^"\s]+)"?[^>]+>)([^>]*)<"}, "\1{{\2}}<");
edgestash.parse_response();
}
}
}
sub vcl_deliver
{
if (edgestash.is_edgestash()) {
# Use the homepage as the xbody data source
edgestash.add_json_url("/", json_host="xbody." + req.http.host, xbody=true);
edgestash.execute();
}
}
VOID regsub(STRING pattern, STRING substitution, STRING mode = "g", INT max = 0)
Perform a regex substitution on the response body.
Arguments:
pattern accepts type STRING
substitution accepts type STRING
mode accepts type STRING with a default value of g optional
max accepts type INT with a default value of 0 optional
Type: Function
Returns: None
Restricted to: vcl_backend_response
VOID capture(STRING name, STRING pattern, STRING value, STRING mode = "", INT max = 0)
Perform a regex capture on the response body. If pattern is found, value is set as the value of name.
Use xbody.get() to read the value in sub vcl_deliver.
When used, streaming is disabled as to allow the captured value to be safely read in sub vcl_deliver.
Arguments:
name accepts type STRING
pattern accepts type STRING
value accepts type STRING
mode accepts type STRING with a default value of empty. optional
max accepts type INT with a default value of 0 optional
Type: Function
Returns: None
Restricted to: vcl_backend_response
VOID hash_body(ENUM {md5,sha1,sha224,sha256,sha384,sha512} algorithm, STRING name = "", BOOL do_stream = 0)
Generate the hash of the response body and store it under the label name. It can then be retrieved in vcl_deliver with xbody.get_hash()
using the same name
import xbody;
import crypto;
sub vcl_backend_response {
xbody.hash_body(md5, "1");
xbody.hash_body(sha1, "2");
}
sub vcl_deliver {
set resp.http.md5 = crypto.hex_encode(xbody.get_hash("1"));
set resp.http.sha1 = crypto.hex_encode(xbody.get_hash("2"));
}
Arguments:
name accepts type STRING with a default value of empty. optional
do_stream accepts type BOOL with a default value of 0 optional
algorithm is an ENUM that accepts values of md5, sha1, sha224, sha256, sha384, and sha512
Type: Function
Returns: None
Restricted to: vcl_backend_response
BLOB get_hash(STRING name = "")
Get the hash generated with hash_body. name must match the value you used when xbody.hash_body() was called.
This function can be useful for example to generate an Etag if the backend didn’t provide one
import blob;
import xbody;
sub vcl_backend_response {
# no etag, ask Varnish to hash the body as it receives it
if (!beresp.http.etag) {
xbody.hash_body(sha1);
}
}
sub vcl_deliver {
# set the ETag header to the hash we computed during fetching
# surrounding with double-quote to follow the HTTP specification
if (!resp.http.etag) {
set resp.http.etag = {"""} + blob.encode(BASE64, DEFAULT, xbody.get_hash()) + {"""};
}
}
Arguments:
name accepts type STRING with a default value of empty. optional
Type: Function
Returns: Blob
Restricted to: vcl_deliver
VOID log_body(BYTES max = 4096)
Log the response body to the VSL (varnishlog). The Body VSL tag is used for this operation.
The body logging can span multiple tag lines, this is determined by the maximum VSL record length.
Arguments:
max accepts type BYTES with a default value of 4096 optional
Type: Function
Returns: None
Restricted to: vcl_backend_response
STRING get(STRING value, STRING default = 0)
Get a capture value by name. Can only be used in sub vcl_deliver. Can be used in sub vcl_synth in combination with xbody.synth().
Arguments:
value accepts type STRING
default accepts type STRING with a default value of 0 optional
Type: Function
Returns: String
Restricted to: vcl_deliver, vcl_synth
STRING get_all()
Get all capture values as a JSON object string. Can only be used in sub vcl_deliver.
Can be used in sub vcl_synth in combination with xbody.synth().
Arguments: None
Type: Function
Returns: String
Restricted to: vcl_deliver, vcl_synth
VOID synth()
Allow captured JSON data to be accessible in sub vcl_synth.
Arguments: None
Type: Function
Returns: None
Restricted to: vcl_deliver
VOID set(STRING json)
Set capture values. When used, regex and capture operations are skipped.
This is used to replicate xbody operations via VHA.
Arguments:
json accepts type STRINGType: Function
Returns: None
Restricted to: vcl_backend_response
STRING get_req_body()
Get the contents of the request body.
Must call std.cache_req_body() prior to this call.
Arguments: None
Type: Function
Returns: String
Restricted to: vcl_recv
BLOB get_req_body_hash(ENUM {md5,sha1,sha224,sha256,sha384,sha512} algorithm)
Generate and return the hash of the request body.
Arguments:
algorithm is an ENUM that accepts values of md5, sha1, sha224, sha256, sha384, and sha512Type: Function
Returns: Blob
Restricted to: vcl_recv
VOID reset()
Reset the internal state.
Arguments: None
Type: Function
Returns: None
Restricted to: client, backend
The xbody VMOD is available in Varnish Enterprise version 6.0.1r1 and later.