Commit 02ce5905 authored by Geoff Simmons's avatar Geoff Simmons

Add the integer param to .add(), and the .integer() method.

parent 88dea3a7
......@@ -12,9 +12,9 @@
vmod_selector
=============
---------------------------------------------------------------------------------------
Varnish Module for matching strings associated with backends, regexen and other strings
---------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
Varnish Module for matching strings associated with backends, regexen and other data
------------------------------------------------------------------------------------
:Manual section: 3
......@@ -22,7 +22,7 @@ Varnish Module for matching strings associated with backends, regexen and other
.. _VMOD re2: https://code.uplex.de/uplex-varnish/libvmod-re2
......@@ -36,7 +36,7 @@ SYNOPSIS
# Set creation
new <obj> = selector.set([BOOL case_sensitive])
VOID <obj>.add(STRING [, STRING string] [, STRING regex]
[, BACKEND backend])
[, BACKEND backend] [, INT integer])
VOID <obj>.create_stats()
# Matching
......@@ -49,12 +49,13 @@ SYNOPSIS
INT <obj>.which([ENUM select])
# Retrieving objects after match
STRING <obj>.element([INT n] [, ENUM select])
STRING <obj>.string([INT n,] [, ENUM select])
STRING <obj>.element([INT n] [, ENUM select])
STRING <obj>.string([INT n] [, ENUM select])
INT <obj>.integer([INT n] [, ENUM select])
BACKEND <obj>.backend([INT n] [, ENUM select])
BOOL <obj>.re_match(STRING [, INT n] [, ENUM select])
STRING <obj>.sub(STRING text, STRING rewrite [, BOOL all] [, INT n]
[, ENUM select])
BOOL <obj>.re_match(STRING [, INT n] [, ENUM select])
STRING <obj>.sub(STRING text, STRING rewrite [, BOOL all] [, INT n]
[, ENUM select])
# VMOD version
STRING selector.version()
......@@ -62,9 +63,11 @@ SYNOPSIS
DESCRIPTION
===========
.. _VMOD re2: https://code.uplex.de/uplex-varnish/libvmod-re2
Varnish Module (VMOD) for matching strings against sets of fixed
strings, and optionally associating the matched string with a backend,
another string, or a regular expression.
another string, an integer, or a regular expression.
The VMOD is intended to support a variety of use cases that are
typical for VCL deployments, such as:
......@@ -99,11 +102,12 @@ lines. For example::
# For requests with these Host headers, generate a redirect
# response, using the associated string to construct the
# Location header.
# Location header, and the integer to set the response code.
new redirect = selector.set();
redirect.add("www.foo.com", string="/foo");
redirect.add("www.bar.com", string="/bar");
redirect.add("www.baz.com", string="/baz");
redirect.add("www.foo.com", string="/foo", integer=301);
redirect.add("www.bar.com", string="/bar", integer=302);
redirect.add("www.baz.com", string="/baz", integer=303);
redirect.add("www.quux.com", string="/quux", integer=307);
# Requests for these URLs are rewritten by altering the
# query string, using the associated regex for a
......@@ -113,7 +117,6 @@ lines. For example::
rewrite.add("/alpha/beta", regex="(\?.*)\bfoo=[^&]+&?(.*)$");
rewrite.add("/delta/gamma", regex="(\?.*)\bbar=[^&]+&?(.*)$");
rewrite.add("/epsilon/zeta", regex="(\?.*)\bbaz=[^&]+&?(.*)$");
}
sub vcl_recv {
......@@ -121,13 +124,19 @@ lines. For example::
# .match() returns true if the Host header exactly matches
# one of the strings in the set.
if (redirect.match(req.http.Host)) {
# .string() returns the string added to the set above
# with the 'string' parameter, for the string that was
# matched. We assign it to another header, to be
# retrieved in vcl_synth below to construct the
# .string() returns the string added to the set above with
# the 'string' parameter, for the string that was
# matched. We use it to construct a Location header, which
# will be retrieved in vcl_synth below to construct the
# redirect response.
set req.http.X-URL-Prefix = redirect.string();
return (synth(301));
#
# .integer() returns the integer added to the set with the
# 'integer' parameter, for the string that was matched. We
# use it as the argument of synth() to set the response
# status (one of the redirect status codes).
set req.http.Location
= "http://other.com" + redirect.string() + req.url;
return (synth(redirect.integer()));
}
# If the URL matches the rewrite set, change the query string by
......@@ -136,7 +145,17 @@ lines. For example::
if (rewrite.match(req.req.url)) {
set req.url = rewrite.sub(req.url, "\1\2");
}
}
sub vcl_synth {
# We come here when Host matched the redirect set in vcl_recv
# above. Set the Location response header from the request header
# set in vcl_recv.
if (req.http.Location && resp.status >= 301 && resp.status <= 307) {
set resp.http.Location = req.http.Location;
return (deliver);
}
}
sub vcl_backend_fetch {
......@@ -148,21 +167,6 @@ lines. For example::
# string in the set that was matched as a prefix.
set bereq.backend = url_prefix.backend();
}
}
sub vcl_synth {
# We come here when Host matched the redirect set in vcl_recv
# above. Set the Location response header using the URL prefix
# saved in the request header, and generate the redirect
# response.
if (resp.status == 301) {
set resp.http.Location
= "http://other.com" + req.http.X-URL-Prefix + req.url;
return (deliver);
}
}
Matches with the ``.match()`` and ``.hasprefix()`` methods scale well
......@@ -221,10 +225,10 @@ Just calling ``.hasprefix()`` may be sufficient if all that matters is
whether a string has any prefix that appears in the set. But for some
uses it may be necessary to identify one matching element of the set;
this is done in particular for the ``.element()``, ``.backend()``,
``.string()``, ``.re_match()`` and ``.sub()`` methods, which retrieve
data associated with a specific set element. For such cases, the
method parameters ``INT n`` and ``ENUM select`` are used to choose a
matched element.
``.string()``, ``.integer()``, ``.re_match()`` and ``.sub()`` methods,
which retrieve data associated with a specific set element. For such
cases, the method parameters ``INT n`` and ``ENUM select`` are used to
choose a matched element.
As indicated in the example, elements of a set are implicitly numbered
in the order in which they were added to the set using the ``.add()``
......@@ -237,9 +241,8 @@ method, starting from 1. In all of the following, the ``n`` and
operations.
* If ``n`` is greater than the number of elements in the set, the
method fails, returning a "error" return value (depending on the
method's return type), with an error message written to the Varnish
log (see `ERRORS`_ below).
method fails, with an error message written to the Varnish
log. See `ERRORS`_ below for details about method failure.
* If ``n`` <= 0, then the ``select`` parameter is used to choose an
element based on the most recent ``.match()`` or ``.hasprefix()``
......@@ -344,19 +347,21 @@ set.add(...)
VOID xset.add(
STRING,
STRING string=0,
STRING regex=0,
BACKEND backend=0
[STRING string],
[STRING regex],
[BACKEND backend],
[INT integer]
)
Add the given string to the set. As indicated above, elements added to
the set are implicitly numbered in the order in which they are added
with ``.add()``, starting with 1.
If values are set for the optional parameters ``string``, ``regex`` or
``backend``, then those values are associated with this element, and
can be retrieved with the ``.string()``, ``.backend()``,
``.re_match()`` or ``.sub()`` methods, as described below.
If values are set for the optional parameters ``string``, ``regex``,
``backend`` or ``integer``, then those values are associated with this
element, and can be retrieved with the ``.string()``, ``.backend()``,
``.integer()``, ``.re_match()`` or ``.sub()`` methods, as described
below.
A regular expression in the ``regex`` parameter is compiled at VCL load
time. If the compile fails, then the VCL load fails with an error message.
......@@ -673,6 +678,54 @@ Example::
}
.. _func_set.integer:
INT xset.integer(INT n, ENUM select)
------------------------------------
::
INT xset.integer(
INT n=0,
ENUM {UNIQUE, EXACT, FIRST, LAST, SHORTEST, LONGEST} select=UNIQUE
)
Returns the integer set by the ``integer`` parameter for the element of
the set indicated by ``n`` and ``select``, according to the rules
given above.
``.integer()`` invokes VCL failure (see `ERRORS`_) if:
* The rules for ``n`` and ``select`` indicate failure.
* No integer was set with the ``integer`` parameter in ``.add()``.
Note that VCL failure for ``.integer()`` differs from the failure mode
for other methods that retrieve data associated with the selected set
element, such as ``.string()`` and ``.backend()``. Since there is no
distinguished "error value" for an INT, the VMOD does not return one
that can be detected in VCL, so that processing could continue without
failure.
So you may want to check more carefully in VCL for possible errors
that may cause ``.integer()`` to fail; for example, by checking
whether ``.nmatches() == 1`` before calling ``.integer()`` with
``select=UNIQUE`` or ``select=EXACT``, if the previous match operation
was ``.hasprefix()`` and the set contains overlapping prefixes.
Example::
# Send a synthetic response if the URL has a prefix in the set,
# using the response code set in .add().
if (myset.hasprefix(req.url)) {
# Check .nmatches() to ensure that select=UNIQUE can be used
# without risk of VCL failure.
if (myset.nmatches() == 1) {
return( synth(myset.integer(select=UNIQUE)) );
}
}
.. _func_set.re_match:
BOOL xset.re_match(STRING subject, INT n, ENUM select)
......@@ -864,8 +917,14 @@ The method documentation above refers to two kinds of failures: method
failure and VCL failure.
When a method fails, an error message is written to the Varnish log
with the ``VCL_Error`` tag, and the method returns an "error" value,
which depends on the return type, as documented above.
with the ``VCL_Error`` tag. If the method's return type allows for a
distinguished "error" value, such as NULL for ``.string()``, then that
value is returned. VCL processing can then continue (although your
code should of course check for the error).
For some return types, there is no such distinguished value; for
example for INT as returned by ``.integer()``. In such cases, VCL
failues is invoked.
VCL failure has the same results as if ``return(fail)`` is called from
a VCL subroutine:
......@@ -884,8 +943,8 @@ a VCL subroutine:
REQUIREMENTS
============
The VMOD requires Varnish since version 6.0.0, or the current master
branch.
The VMOD requires Varnish version 6.1. See the source repository for
other versions.
INSTALLATION
============
......@@ -931,10 +990,17 @@ SEE ALSO
========
* varnishd(1)
* vcl(7)
* varnishstat(1)
* varnish-cli(7)
* VMOD source repository: https://code.uplex.de/uplex-varnish/libvmod-selector
* Gitlab mirror: https://gitlab.com/uplex/varnish/libvmod-selector
* `VMOD re2`_: https://code.uplex.de/uplex-varnish/libvmod-re2
......
# looks like -*- vcl -*-
varnishtest "integer() method"
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foo", integer=0);
s.add("bar", integer=1);
s.add("baz", integer=2);
s.add("quux", integer=-1);
s.add("foobar", integer=-2);
}
sub vcl_recv {
return (synth(200));
}
sub vcl_synth {
set resp.http.N-1 = s.integer(1);
set resp.http.N-2 = s.integer(2);
set resp.http.N-3 = s.integer(3);
set resp.http.N-4 = s.integer(4);
set resp.http.N-5 = s.integer(5);
if (s.match(req.http.Word)) {
set resp.http.Integer = s.integer();
set resp.http.Integer-Unique = s.integer(select=UNIQUE);
set resp.http.Integer-Exact = s.integer(select=EXACT);
set resp.http.Integer-First = s.integer(select=FIRST);
set resp.http.Integer-Last = s.integer(select=LAST);
set resp.http.Integer-Shortest
= s.integer(select=SHORTEST);
set resp.http.Integer-Longest
= s.integer(select=LONGEST);
}
return (deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.N-1 == 0
expect resp.http.N-2 == 1
expect resp.http.N-3 == 2
expect resp.http.N-4 == -1
expect resp.http.N-5 == -2
txreq -hdr "Word: foo"
rxresp
expect resp.status == 200
expect resp.http.Integer == 0
expect resp.http.Integer-Unique == resp.http.Integer
expect resp.http.Integer-Exact == resp.http.Integer
expect resp.http.Integer-First == 0
expect resp.http.Integer-Last == 0
expect resp.http.Integer-Shortest == 0
expect resp.http.Integer-Longest == 0
txreq -hdr "Word: bar"
rxresp
expect resp.status == 200
expect resp.http.Integer == 1
expect resp.http.Integer-Unique == resp.http.Integer
expect resp.http.Integer-Exact == resp.http.Integer
expect resp.http.Integer-First == 1
expect resp.http.Integer-Last == 1
expect resp.http.Integer-Shortest == 1
expect resp.http.Integer-Longest == 1
txreq -hdr "Word: baz"
rxresp
expect resp.status == 200
expect resp.http.Integer == 2
expect resp.http.Integer-Unique == resp.http.Integer
expect resp.http.Integer-Exact == resp.http.Integer
expect resp.http.Integer-First == 2
expect resp.http.Integer-Last == 2
expect resp.http.Integer-Shortest == 2
expect resp.http.Integer-Longest == 2
txreq -hdr "Word: quux"
rxresp
expect resp.status == 200
expect resp.http.Integer == -1
expect resp.http.Integer-Unique == resp.http.Integer
expect resp.http.Integer-Exact == resp.http.Integer
expect resp.http.Integer-First == -1
expect resp.http.Integer-Last == -1
expect resp.http.Integer-Shortest == -1
expect resp.http.Integer-Longest == -1
txreq -hdr "Word: foobar"
rxresp
expect resp.status == 200
expect resp.http.Integer == -2
expect resp.http.Integer-Unique == resp.http.Integer
expect resp.http.Integer-Exact == resp.http.Integer
expect resp.http.Integer-First == -2
expect resp.http.Integer-Last == -2
expect resp.http.Integer-Shortest == -2
expect resp.http.Integer-Longest == -2
} -run
logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error {^vmod selector error: s\.match\(\): subject string is NULL$}
expect * = End
} -run
varnish v1 -vcl {
import ${vmod_selector};
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foo", integer=0);
s.add("bar", integer=1);
s.add("baz", integer=2);
s.add("quux", integer=-1);
s.add("foobar", integer=-2);
}
sub vcl_recv {
set req.http.Integer = s.integer(std.integer(req.http.Int, -7));
return (synth(200));
}
}
logexpect l1 -v v1 -d 0 -g vxid -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error {^vmod selector error: s\.integer\(\) called without prior match$}
expect 0 = ReqHeader "Integer: 0"
expect * = End
expect 0 * Begin req
expect * = VCL_Error {^vmod selector error: s\.integer\(\) called without prior match$}
expect 0 = ReqHeader "Integer: 0"
expect * = End
expect 0 * Begin req
expect * = VCL_Error {^vmod selector error: s\.integer\(6\): set has 5 elements$}
expect 0 = ReqHeader "Integer: 0"
expect * = End
} -start
client c1 {
txreq -hdr "Int: -1"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
expect_close
} -run
client c1 {
txreq -hdr "Int: 0"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
expect_close
} -run
client c1 {
txreq -hdr "Int: 6"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
expect_close
} -run
logexpect l1 -wait
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foobarbazquux", integer=1);
s.add("foobarbaz", integer=2);
s.add("foobar", integer=3);
s.add("foo", integer=4);
}
sub vcl_recv {
return (synth(200));
}
sub vcl_synth {
if (s.hasprefix(req.http.Word)) {
set resp.http.Integer = s.integer();
set resp.http.Integer-Unique = s.integer(select=UNIQUE);
set resp.http.Integer-Exact = s.integer(select=EXACT);
set resp.http.Integer-First = s.integer(select=FIRST);
set resp.http.Integer-Last = s.integer(select=LAST);
set resp.http.Integer-Shortest
= s.integer(select=SHORTEST);
set resp.http.Integer-Longest
= s.integer(select=LONGEST);
}
return (deliver);
}
}
client c1 {
txreq -hdr "Word: foo"
rxresp
expect resp.status == 200
expect resp.http.Integer == 4
expect resp.http.Integer-Unique == 4
expect resp.http.Integer-Exact == 4
expect resp.http.Integer-First == 4
expect resp.http.Integer-Last == 4
expect resp.http.Integer-Shortest == 4
expect resp.http.Integer-Longest == 4
} -run
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foobarbazquux", integer=1);
s.add("foobarbaz", integer=2);
s.add("foobar", integer=3);
s.add("foo", integer=4);
}
sub vcl_recv {
return (synth(200));
}
sub vcl_synth {
if (s.hasprefix(req.http.Word)) {
set resp.http.Integer-Exact = s.integer(select=EXACT);
set resp.http.Integer-First = s.integer(select=FIRST);
set resp.http.Integer-Last = s.integer(select=LAST);
set resp.http.Integer-Shortest
= s.integer(select=SHORTEST);
set resp.http.Integer-Longest
= s.integer(select=LONGEST);
}
return (deliver);
}
}
client c1 {
txreq -hdr "Word: foobar"
rxresp
expect resp.status == 200
expect resp.http.Integer-Exact == 3
expect resp.http.Integer-First == 3
expect resp.http.Integer-Last == 4
expect resp.http.Integer-Shortest == 4
expect resp.http.Integer-Longest == 3
txreq -hdr "Word: foobarbaz"
rxresp
expect resp.status == 200
expect resp.http.Integer-Exact == 2
expect resp.http.Integer-First == 2
expect resp.http.Integer-Last == 4
expect resp.http.Integer-Shortest == 4
expect resp.http.Integer-Longest == 2
txreq -hdr "Word: foobarbazquux"
rxresp
expect resp.status == 200
expect resp.http.Integer-Exact == 1
expect resp.http.Integer-First == 1
expect resp.http.Integer-Last == 4
expect resp.http.Integer-Shortest == 4
expect resp.http.Integer-Longest == 1
} -run
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foobarbazquux", integer=1);
s.add("foobarbaz", integer=2);
s.add("foobar", integer=3);
s.add("foo", integer=4);
}
sub vcl_recv {
if (s.hasprefix(req.http.Word)) {
set req.http.Integer-Exact = s.integer(select=EXACT);
set req.http.Integer-Unique = s.integer(select=UNIQUE);
}
return (synth(200));
}
}
logexpect l1 -v v1 -d 0 -g vxid -q "VCL_Error" {
expect * * Begin req
expect * = ReqHeader "Integer-Exact: 3"
expect * = VCL_Error {^vmod selector error: s\.integer\(select=UNIQUE\): 2 elements were matched$}
expect 0 = ReqHeader "Integer-Unique: 0"
expect * = End
expect 0 * Begin req
expect * = VCL_Error {^vmod selector error: s\.integer\(select=EXACT\): no element matched exactly$}
expect 0 = ReqHeader "Integer-Exact: 0"
expect * = End
} -start
client c1 {
txreq -hdr "Word: foobar"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
expect_close
} -run
client c1 {
txreq -hdr "Word: foobarb"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
expect_close
} -run
logexpect l1 -wait
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foo", integer=0);
s.add("bar");
}
sub vcl_recv {
if (s.match(req.http.Word)) {
set req.http.Integer = s.integer();
}
return (synth(200));
}
sub vcl_synth {
set resp.http.Integer = req.http.Integer;
return (deliver);
}
}
logexpect l1 -v v1 -d 0 -g vxid -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error {^vmod selector error: s\.integer\(\): integer not added for element 2$}
expect 0 = ReqHeader "Integer: 0"
expect * = End
} -start
client c1 {
txreq -hdr "Word: foo"
rxresp
expect resp.status == 200
expect resp.http.Integer == 0
txreq -hdr "Word: bar"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
expect_close
} -run
logexpect l1 -wait
......@@ -61,12 +61,14 @@ struct entry {
char *string;
VCL_BACKEND backend;
vre_t *re;
VCL_INT integer;
};
enum bitmap_e {
STRING = 0,
BACKEND,
REGEX,
INTEGER,
__MAX_BITMAP,
};
......@@ -240,8 +242,8 @@ vmod_set__fini(struct vmod_selector_set **setp)
}
VCL_VOID
vmod_set_add(VRT_CTX, struct vmod_selector_set *set, VCL_STRING member,
VCL_STRING string, VCL_STRING regex, VCL_BACKEND backend)
vmod_set_add(VRT_CTX, struct vmod_selector_set *set,
struct vmod_set_add_arg *args)
{
struct entry *entry;
unsigned n;
......@@ -252,7 +254,8 @@ vmod_set_add(VRT_CTX, struct vmod_selector_set *set, VCL_STRING member,
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);
CHECK_OBJ_ORNULL(backend, DIRECTOR_MAGIC);
AN(args);
CHECK_OBJ_ORNULL(args->backend, DIRECTOR_MAGIC);
if ((ctx->method & VCL_MET_INIT) == 0) {
VFAIL(ctx, "%s.add() may only be called in vcl_init",
......@@ -260,7 +263,7 @@ vmod_set_add(VRT_CTX, struct vmod_selector_set *set, VCL_STRING member,
return;
}
if (member == NULL) {
if (args->arg1 == NULL) {
VFAIL(ctx, "%s.add(): string to be added is NULL",
set->vcl_name);
return;
......@@ -270,7 +273,7 @@ vmod_set_add(VRT_CTX, struct vmod_selector_set *set, VCL_STRING member,
n = set->nmembers;
set->members = realloc(set->members, n * sizeof(VCL_STRING));
AN(set->members);
set->members[n - 1] = strdup(member);
set->members[n - 1] = strdup(args->arg1);
AN(set->members[n - 1]);
members = set->members;
......@@ -278,7 +281,7 @@ vmod_set_add(VRT_CTX, struct vmod_selector_set *set, VCL_STRING member,
set->lomembers = realloc(set->lomembers,
n * sizeof(VCL_STRING));
AN(set->lomembers);
set->lomembers[n - 1] = strdup(member);
set->lomembers[n - 1] = strdup(args->arg1);
AN(set->lomembers[n - 1]);
for (char *m = set->lomembers[n-1]; *m; m++)
*m = tolower(*m);
......@@ -289,43 +292,49 @@ vmod_set_add(VRT_CTX, struct vmod_selector_set *set, VCL_STRING member,
if (PT_Insert(&set->origo, n - 1, members) != 0) {
if (errno == EINVAL)
VFAIL(ctx, "%s.add(): \"%s\" added more than once",
set->vcl_name, member);
set->vcl_name, args->arg1);
else
VFAIL(ctx, "%s.add(\"%s\") failed: %s", set->vcl_name,
member, strerror(errno));
args->arg1, strerror(errno));
return;
}
if (regex != NULL) {
if (args->valid_regex) {
/* XXX expose VRE options */
re = VRE_compile(regex, 0, &error, &erroffset);
re = VRE_compile(args->regex, 0, &error, &erroffset);
if (re == NULL) {
VFAIL(ctx, "%s.add(): cannot compile regular expression"
" '%s': %s at offset %d", set->vcl_name, regex,
error, erroffset);
" '%s': %s at offset %d", set->vcl_name,
args->regex, error, erroffset);
return;
}
}
if (string == NULL && re == NULL && backend == NULL)
if (!args->valid_string && re == NULL && !args->valid_backend
&& !args->valid_integer)
return;
set->table = realloc(set->table, n * sizeof(struct entry *));
AN(set->table);
ALLOC_OBJ(entry, VMOD_SELECTOR_ENTRY_MAGIC);
AN(entry);
if (string != NULL) {
entry->string = strdup(string);
if (args->valid_string) {
AN(args->string);
entry->string = strdup(args->string);
set_added(set, n - 1, STRING);
}
if (re != NULL) {
entry->re = re;
set_added(set, n - 1, REGEX);
}
if (backend != NULL) {
entry->backend = backend;
if (args->valid_backend) {
entry->backend = args->backend;
set_added(set, n - 1, BACKEND);
}
if (args->valid_integer) {
entry->integer = args->integer;
set_added(set, n - 1, INTEGER);
}
set->table[n - 1] = entry;
}
......@@ -676,6 +685,28 @@ vmod_set_string(VRT_CTX, struct vmod_selector_set * set, VCL_INT n,
return (s);
}
VCL_INT
vmod_set_integer(VRT_CTX, struct vmod_selector_set * set, VCL_INT n,
VCL_ENUM selects)
{
unsigned idx;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);
idx = get_idx(ctx, n, set, "integer", selects);
if (idx == UINT_MAX) {
*ctx->handling = VCL_RET_FAIL;
return (0);
}
if (!check_added(ctx, set, idx, INTEGER, "integer", "integer")) {
*ctx->handling = VCL_RET_FAIL;
return (0);
}
return (set->table[idx]->integer);
}
static vre_t *
get_re(VRT_CTX, const struct vmod_selector_set * const restrict set,
VCL_INT n, VCL_ENUM const restrict selects,
......
......@@ -7,12 +7,10 @@
# See LICENSE
#
$Module selector 3 Varnish Module for matching strings associated with backends, regexen and other strings
$Module selector 3 Varnish Module for matching strings associated with backends, regexen and other data
$ABI vrt
.. _VMOD re2: https://code.uplex.de/uplex-varnish/libvmod-re2
$Synopsis manual
SYNOPSIS
......@@ -25,7 +23,7 @@ SYNOPSIS
# Set creation
new <obj> = selector.set([BOOL case_sensitive])
VOID <obj>.add(STRING [, STRING string] [, STRING regex]
[, BACKEND backend])
[, BACKEND backend] [, INT integer])
VOID <obj>.create_stats()
# Matching
......@@ -38,12 +36,13 @@ SYNOPSIS
INT <obj>.which([ENUM select])
# Retrieving objects after match
STRING <obj>.element([INT n] [, ENUM select])
STRING <obj>.string([INT n,] [, ENUM select])
STRING <obj>.element([INT n] [, ENUM select])
STRING <obj>.string([INT n] [, ENUM select])
INT <obj>.integer([INT n] [, ENUM select])
BACKEND <obj>.backend([INT n] [, ENUM select])
BOOL <obj>.re_match(STRING [, INT n] [, ENUM select])
STRING <obj>.sub(STRING text, STRING rewrite [, BOOL all] [, INT n]
[, ENUM select])
BOOL <obj>.re_match(STRING [, INT n] [, ENUM select])
STRING <obj>.sub(STRING text, STRING rewrite [, BOOL all] [, INT n]
[, ENUM select])
# VMOD version
STRING selector.version()
......@@ -51,9 +50,11 @@ SYNOPSIS
DESCRIPTION
===========
.. _VMOD re2: https://code.uplex.de/uplex-varnish/libvmod-re2
Varnish Module (VMOD) for matching strings against sets of fixed
strings, and optionally associating the matched string with a backend,
another string, or a regular expression.
another string, an integer, or a regular expression.
The VMOD is intended to support a variety of use cases that are
typical for VCL deployments, such as:
......@@ -88,11 +89,12 @@ lines. For example::
# For requests with these Host headers, generate a redirect
# response, using the associated string to construct the
# Location header.
# Location header, and the integer to set the response code.
new redirect = selector.set();
redirect.add("www.foo.com", string="/foo");
redirect.add("www.bar.com", string="/bar");
redirect.add("www.baz.com", string="/baz");
redirect.add("www.foo.com", string="/foo", integer=301);
redirect.add("www.bar.com", string="/bar", integer=302);
redirect.add("www.baz.com", string="/baz", integer=303);
redirect.add("www.quux.com", string="/quux", integer=307);
# Requests for these URLs are rewritten by altering the
# query string, using the associated regex for a
......@@ -102,7 +104,6 @@ lines. For example::
rewrite.add("/alpha/beta", regex="(\?.*)\bfoo=[^&]+&?(.*)$");
rewrite.add("/delta/gamma", regex="(\?.*)\bbar=[^&]+&?(.*)$");
rewrite.add("/epsilon/zeta", regex="(\?.*)\bbaz=[^&]+&?(.*)$");
}
sub vcl_recv {
......@@ -110,13 +111,19 @@ lines. For example::
# .match() returns true if the Host header exactly matches
# one of the strings in the set.
if (redirect.match(req.http.Host)) {
# .string() returns the string added to the set above
# with the 'string' parameter, for the string that was
# matched. We assign it to another header, to be
# retrieved in vcl_synth below to construct the
# .string() returns the string added to the set above with
# the 'string' parameter, for the string that was
# matched. We use it to construct a Location header, which
# will be retrieved in vcl_synth below to construct the
# redirect response.
set req.http.X-URL-Prefix = redirect.string();
return (synth(301));
#
# .integer() returns the integer added to the set with the
# 'integer' parameter, for the string that was matched. We
# use it as the argument of synth() to set the response
# status (one of the redirect status codes).
set req.http.Location
= "http://other.com" + redirect.string() + req.url;
return (synth(redirect.integer()));
}
# If the URL matches the rewrite set, change the query string by
......@@ -125,7 +132,17 @@ lines. For example::
if (rewrite.match(req.req.url)) {
set req.url = rewrite.sub(req.url, "\1\2");
}
}
sub vcl_synth {
# We come here when Host matched the redirect set in vcl_recv
# above. Set the Location response header from the request header
# set in vcl_recv.
if (req.http.Location && resp.status >= 301 && resp.status <= 307) {
set resp.http.Location = req.http.Location;
return (deliver);
}
}
sub vcl_backend_fetch {
......@@ -137,21 +154,6 @@ lines. For example::
# string in the set that was matched as a prefix.
set bereq.backend = url_prefix.backend();
}
}
sub vcl_synth {
# We come here when Host matched the redirect set in vcl_recv
# above. Set the Location response header using the URL prefix
# saved in the request header, and generate the redirect
# response.
if (resp.status == 301) {
set resp.http.Location
= "http://other.com" + req.http.X-URL-Prefix + req.url;
return (deliver);
}
}
Matches with the ``.match()`` and ``.hasprefix()`` methods scale well
......@@ -210,10 +212,10 @@ Just calling ``.hasprefix()`` may be sufficient if all that matters is
whether a string has any prefix that appears in the set. But for some
uses it may be necessary to identify one matching element of the set;
this is done in particular for the ``.element()``, ``.backend()``,
``.string()``, ``.re_match()`` and ``.sub()`` methods, which retrieve
data associated with a specific set element. For such cases, the
method parameters ``INT n`` and ``ENUM select`` are used to choose a
matched element.
``.string()``, ``.integer()``, ``.re_match()`` and ``.sub()`` methods,
which retrieve data associated with a specific set element. For such
cases, the method parameters ``INT n`` and ``ENUM select`` are used to
choose a matched element.
As indicated in the example, elements of a set are implicitly numbered
in the order in which they were added to the set using the ``.add()``
......@@ -226,9 +228,8 @@ method, starting from 1. In all of the following, the ``n`` and
operations.
* If ``n`` is greater than the number of elements in the set, the
method fails, returning a "error" return value (depending on the
method's return type), with an error message written to the Varnish
log (see `ERRORS`_ below).
method fails, with an error message written to the Varnish
log. See `ERRORS`_ below for details about method failure.
* If ``n`` <= 0, then the ``select`` parameter is used to choose an
element based on the most recent ``.match()`` or ``.hasprefix()``
......@@ -320,16 +321,18 @@ Example::
# ...
}
$Method VOID .add(STRING, STRING string=0, STRING regex=0, BACKEND backend=0)
$Method VOID .add(STRING, [STRING string], [STRING regex], [BACKEND backend],
[INT integer])
Add the given string to the set. As indicated above, elements added to
the set are implicitly numbered in the order in which they are added
with ``.add()``, starting with 1.
If values are set for the optional parameters ``string``, ``regex`` or
``backend``, then those values are associated with this element, and
can be retrieved with the ``.string()``, ``.backend()``,
``.re_match()`` or ``.sub()`` methods, as described below.
If values are set for the optional parameters ``string``, ``regex``,
``backend`` or ``integer``, then those values are associated with this
element, and can be retrieved with the ``.string()``, ``.backend()``,
``.integer()``, ``.re_match()`` or ``.sub()`` methods, as described
below.
A regular expression in the ``regex`` parameter is compiled at VCL load
time. If the compile fails, then the VCL load fails with an error message.
......@@ -589,6 +592,45 @@ Example::
set req.url = myset.string();
}
$Method INT .integer(INT n=0,
ENUM {UNIQUE, EXACT, FIRST, LAST, SHORTEST, LONGEST}
select=UNIQUE)
Returns the integer set by the ``integer`` parameter for the element of
the set indicated by ``n`` and ``select``, according to the rules
given above.
``.integer()`` invokes VCL failure (see `ERRORS`_) if:
* The rules for ``n`` and ``select`` indicate failure.
* No integer was set with the ``integer`` parameter in ``.add()``.
Note that VCL failure for ``.integer()`` differs from the failure mode
for other methods that retrieve data associated with the selected set
element, such as ``.string()`` and ``.backend()``. Since there is no
distinguished "error value" for an INT, the VMOD does not return one
that can be detected in VCL, so that processing could continue without
failure.
So you may want to check more carefully in VCL for possible errors
that may cause ``.integer()`` to fail; for example, by checking
whether ``.nmatches() == 1`` before calling ``.integer()`` with
``select=UNIQUE`` or ``select=EXACT``, if the previous match operation
was ``.hasprefix()`` and the set contains overlapping prefixes.
Example::
# Send a synthetic response if the URL has a prefix in the set,
# using the response code set in .add().
if (myset.hasprefix(req.url)) {
# Check .nmatches() to ensure that select=UNIQUE can be used
# without risk of VCL failure.
if (myset.nmatches() == 1) {
return( synth(myset.integer(select=UNIQUE)) );
}
}
$Method BOOL .re_match(STRING subject, INT n=0,
ENUM {UNIQUE, EXACT, FIRST, LAST, SHORTEST, LONGEST}
select=UNIQUE)
......@@ -748,8 +790,14 @@ The method documentation above refers to two kinds of failures: method
failure and VCL failure.
When a method fails, an error message is written to the Varnish log
with the ``VCL_Error`` tag, and the method returns an "error" value,
which depends on the return type, as documented above.
with the ``VCL_Error`` tag. If the method's return type allows for a
distinguished "error" value, such as NULL for ``.string()``, then that
value is returned. VCL processing can then continue (although your
code should of course check for the error).
For some return types, there is no such distinguished value; for
example for INT as returned by ``.integer()``. In such cases, VCL
failues is invoked.
VCL failure has the same results as if ``return(fail)`` is called from
a VCL subroutine:
......@@ -768,8 +816,8 @@ a VCL subroutine:
REQUIREMENTS
============
The VMOD requires Varnish since version 6.0.0, or the current master
branch.
The VMOD requires Varnish version 6.1. See the source repository for
other versions.
INSTALLATION
============
......@@ -815,10 +863,17 @@ SEE ALSO
========
* varnishd(1)
* vcl(7)
* varnishstat(1)
* varnish-cli(7)
* VMOD source repository: https://code.uplex.de/uplex-varnish/libvmod-selector
* Gitlab mirror: https://gitlab.com/uplex/varnish/libvmod-selector
* `VMOD re2`_: https://code.uplex.de/uplex-varnish/libvmod-re2
$Event event
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment