File Coverage

blib/lib/Kelp/Request.pm
Criterion Covered Total %
statement 50 53 94.3
branch 21 22 95.4
condition 11 13 84.6
subroutine 12 15 80.0
pod 9 9 100.0
total 103 112 91.9


line stmt bran cond sub pod time code
1             package Kelp::Request;
2              
3 21     21   645 use Kelp::Base 'Plack::Request';
  21         44  
  21         195  
4              
5 21     21   2040 use Encode;
  21         102  
  21         1650  
6 21     21   135 use Carp;
  21         89  
  21         1121  
7 21     21   130 use Try::Tiny;
  21         47  
  21         19130  
8              
9             attr -app => sub { croak "app is required" };
10              
11             # The stash is used to pass values from one route to another
12             attr stash => sub { {} };
13              
14             # The named hash contains the values of the named placeholders
15             attr named => sub { {} };
16              
17             # The name of the matched route for this request
18             attr route_name => sub { undef };
19              
20             # If you're running the web app as a proxy, use Plack::Middleware::ReverseProxy
21 1     1 1 4 sub address { $_[0]->env->{REMOTE_ADDR} }
22 0     0 1 0 sub remote_host { $_[0]->env->{REMOTE_HOST} }
23 0     0 1 0 sub user { $_[0]->env->{REMOTE_USER} }
24              
25             sub new {
26 202     202 1 742 my ( $class, %args ) = @_;
27 202         966 my $self = $class->SUPER::new( delete $args{env} );
28 202         2371 $self->{$_} = $args{$_} for keys %args;
29 202         928 return $self;
30             }
31              
32             sub is_ajax {
33 1     1 1 2 my $self = shift;
34 1 50       9 return unless my $with = $self->headers->header('X-Requested-With');
35 1         235 return $with =~ /XMLHttpRequest/i;
36             }
37              
38             sub is_json {
39 76     76 1 114 my $self = shift;
40 76 100       243 return unless $self->content_type;
41 43         304 return lc($self->content_type) =~ qr[^application/json]i;
42             }
43              
44              
45             sub param {
46 72     72 1 172 my $self = shift;
47 72   100     179 my $safe_param = $self->app->config('safe_param') // 0;
48 72         160 my $warn_message =
49             'Using "param" with argument in list context is deprecated ' .
50             'in Kelp version 1.04. See documentation of for details'
51             ;
52              
53 72 100 66     162 if ( $self->is_json && $self->app->can('json') ) {
54             my $hash = try {
55 21     21   833 $self->app->json->decode( $self->content );
56             }
57             catch {
58 1     1   241 {};
59 21         138 };
60 21 100       5103 $hash = { ref($hash), $hash } unless ref($hash) eq 'HASH';
61              
62 21 100       138 return $hash->{ $_[0] } if @_;
63 7 100       26 return $hash if !wantarray;
64 4         33 return keys %$hash;
65             }
66              
67             # unsafe method - Plack::Request::param
68 51 100 100     656 if (@_ && wantarray && !$safe_param) {
      100        
69 14         2932 carp $warn_message;
70 14         610 return $self->SUPER::param(@_);
71             }
72              
73             # safe method without calling PLack::Request::param
74 37 100       140 return $self->parameters->get($_[0]) if @_;
75 9         14 return keys %{ $self->parameters };
  9         41  
76             }
77              
78             sub cgi_param {
79 0     0 1 0 shift->SUPER::param(@_);
80             }
81              
82             sub session {
83 10     10 1 32 my $self = shift;
84 10   50     22 my $session = $self->env->{'psgix.session'}
85             // die "No Session middleware wrapped";
86              
87 10 100       55 return $session if !@_;
88              
89 8 100       17 if ( @_ == 1 ) {
90 5         9 my $value = shift;
91 5 100       27 return $session->{$value} unless ref $value;
92 1         4 return $self->env->{'psgix.session'} = $value;
93             }
94              
95 3         10 my %hash = @_;
96 3         12 $session->{$_} = $hash{$_} for keys %hash;
97 3         16 return $session;
98             }
99              
100             1;
101              
102             __END__
103              
104             =pod
105              
106             =head1 NAME
107              
108             Kelp::Request - Request class for a Kelp application
109              
110             =head1 SYNOPSIS
111              
112             my $request = Kelp::Request( app => $app, env => $env );
113              
114             =head1 DESCRIPTION
115              
116             This module provides a convenience layer on top of L<Plack::Request>. It extends
117             it to add several convenience methods.
118              
119             =head1 ATTRIBUTES
120              
121             =head2 app
122              
123             A reference to the Kelp application.
124              
125             =head2 stash
126              
127             Returns a hashref, which represents the stash of the current the request
128              
129             An all use, utility hash to use to pass information between routes. The stash
130             is a concept originally conceived by the developers of L<Catalyst>. It's a hash
131             that you can use to pass data from one route to another.
132              
133             # put value into stash
134             $self->req->stash->{username} = app->authenticate();
135             # more convenient way
136             $self->stash->{username} = app->authenticate();
137              
138             # get value from stash
139             return "Hello " . $self->req->stash->{username};
140             # more convenient way
141             return "Hello " . $self->stash('username');
142              
143             =head2 named
144              
145             This hash is initialized with the named placeholders of the path that the
146             current route is processing.
147              
148             =head2 route_name
149              
150             Contains a string name of the route matched for this request. Contains route pattern
151             if the route was not named.
152              
153             =head2 param
154              
155             I<B<Change of behavior> in version 1.04, see below for details>
156              
157             Returns the HTTP parameters of the request. This method delegates all the work
158             to L<Plack::Request/param>, except when the content type of the request is
159             C<application/json> and a JSON module is loaded. In that case, it will decode
160             the JSON body and return as follows:
161              
162             =over
163              
164             =item
165              
166             If no arguments are passed, then it will return the names of the HTTP parameters
167             when called in array contest, and a reference to the entire JSON hash when
168             called in scalar context.
169              
170             # JSON body = { bar => 1, foo => 2 }
171             my @names = $self->param; # @names = ('bar', 'foo')
172             my $json = $self->param; # $json = { bar => 1, foo => 2 }
173              
174              
175             =item
176              
177             If a single argument is passed, then the corresponding value in the JSON
178             document is returned.
179              
180             my $bar = $self->param('bar'); # $bar = 1
181              
182             =item
183              
184             If the root contents of the JSON document is not an C<HASH> (after decoding), then it will be wrapped into a hash with its reftype as a key, for example:
185              
186             { ARRAY => [...] } # when JSON contains an array as root element
187             { '' => [...] } # when JSON contains something that's not a reference
188              
189             my $array = $kelp->param('ARRAY');
190              
191             =back
192              
193             Since version I<1.04>, a new application configuration field C<safe_param> is
194             introduced that B<changes the behavior> of this method:
195              
196             =over
197              
198             =item
199              
200             Without C<safe_param>, method will produce a warning if used in list context
201             while passing the first argument, but will continue to work the same. This is
202             done to combat a very nasty and easy to make bug:
203              
204             $kelp->some_function(
205             param1 => $value,
206             param2 => $kelp->param('key'), # BUG, list context
207             );
208              
209             Since HTTP requests can accept multiple values for the same key, someone could
210             inject additional parameters to the function with the simple query, due to
211             array flattening:
212              
213             ?key=something&key=additional_hash_key&key=additional_hash_value
214              
215             =item
216              
217             With C<safe_param>, a call to C<param> with an argument (a key to fetch from
218             the parameters) will no longer return a list but always a scalar value
219             regardless of context, even if there are more than one entries of that name
220             (will then return the last one). This makes usages like the one above perfectly
221             safe.
222              
223             my @array = $kelp->param('name'); # changed, will never return more than one scalar
224              
225             =item
226              
227             Since this method has so many ways to use it, you're still B<encouraged> to use
228             other, more specific methods from L<Plack::Request>.
229              
230             =back
231              
232             You are B<strongly advised> to introduce C<safe_param> into your configuration as
233             quickly as possible. Currently, a value of C<0> is the default, meaning that
234             param will work the same as it did, but produce warnings. In no less than half
235             a year from version 1.04 the old behavior of C<param> will be removed
236             altogether, and C<safe_param> configuration will no longer cause any change in
237             behavior, allowing for its safe removal. Use L</cgi_param> if you'd like to
238             retain the old behavior regardless of security risks.
239              
240             =head2 cgi_param
241              
242             Calls C<param> in L<Plack::Request>, which is CGI.pm compatible. It is B<not
243             recommended> to use this method, unless for some reason you have to maintain
244             CGI.pm compatibility. Misusing this method can lead to bugs and security
245             vulnerabilities.
246              
247             =head2 address, remote_host, user
248              
249             These are shortcuts to the REMOTE_ADDR, REMOTE_HOST and REMOTE_USER environment
250             variables.
251              
252             if ( $self->req->address eq '127.0.0.1' ) {
253             ...
254             }
255              
256             Note: See L<Kelp::Cookbook/Deploying> for configuration required for these
257             fields when using a proxy.
258              
259             =head2 session
260              
261             Returns the Plack session hash or dies if no C<Session> middleware was included.
262              
263             sub get_session_value {
264             my $self = shift;
265             $self->session->{user} = 45;
266             }
267              
268             If called with a single argument, returns that value from the session hash:
269              
270             sub set_session_value {
271             my $self = shift;
272             my $user = $self->req->session('user');
273             # Same as $self->req->session->{'user'};
274             }
275              
276             Set values in the session using key-value pairs:
277              
278             sub set_session_hash {
279             my $self = shift;
280             $self->req->session(
281             name => 'Jill Andrews',
282             age => 24,
283             email => 'jill@perlkelp.com'
284             );
285             }
286              
287             Set values using a Hashref:
288              
289             sub set_session_hashref {
290             my $self = shift;
291             $self->req->session( { bar => 'foo' } );
292             }
293              
294             Clear the session:
295              
296             sub clear_session {
297             my $self = shift;
298             $self->req->session( {} );
299             }
300              
301             =head3 Common tasks with sessions
302              
303             =over
304              
305             =item Initialize file sessions
306              
307             In your config file:
308              
309             middleware => ['Session'],
310             middleware_init => {
311             Session => {
312             store => 'File'
313             }
314             }
315              
316             =item Delete session value
317              
318             delete $self->req->session->{'useless'};
319              
320             =item Remove all session values
321              
322             $self->req->session( {} );
323              
324             =back
325              
326             =head2 is_ajax
327              
328             Returns true if the request was called with C<XMLHttpRequest>.
329              
330             =head2 is_json
331              
332             Returns true if the request's content type was C<application/json>.
333              
334             =cut
335