NAME
Plack::Middleware::ReviseEnv - Revise request environment at will
VERSION
This document describes Plack::Middleware::ReviseEnv version 0.004.
SYNOPSIS
use Plack::Middleware::ReviseEnv;
my $mw = Plack::Middleware::ReviseEnv->new(
# straight value
var1 => 'a simple, overriding value',
# value from %ENV
var2 => '[% ENV:USER %]',
# value from other element in $env
var3 => '[% env:foobar %]',
# mix and match, values are templates actually
var4 => 'Hey [% ENV:USER %] this is [% env:var1 %]',
# to delete an element just "undef" it
X_REMOVE_ME => undef,
# overriding is the default behaviour, but you can disable it
X_FOO => {
value => 'Get this by default',
override => 0,
},
# the key is a template too!
'[% ENV:USER %]' => '[% ENV:HOME %]',
# the "key" can be specified inside, ignoring the "outer" one
IGNORED_KEY => {
key => 'THIS IS THE KEY!',
value => 'whatever',
},
# more examples and features below in example with array ref
);
# you can also pass the key/value pairs as a hash reference
# associated to a key named 'revisors'. This is necessary e.g. if
# you want to set a variable in $env with name 'app', 'opts' or
# 'revisors'
my $mw2 = Plack::Middleware::ReviseEnv->new(revisors => \%revisors);
# when evaluation order or repetition is important... use an array
# reference for 'revisors'. You can also avoid passing the external
# key here, and just provide a sequence of hash definitions
my $mw3 = Plack::Middleware::ReviseEnv->new(
revisors => [
KEY => { ... specification ... },
# by default inexistent/undef inputs are expanded as empty
# strings.
{
key => 'weird',
value => '[% ENV:HOST %]:[% ENV:UNDEFINED %]',
# %ENV = (HOST => 'www.example.com'); # no UNDEFINED
# # weird => 'www.example.com:' # note trailing colon...
},
# you can "fail" generating a variable if something is missing,
# so you can avoid the trailing colon above in two steps:
{
key => 'correct_port_spec',
value => ':[% ENV:PORT %]',
require_all => 1,
# %ENV = (); # no PORT
# # -> no "correct_port_spec" is generated in $env
# %ENV = (PORT => 8080);
# # correct_port_spec => ':8080'
},
{
key => 'host_and_port',
value => '[% ENV:HOST %][% env:correct_port_spec %]',
# %ENV = (HOST => 'www.example.com'); # no PORT
# # host_and_port => 'www.example.com'
# %ENV = (HOST => 'www.example.com', PORT => 8080);
# # host_and_port => 'www.example.com:8080'
}
# the default value is "undef" which ultimately means "do not
# generate" or "delete if existent". You can set a different
# one for the key and the value separately
{
key => '[% ENV:USER %]',
default_key => 'nobody',
value => '[% ENV:HOME %]',
default_value => '/tmp',
},
# the default is applied only when the outcome is "undef", but
# you can extend it to the empty string too. This is useful to
# obtain the same effect of shell's test [ -z "$VAR" ] which is
# true both for missing and empty values
{
key => '[% ENV:USER %]',
default_key => 'nobody',
value => '[% ENV:HOME %]',
default_value => '/tmp',
empty_as_default => 1,
}
# We can revisit the example on host/port and set defaults for
# the missing variables, using two temporary variables that
# will be cleared afterwards
{
key => '_host',
value => '[% ENV:HOST %]',
default_value => 'www.example.com',
empty_as_default => 1,
},
{
key => '_port',
value => '[% ENV:PORT %]',
default_value => '8080',
empty_as_default => 1,
},
host_and_port => '[% env:_host %]:[% env:_port %]',
_host => undef, # clear temporary variable
_port => undef, # ditto
]
);
DESCRIPTION
This module allows you to reshape Plack's $env
that is passed along to the sequence of apps, taking values from an interpolation of items in %ENV
and $env
.
At the most basic level, it allows you to get selected values from the environment and override some values in $env
accordingly. For example, if you want to use environment variables to configure a reverse proxy setup, you can use the following revisor definitions:
...
'psgi.url_scheme' => '[% ENV:RP_SCHEME %]',
'HTTP_HOST' => '[% ENV:RP_HOST %]',
'SCRIPT_NAME' => '[% ENV:RP_PATH %]',
...
This would basically implement the functionality provided by Dancer::Middleware::Rebase (without the strip
capabilities).
Value definitions are actually templates with normal text and variables expansions between delimiters. So, the following definition does what you think:
salutation => 'Hello, [% ENV:USER %], welcome [% ENV:HOME %]',
You are not limited to taking values from the environment and peek into $env
too:
...
bar => 'baz', # no expansion in this template, just returns 'bar'
foo => '[% env:bar %]',
...
As you can understand, if you want to peek at other values in $env
and these values are generated too, order matters! Take a look at "Ordering Revisors" to avoid being biten by this, but the bottom line is: use the array-reference form and put revisors in the order you want them evaluated.
Defining Revisors
There are multiple ways you can provide the definition of a revisor. Before explaining the details, it's useful to notice that you can invoke the constructor for Plack::Middleware::ReviseEnv
in different ways:
# the "hash" way, where %hash MUST NOT contain the "revisors" key
my $mwh = Plack::Middleware::ReviseEnv->new(%hash);
# the "hash reference" way
my $mwhr = Plack::Middleware::ReviseEnv->new(revisors => \%hash);
# the "array reference" way
my $mwar = Plack::Middleware::ReviseEnv->new(revisors => \@array);
The first two will be eventually turned into the last one by means of "normalize_input_structure" by simply putting the sequence of key/value pairs in the array, ordered by key.
In the array reference form, for each revisor you can provide:
a single hash reference with the details on the revisor (see below for the explaination), OR
a string key (that we will call external key) and a hash reference. If the hash reference contains the key
key
(sorry!) then theexternal key
will be ignored, otherwise it will be set corresponding to keykey
. Example:foo => { value => 'ciao' }
is interpreted as:
{ key => 'foo', value => 'ciao' }
while:
foo => { key => 'bar', value => 'baz' }
is interpreted as:
{ key => 'bar', value => 'baz' }
This is useful when you start from the hash or hash-reference forms, because the external key will be used for ordering revisors only (see "Ordering Revisors");
two strings, one for the key and one for the value. Example:
foo => 'bar'
is interpreted as:
{ key => 'foo', value => 'bar' }
While the normal key/value pairs should be sufficient in the general case, to trigger more advanced features you have to pass the whole hash reference definition for a revisors. The hash can contain the following keys:
cache
-
after computing a value the first time, cache the result for all following invocations. This will speed up the execution at the expense of flexibility.
You might want to use this option if you're only relying on value coming from
%ENV
and your code is not going to change its items dynamically. As this is probably the most common case, this option defaults to1
, which means that the value will be cached. You can disable it either in theopts
in the constructor, or per-revisor, by setting it to a Perl-false value; default_key
default_value
-
when the computed value for either the key or the value are undefined, the corresponding key is deleted. If you set a defined value, this will be used instead.
Setting a default value makes sense only if either
empty_as_default
orrequire_all
are set too; otherwise, whatever expansion will always yield a defined value (possibly empty). empty_as_default
-
when the computed value is empty, treat it as it were undefined. This is a single setting for both key and value.
It is useful if you suspect that your environment might actually contain a variable, but with an empty value that you want to override with a default.
esc
-
the escape character to use when parsing templates. It defaults to a single backslash, but you can override this with a different string as long as it's not empty, it does not start with a space and is different from both
start
andstop
(see below) values. This might come handy in the (unlikely) case that you must use lots of backslashes. key
-
the key that will be set in
$env
. It is a template itself, so it is subject to expansion and other rules explained here.If you set the revisor with the key/value pair style, the key will be used as the default value here; if you just provide a specification revisor via a hash reference, you MUST provide a key though.
override
-
boolean flag to indicate that you want to overwrite any previously existing value in
$env
for a specific computed key.It defaults to true, but you can set it to e.g.
0
to disable overriding and set the value in$env
only if there's nothing there already. require_all
-
boolean flag that makes an expansion fail (returning
undef
) if any component is missing. Defaults to a false value, meaning that missing values are expanded as empty (but defined!) strings.For example, consider the following revisors:
... inexistent => undef, # this removes inexistent from $env set_but_awww => 'Foo: [% env:inexistent %]', not_set_at_all => { value => 'Foo: [% env:inexistent %]', require_all => 1, }, ...
As a final result,
$env->{set_but_empty}
ends up being present with valueFoo:
, while$env->{not_set_at_all}
is not set or deleted if present.This can be also combined with
default_key
ordefault_value
. start
stop
-
the delimiters for the expansion sections, defaulting to
[%
and%]
respectively (or whatever option was set inopts
at object creation). You can override them with any non-empty string. value
-
the template for the value.
Templates
Both the key and the value of a revisor are templates. They are initially parsed (during prepare_app
) and later expanded when needed (i.e. during call
).
The parsing verifies that the template adheres to the "Template rules"; the expansion is explained in section "Expansion".
Template rules
Templates are a sequence of plain text and variable expansion sections. The latter ones are delimited by a start and stop character sequence. So, for example, with the default start and stop markers the following text:
Foo [% ENV:BAR %] baz
is interpreted as:
plain text 'Foo '
expansion section ' ENV:BAR '
plain text ' baz'
Plain text sections can contain whatever character sequences, except (unescaped) start for a variable expansion section. If you want to include a start sequence, prepend it with an escape sequence (defaulting to a single backslash), like this:
Foo \[% ENV:BAR %] baz
is interpreted as:
plain text 'Foo \[% ENV:BAR %] baz'
The escape just makes the character immediately following it be ignored during parsing, which happens in the expansion sections too. So, suppose that you have a variable whose name contains the end sequence, you can still use it like this:
Foo [% env:bar \%] %] baz
is interpreted as:
plain text 'Foo '
expansion section ' env:bar \%] '
plain text ' baz'
After dividing the input template into sections, the plain text sections are just unescaped, while the expansion sections are futher analyzed:
the section string is trimmed while still honoring escape characters (i.e. escaped trailing spaces are kept, even if it can sound crazy);
then it is unescaped;
then it is split into two components separated by a colon, checking that the first part is either
ENV
orenv
(the source for the expansion) and the second is the name of the item inside the source.
Example:
' ENV:FOO\ \ '
trimmed to --> 'ENV:FOO\ \ '
unescaped to --> 'ENV:FOO '
split to --> 'ENV', 'FOO '
In the example, the expansion section will be used to get the value of item FOO
(with two trailing spaces) from %ENV
.
You can set different start, stop and escape sequences by:
setting options
start
,stop
andesc
(respectively) in configuration hashopts
in the constructor, orsetting options
start
,stop
andesc
(respectively) in the revisor definition (this takes precedence with respect to the ones in theopts
for the object, of course).
Expansion
While parsing happens once at the beginning (during phase prepare_app
), usage of a parsed template happens at call
time, i.e. at every request hitting the plugin.
The first time the request comes, the parsed template is evaluated according to what described below. Depending on the value of cache
(which can be set both in opts
for the constructor, and in each revisor singularly), this value might be reused for following calls (providing better performance) or computed each time (providing greater flexibility to cope with changing inputs). Caching is enabled by default, assuming that most of the times you will just want to get values from an unchanging environment; if you need to do fancier things, though, you can disable it altogether (setting option cache
to a false value in the constructor parameters) or for each single revisor that needs special attention.
During expansion, text parts are passed verbatim, while expansion sections take the value from either %ENV
or $env
depending on the expansion section itself. If the corresponding value is not present or is undef
:
by default the empty string is used
if option
require_all
in the revisor definition is set to a (Perl) true value, the whole expansion fails and returnsundef
or whatever default value has been set indefault_key
ordefault_value
for keys and values respectively.
If the expansion above yields undef
:
if it's the expansion of a key, it is skipped;
if it's the expansion of a value, "Removing Variables" applies (i.e. the variable is not set and removed if present).
Removing Variables
In addition to setting values, you can also remove them (e.g. suppose that you are getting some headers and you want to silence them (e.g. for debugging purposes). To do this, just set the corresponding key to undef
:
...
remove_me => undef,
...
This actually works whenever the expanded value returns undef
, although this never happens by default because undef
values in the expansion are turned into empty strings:
...
will_be_empty => '[% ENV:inexistent_value %]',
...
See "Expansion" for making the above return undef
(via require_all
) and trigger the removal of will_be_empty
.
Ordering Revisors
If you plan using intermediate variables for building up complex values, you might want to switch to the array reference form of the revisor definition (see "Defining Revisors"), because the hash-based alternatives require more care.
As an example, the following will NOT do what you think:
# using plain hash way... and being BITEN HARD!
my $me = Plack::Middleware::ReviseEnv->new(
foo => 'FOO',
bar => 'Hey [% env:foo %]',
);
This is because the following array-based rendition will be used:
[
bar => 'Hey [% env:foo %]',
foo => 'FOO',
]
i.e. bar
will be eventually expanded before foo
. This is because keys are used for ordering revisors when transforming to the array-based form.
The ordering part is actually there to help you, because by default Perl does not guarantee any kind of order when you expand a hash to the list of key/value pairs. So, at least, in this case you have some guarantees!
So what can you do? You can take advantage of the full form for defining a revisor, like this:
# using plain hash way... more verbose but correct now
my $me = Plack::Middleware::ReviseEnv->new(
'1' => { key => foo => value => 'FOO' },
'2' => { key => bar => value => 'Hey [% env:foo %]'},
);
The hash keys 1
and 2
will be used to order revisors, so they are set correctly now:
[
'1' => { key => foo => value => 'FOO' },
'2' => { key => bar => value => 'Hey [% env:foo %]'},
]
Note that the revisor definitions already contain a key
field, so neither 1
nor 2
will be used to override this field, which is the same as the following array form:
[
{ key => foo => value => 'FOO' },
{ key => bar => value => 'Hey [% env:foo %]'},
]
i.e. what you were after in the first place.
Takeaway: if you can, always use the array-based form!
METHODS
The following methods are implemented as part of the interface for a Plack middleware. Although you can override them... there's probably little sense in doing this!
- call
- prepare_app
Methods described in the following subsections can be overridden or used in derived classes, with the exception of "new".
escaped_index
my $i = $obj->escaped_index($template, $str, $esc, $pos);
Low-level method for finding the first unescaped occurrence of $str
inside $template
, starting from $pos
and considering $esc
as the escape sequence. Returns -1 if the search is unsuccessful, otherwise the index value in the string (indexes start from 0), exactly as CORE::index
.
escaped_trim
my $trimmed = $obj->escaped_trim($str, $esc);
Low-level function to trim away spaces from an escaped string. It takes care to remove all leading spaces, and all unescaped trailing spaces (there can be no "escaped leading spaces" because escape sequences cannot start with a space).
Note that trimming targets only plain horizontal spaces (ASCII 0x20).
generate_revisor
my $revisor = $obj->generate_revisor($input_definition);
Generate a revisor from an input definition.
The input definition MUST be a hash reference with fields explained in section "Defining Revisors".
Returns a new revisor.
It is used by "prepare_app" to set the list of revisors that will be used during expansion.
new
# Alternative 1, when %hash DOES NOT contain a "revisors" key
my $mw_h = Plack::Middleware::ReviseEnv->new(%hash);
# Alternative 2, "revisors" points to a hash ref
my $mw_r = Plack::Middleware::ReviseEnv->new(
revisors => $hash_or_array_ref, # array ref is PREFERRED
opts => \%hash_with_options
)
You are not supposed to use this method directly, although these are exactly the same parameters that you are supposed to pass e.g. to builder
in Plack::Builder.
The first form is quick and dirty and should be fine in most of the simple cases, like if you just want to set a few variables taking them from the environment (%ENV
) and you're fine with the default options.
The second form allows you to pass options, e.g. to change the delimiters for expansion sections, and also to define the sequence of revisors as an array reference, which is quite important if you are going to do fancy things (see "Ordering Revisors" for example).
Available opts are:
cache
-
boolean flag indicating, when true, that expanded values (see "Expansion") should be cached for later reuse in following calls. This improves performance (values are computed only the first time they are needed) at the expense of flexibility (if you have a changing
%ENV
or rely on values in$env
that depend on the specific call, caching will not take those changes). This can be overridden on a per-revisor basis.Defaults to 1 i.e. caching is enabled for all revisors by default; disable it inside a revisor that needs dynamic computing of its value.
esc
-
the escape sequence to use when parsing a template (see "Template rules"). This can be overridden on a per-revisor basis.
Defaults to a single backslash
\
. start
-
the start sequence to use when parsing a template (see "Template rules"). This can be overridden on a per-revisor basis.
Defaults to string
[%
, in Template::Toolkit spirit. stop
-
the stop sequence to use when parsing a template (see "Template rules"). This can be overridden on a per-revisor basis.
Defaults to string
%]
, in Template::Toolkit spirit.
normalize_input_structure
my $normal = $obj->normalize_input_structure($source, $defaults);
normalizes the object internally landing you with the following fields:
app
revisors
opts
where revisors
is in the array form and opts
has any missing item initialised to the corresponding default (if not already present).
parse_template
my $expandable = $obj->parse_template($template, $start, $stop, $esc);
applies the parsing explained in section "Template rules" and returns an array reference of sequences of either plain text chunks, or hash references each containing:
src
-
either
env
orENV
key
-
the key to use inside the
src
for expanding a variable.
This method is quite low-level and you have to explicitly pass the start, stop and escaping sequences, making sure they don't tread on each other.
Used by "generate_revisor".
unescape
my $text = $obj->unescape($escaped_text, $esc);
Removes the escaping sequence $esc
from $escaped_text
.
BUGS AND LIMITATIONS
Report bugs either through RT or GitHub (patches welcome).
SEE ALSO
Plack, Plack::Middleware::ForceEnv, Plack::Middleware::ReverseProxy, Plack::Middleware::SetEnvFromHeader, Plack::Middleware::SetLocalEnv.
AUTHOR
Flavio Poletti <polettix@cpan.org>
COPYRIGHT AND LICENSE
Copyright (C) 2016 by Flavio Poletti <polettix@cpan.org>
This module is free software. You can redistribute it and/or modify it under the terms of the Artistic License 2.0.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.