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 the external
key will be ignored, otherwise it will be set corresponding to key
key. 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 to 1, which means that the value will be cached. You can
disable it either in the opts 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
or require_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 and stop (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
value Foo: , while $env->{not_set_at_all} is not set or deleted if
present.
This can be also combined with default_key or default_value.
start
stop
the delimiters for the expansion sections, defaulting to [% and %]
respectively (or whatever option was set in opts 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 or env (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 and esc (respectively) in configuration
hash opts in the constructor, or
* setting options start, stop and esc (respectively) in the revisor
definition (this takes precedence with respect to the ones in the
opts 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 returns undef or whatever
default value has been set in default_key or default_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 or ENV
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.