Commit 824451a2 authored by Nils Goroll's avatar Nils Goroll

initial release

parent 008606f9
Copyright 2018 UPLEX Nils Goroll Systemoptimierung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
.. role:: ref(emphasis)
.. _vmod_frozen(3):
===========
vmod_frozen
===========
---------------------
Varnish frozen Module
---------------------
:Manual section: 3
SYNOPSIS
========
::
import frozen [from "path"] ;
new xparser = parser(INT depth)
VOID xparser.expect(STRING path, ENUM type, BOOL null, BOOL required)
BOOL xparser.parse(STRING)
STRING xparser.extract(STRING path, STRING null, STRING undef)
STRING xparser.type(STRING path)
DESCRIPTION
===========
This vmod makes available to VCL the _frozen_ JSON parser with low
overhead: By specifying a set of expected JSON paths, a callback to
the parser is used to track only paths of interest, which can then be
extracted.
Example
::
import frozen;
sub vcl_init {
new jwt_hdr = frozen.parser();
jwt_hdr.expect(".alg", type=STRING, null=false, required=true);
jwt_hdr.expect(".typ", type=STRING, null=false, required=true);
new jwt_payload = frozen.parser();
jwt_payload.expect(".attr");
}
sub vcl_recv {
if (! jwt_hdr.parse(...) ||
jwt_hdr.extract(".alg") != "RS256" ||
jwt_hdr.extract(".typ") != "JWT") {
return (synth(400, "unsupported jwt alg/typ"));
}
if (! jwt_payload.parse(...)) {
return (synth(400, "parse error"));
}
set req.http.something =
jwt_payload.extract(".attr",
null = "<null>", undef = "<undef>");
# ...
}
INSTALL
=======
Building from source
~~~~~~~~~~~~~~~~~~~~
This VMOD is using the standard build procedure, but includes the
`frozen` parser as a submodule, so the source should be cloned using::
git clone --recurse-submodules $URL
Quick start
-----------
This sequence should be enough in typical setups:
1. ``./bootstrap``
2. ``make``
3. ``make check`` (regression tests)
4. ``make install`` (may require root: sudo make install)
Alternative configs
-------------------
If you have installed Varnish to a non-standard directory, call
``autogen.sh`` and ``configure`` with ``PKG_CONFIG_PATH`` pointing to
the appropriate path. For example, when varnishd configure was called
with ``--prefix=$PREFIX``, use::
PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig
ACLOCAL_PATH=${PREFIX}/share/aclocal
export PKG_CONFIG_PATH ACLOCAL_PATH
CONTENTS
========
* :ref:`obj_parser`
* :ref:`func_parser.expect`
* :ref:`func_parser.extract`
* :ref:`func_parser.parse`
* :ref:`func_parser.type`
.. _obj_parser:
new xparser = parser(INT depth=10)
----------------------------------
Instiantiate a JSON parser object.
The `depth` argument specifies the maximum recursion depth. This
should be set to the lowest possible value based on the expected input
to avoid stack overflows.
Without additional configuration using the `.expect()` method, a JSON
parser obect will just check the syntactic validity of JSON input.
.. _func_parser.expect:
parser.expect(...)
------------------
::
VOID xparser.expect(
STRING path,
ENUM {ANY, STRING, NUMBER, BOOL, OBJECT, ARRAY} type=ANY,
BOOL null=1,
BOOL required=0
)
Prepare a parser object for extration of a value at `path`, which is
constructed as follows:
* the root element has the empty path ``""``
* for objects, ``.`` (dot) is appended to the path
* for object keys, the key name is appended to the path
* for arrays, [ELEMENT_INDEX] is appended for each element
The `type` argument may be use to restrict matches to a particular
JSON type.
The `null` argument specifies if ``null`` is to be accepted as a valid
value. For ``type = ANY``, `null` is _not_ implied.
The `required` argument speifies if presence of this value is required
for successful `.match()`
This method may only be called from ``vcl_init {}``.
For best efficiency, call `.expect` according to the most likely order
of elements in JSON input to be parsed.
Errors will cause a VCL failure.
=== path examples ===
Consider this json string::
{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }
The following examples are valid paths (values given for clarity)
* ``".foo" == "123"``
* ``".bar[0]" == "1"``
* ``".bar[2].baz" == "true"``
.. _func_parser.parse:
BOOL xparser.parse(STRING)
--------------------------
Parse the given string with the parser object.
The method will return `true`, if
* The string represents valid JSON
* The recursion depth is not exceeded
* All expected paths with the `required` argument set to `true`
have been found.
For expected paths, the first match is recorded.
Details on parse errors are logged as ``VCL_Error``
.. _func_parser.extract:
parser.extract(...)
-------------------
::
STRING xparser.extract(
STRING path,
STRING null="",
STRING undef=""
)
After a successful `.parse()`, extract the given path, which must have
been declared in ``vcl_init {}`` using `.expect()`.
For JSON ``null`` values, the `null` argument is returned.
For paths which are not found and not defined as required, the `undef`
argument is returned.
.. _func_parser.type:
STRING xparser.type(STRING path)
--------------------------------
Return the JSON type of an extracted path or NULL otherwise.
This is particularly relevant for paths expected to be of type ANY if
strings need to be differentiated from other types.
SEE ALSO
========vcl\(7),varnishd\(1)
COPYRIGHT
=========
::
Copyright 2018 UPLEX Nils Goroll Systemoptimierung
All rights reserved
Author: Nils Goroll <nils.goroll@uplex.de>
See LICENSE
AC_PREREQ([2.68]) AC_PREREQ([2.68])
AC_COPYRIGHT([Copyright 2018 UPLEX Nils Goroll Systemoptimierung])
AC_INIT([libvmod-frozen], [0.1]) AC_INIT([libvmod-frozen], [0.1])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_AUX_DIR([build-aux])
......
...@@ -8,7 +8,9 @@ vmod_LTLIBRARIES = \ ...@@ -8,7 +8,9 @@ vmod_LTLIBRARIES = \
libvmod_frozen.la libvmod_frozen.la
libvmod_frozen_la_LDFLAGS = $(VMOD_LDFLAGS) libvmod_frozen_la_LDFLAGS = $(VMOD_LDFLAGS)
libvmod_frozen_la_SOURCES = vmod_frozen.c libvmod_frozen_la_SOURCES = \
vmod_frozen.c \
vmod_frozen.h
nodist_libvmod_frozen_la_SOURCES = \ nodist_libvmod_frozen_la_SOURCES = \
vcc_frozen_if.c \ vcc_frozen_if.c \
vcc_frozen_if.h \ vcc_frozen_if.h \
......
VMODENUM(ANY)
VMODENUM(ARRAY)
VMODENUM(BOOL)
VMODENUM(NUMBER)
VMODENUM(OBJECT)
VMODENUM(STRING)
#undef VMODENUM
This diff is collapsed.
enum type_e {
#define VMODENUM(x) type_ ## x,
_TYPE_E_INVALID,
#include "tbl_enum_type.h"
_TYPE_E_MAX
};
#-
# Copyright 2018 UPLEX Nils Goroll Systemoptimierung
# All rights reserved
#
# Author: Nils Goroll <nils.goroll@uplex.de>
#
# See LICENSE
#
$Module frozen 3 Varnish frozen Module $Module frozen 3 Varnish frozen Module
DESCRIPTION DESCRIPTION
=========== ===========
This VCC file was generated by VCDK, it is used to for both the VMOD This vmod makes available to VCL the _frozen_ JSON parser with low
interface and its manual using reStructuredText. overhead: By specifying a set of expected JSON paths, a callback to
the parser is used to track only paths of interest, which can then be
XXX: document vmod-frozen extracted.
Example Example
:: ::
import frozen; import frozen;
sub vcl_init {
new jwt_hdr = frozen.parser();
jwt_hdr.expect(".alg", type=STRING, null=false, required=true);
jwt_hdr.expect(".typ", type=STRING, null=false, required=true);
new jwt_payload = frozen.parser();
jwt_payload.expect(".attr");
}
sub vcl_recv {
if (! jwt_hdr.parse(...) ||
jwt_hdr.extract(".alg") != "RS256" ||
jwt_hdr.extract(".typ") != "JWT") {
return (synth(400, "unsupported jwt alg/typ"));
}
if (! jwt_payload.parse(...)) {
return (synth(400, "parse error"));
}
set req.http.something =
jwt_payload.extract(".attr",
null = "<null>", undef = "<undef>");
# ...
}
$Object parser(INT depth=10)
Instiantiate a JSON parser object.
The `depth` argument specifies the maximum recursion depth. This
should be set to the lowest possible value based on the expected input
to avoid stack overflows.
Without additional configuration using the `.expect()` method, a JSON
parser obect will just check the syntactic validity of JSON input.
$Method VOID .expect(STRING path,
ENUM {ANY, STRING, NUMBER, BOOL, OBJECT, ARRAY} type = ANY,
BOOL null = 1, BOOL required = 0)
Prepare a parser object for extration of a value at `path`, which is
constructed as follows:
* the root element has the empty path ``""``
* for objects, ``.`` (dot) is appended to the path
* for object keys, the key name is appended to the path
* for arrays, [ELEMENT_INDEX] is appended for each element
The `type` argument may be use to restrict matches to a particular
JSON type.
The `null` argument specifies if ``null`` is to be accepted as a valid
value. For ``type = ANY``, `null` is _not_ implied.
The `required` argument speifies if presence of this value is required
for successful `.match()`
This method may only be called from ``vcl_init {}``.
For best efficiency, call `.expect` according to the most likely order
of elements in JSON input to be parsed.
Errors will cause a VCL failure.
=== path examples ===
Consider this json string::
{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }
The following examples are valid paths (values given for clarity)
* ``".foo" == "123"``
* ``".bar[0]" == "1"``
* ``".bar[2].baz" == "true"``
$Method BOOL .parse(STRING)
Parse the given string with the parser object.
The method will return `true`, if
* The string represents valid JSON
* The recursion depth is not exceeded
* All expected paths with the `required` argument set to `true`
have been found.
For expected paths, the first match is recorded.
Details on parse errors are logged as ``VCL_Error``
$Method STRING .extract(STRING path, STRING null = "", STRING undef = "")
After a successful `.parse()`, extract the given path, which must have
been declared in ``vcl_init {}`` using `.expect()`.
For JSON ``null`` values, the `null` argument is returned.
sub vcl_deliver { For paths which are not found and not defined as required, the `undef`
set resp.http.Hello = frozen.hello(); argument is returned.
}
XXX: define vmod-frozen interface $Method STRING .type(STRING path)
$Function STRING hello() Return the JSON type of an extracted path or NULL otherwise.
Description This is particularly relevant for paths expected to be of type ANY if
Hello world for vmod-frozen strings need to be differentiated from other types.
SEE ALSO SEE ALSO
========vcl\(7),varnishd\(1) ========vcl\(7),varnishd\(1)
...@@ -6,16 +6,77 @@ server s1 { ...@@ -6,16 +6,77 @@ server s1 {
} -start } -start
varnish v1 -vcl+backend { varnish v1 -vcl+backend {
import frozen; import frozen;
sub vcl_deliver { sub vcl_init {
set resp.http.Hello = frozen.hello(); new p_test = frozen.parser();
} p_test.expect(".a");
p_test.expect(".b");
p_test.expect(".c");
p_test.expect(".d");
p_test.expect(".e");
p_test.expect(".f[0]");
p_test.expect(".f[1]");
p_test.expect(".g.1");
p_test.expect(".g.h[0]");
}
sub vcl_recv {
return (synth(200));
}
sub vcl_synth {
set resp.http.parse =
p_test.parse({"{ a: 1, b: "hi there", c: true, d: false, "} +
{" e : null, f: [ 1, -2, 3], g: { "1": [], h: [ 7 ] } } "});
set resp.http.a = p_test.extract(".a");
set resp.http.a-type = p_test.type(".a");
set resp.http.b = p_test.extract(".b");
set resp.http.b-type = p_test.type(".b");
set resp.http.c = p_test.extract(".c");
set resp.http.c-type = p_test.type(".c");
set resp.http.d = p_test.extract(".d");
set resp.http.d-type = p_test.type(".d");
set resp.http.e = p_test.extract(".e", null="<null>");
set resp.http.e-type = p_test.type(".e");
set resp.http.f0 = p_test.extract(".f[0]");
set resp.http.f0-type = p_test.type(".f[0]");
set resp.http.f1 = p_test.extract(".f[1]");
set resp.http.f1-type = p_test.type(".f[1]");
set resp.http.g1 = p_test.extract({".g.1"});
set resp.http.g1-type = p_test.type({".g.1"});
set resp.http.gh0 = p_test.extract(".g.h[0]");
set resp.http.gh0-type = p_test.type(".g.h[0]");
}
} -start } -start
client c1 { client c1 {
txreq txreq
rxresp rxresp
expect resp.status == 200 expect resp.status == 200
expect resp.http.Hello == "vmod-frozen" expect resp.http.a == 1
expect resp.http.a-type == NUMBER
expect resp.http.b == "hi there"
expect resp.http.b-type == STRING
expect resp.http.c == true
expect resp.http.c-type == BOOL
expect resp.http.d == false
expect resp.http.d-type == BOOL
expect resp.http.e == "<null>"
expect resp.http.e-type == ""
expect resp.http.f0 == 1
expect resp.http.f0-type == NUMBER
expect resp.http.f1 == -2
expect resp.http.f1-type == NUMBER
expect resp.http.g1 == "[]"
expect resp.http.g1-type == ARRAY
expect resp.http.gh0 == 7
expect resp.http.gh0-type == NUMBER
} -run } -run
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