For me, one of the promises of PSGI and Plack is
to get away from programming with global variables in Perl, particularly
being able to modify the request object after it’s been created. Long
ago, CGI.pm replaced using a global
%FORM
hash with a “query
object”, but it essentially has been used as always-accessible
read/write data structure.
It would appear at first look that improving this might a fundamental
part of the new system: A lexical environment hashref is explicitly
passed around, as seen here:
Plack::Request->new($psgi_env);
vs the old implicit grab from the environment:
CGI->new;
You might hope to be escaping some of th action-at-a-distance badness of global
variables, like having a change to environment alter your object after the
object is created. You might like avoid having changes made to your object
avoid changing the global environment as well.
Not only does Plack::Request require an explicit environment hash to be
passed in, both nearly all methods are read-only, including a param()
method inspired by a read/write method from CGI.pm of the same name.
That’s all good.
This would all seem to speak to safety and simplicity for using Plack::Request,
but the reality turns out to be far muddier than you might hope. I encourage
you to down and run this short, safe interactive perl script which
illustrates some differences. It shows that:
- Plack::Request objects can be altered after they are created by changing the
external environment.
- Modifying a Plack::Request object can potentially alter the external environment hash
(Something which CGI.pm explicitly does not allow).
In effect, the situation with global variables is in some regards worse.
Plack::Request provides the impression that there is a move away from
action-at-distance programming, but the fundamental properties of being
affected by global changes and locally creating them are still present.
On the topic of surprising read/write behavior in Plack::Request, you may also
interested to note the behavior of query_parameters(), body_parameters() and
parameters() is not consistent in this regard. I submitted tests and
suggestion to clarify
this,
although that contribution has not yet been accepted.
Here’s the deal: The hashref returned by query_parameters() and
body_parameters() and parameters() are all read/write — subsequent calls to
the same method return the modified hashref.
However, modifying the hashes returned by body_parameters() or
query_paremeters() does not modify the hashref returned by parameters(), which
claims to be a merger of the two.
It seems that either all the return values should be read-only, ( always
returning the same values ), or if modifying them is supported then the
parameters() hash should be updated when either of the
body_parameters() or
query_parameters() hashes are updated.
Reflections
An incoming HTTP request to your server is by it’s nature read-only. It’s
analogous to a paper letter being delivered to you be postal mail.
It’s a perfect application for an immutable object object design that Yuval
Kogman eloquently advocates
for.
Plack::Request comes close to implementing the idea with mostly read-only
accessors, but falls short. The gap it leave unfortunately carries forward some
possibilities for action-at-distance cases that have been been sources of bugs
in the past. I’d like to see Plack::Request, or some alternative to it, with
the holes plugged: It should copy the input, not modify it by reference, and
parameter related methods should also return copies rather than reference to
internal data structures.
Best practice for handling UTF-8 when percent-encoding?