File Coverage

blib/lib/Web/MREST/Resource.pm
Criterion Covered Total %
statement 307 331 92.7
branch 104 156 66.6
condition 14 22 63.6
subroutine 49 50 98.0
pod 31 31 100.0
total 505 590 85.5


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2016, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33             # ------------------------
34             # This package defines how our web server handles the request-response
35             # cycle. All the "heavy lifting" is done by Web::Machine and Plack.
36             # ------------------------
37              
38             package Web::MREST::Resource;
39              
40 22     22   10044 use strict;
  22         30  
  22         522  
41 22     22   73 use warnings;
  22         26  
  22         726  
42 22     22   73 use feature "state";
  22         24  
  22         1714  
43              
44 22     22   79 use App::CELL qw( $CELL $log $meta $site );
  22         30  
  22         1771  
45 22     22   90 use App::CELL::Status;
  22         26  
  22         469  
46 22     22   61 use Data::Dumper;
  22         27  
  22         796  
47 22     22   88 use JSON;
  22         27  
  22         179  
48 22     22   2028 use Params::Validate qw( :all );
  22         28  
  22         2893  
49 22     22   8369 use Plack::Session;
  22         7141  
  22         449  
50 22     22   94 use Try::Tiny;
  22         22  
  22         915  
51 22     22   2112 use Web::MREST::InitRouter qw( $router );
  22         33  
  22         1766  
52 22     22   2641 use Web::MREST::Util qw( $JSON );
  22         37  
  22         1779  
53              
54             # methods/attributes not defined in this module will be inherited from:
55 22     22   99 use parent 'Web::Machine::Resource';
  22         28  
  22         146  
56              
57             # use this to muffle debug messages in parts of the FSM
58             my %muffle = (
59             '1' => 0,
60             '2' => 1,
61             '3' => 1,
62             '4' => 1,
63             '5' => 0,
64             );
65              
66             =head1 NAME
67              
68             App::MREST::Resource - HTTP request/response cycle
69              
70              
71              
72              
73             =head1 SYNOPSIS
74              
75             In C<YourApp/Resource.pm>:
76              
77             use parent 'Web::MREST::Resource';
78              
79             In PSGI file:
80              
81             use Web::Machine;
82              
83             Web::Machine->new(
84             resource => 'App::YourApp::Resource',
85             )->to_app;
86              
87             It is important to understand that the L<Web::Machine> object created is
88             actually blessed into C<YourApp::Resource>. The line of inheritance is:
89              
90             YourApp::Resource
91             -> Web::MREST::Resource
92             -> Web::Machine::Resource
93             -> Plack::Component
94              
95              
96              
97              
98             =head1 DESCRIPTION
99              
100             Your application should not call any of the routines in this module directly.
101             They are called by L<Web::Machine> during the course of request processing.
102             What your application can do is provide its own versions of selected routines.
103              
104              
105              
106             =head1 METHODS
107              
108              
109             =head2 Context methods
110              
111             Methods for manipulating the context, a hash where we accumulate information
112             about the request.
113              
114              
115             =head3 context
116              
117             Constructor/accessor
118              
119             =cut
120              
121             sub context {
122 1962     1962 1 1578 my $self = shift;
123 1962 100       2953 $self->{'context'} = shift if @_;
124 1962 100       2833 if ( ! $self->{'context'} ) {
125 69         110 $self->{'context'} = {};
126             }
127 1962         3450 return $self->{'context'};
128             }
129              
130              
131             =head3 push_onto_context
132              
133             Takes a hashref and "pushes" it onto C<< $self->{'context'} >> for use later
134             on in the course of processing the request.
135              
136             =cut
137              
138             sub push_onto_context {
139 445     445 1 2663 my $self = shift;
140 445         4779 my ( $hr ) = validate_pos( @_, { type => HASHREF } );
141              
142 445         1175 my $context = $self->context;
143 445         1008 foreach my $key ( keys %$hr ) {
144 894         1210 $context->{$key} = $hr->{$key};
145             }
146 445         655 $self->context( $context );
147             }
148              
149              
150             =head2 Status declaration methods
151              
152             Although L<Web::Machine> takes care of setting the HTTP response status code,
153             but when we have to override L<Web::Machine>'s value we have this "MREST
154             declared status" mechanism, which places a C<declared_status> property in
155             the context. During finalization, the HTTP status code placed in this
156             property overrides the one L<Web::Machine> came up with.
157              
158              
159             =head3 mrest_declare_status
160              
161             This method takes either a ready-made L<App::CELL::Status> object or,
162             alternatively, a PARAMHASH. In the former case, an HTTP status code can be
163             "forced" on the response by including a C<http_code> property in the
164             object. In the latter case, the following keys are recognized (and all of
165             them are optional):
166              
167             =over
168              
169             =item level
170              
171             L<App::CELL::Status> level, can be any of the strings accepted by that module.
172             Defaults to 'ERR'.
173              
174             =item code
175              
176             The HTTP status code to be applied to the response. Include this only if you
177             need to override the code set by L<Web::Machine>.
178              
179             =item explanation
180              
181             Text explaining the status - use this to comply with RFC2616. Defaults to '<NONE>'.
182              
183             =item permanent
184              
185             Boolean value for error statuses, specifies whether or not the error is
186             permanent - use this to comply with RFC2616. Defaults to true.
187              
188             =back
189              
190             =cut
191              
192             sub mrest_declare_status {
193 29     29 1 537 my $self = shift;
194 29         54 my @ARGS = @_;
195 29         78 my @caller = caller;
196 29         89 $log->debug( "Entering " . __PACKAGE__ . "::mrest_declare_status with argument(s) " .
197             Dumper( \@ARGS ) . "\nCaller: " . Dumper( \@caller ) );
198              
199             # if status gets declared multiple times, keep only the first one
200 29 50       4918 if ( exists $self->context->{'declared_status'} ) {
201 0         0 $log->notice(
202             "Cowardly refusing to overwrite previously declared status with this one: " .
203             Dumper( \@ARGS )
204             );
205 0         0 return;
206             }
207              
208 29         39 my $declared_status;
209              
210 29 100 100     151 if ( @ARGS and ref( $ARGS[0] ) eq 'App::CELL::Status' ) {
211              
212             #
213             # App::CELL::Status object was given; bend it to our needs
214             #
215 2         3 $declared_status = $ARGS[0];
216              
217             # make sure there is a payload and it is a hashref
218 2 50       4 if ( ! $declared_status->payload ) {
219 2         12 $declared_status->payload( {} );
220             }
221              
222             # if 'http_code' property given, move it to the payload
223 2 100       14 if ( my $hc = delete( $declared_status->{'http_code'} ) ) {
224 1         6 $log->debug( "mrest_declare_status: HTTP code is $hc" );
225 1         27 $declared_status->payload->{'http_code'} = $hc;
226             }
227              
228             # handle 'permanent' property
229 2 50       7 if ( my $pt = delete( $declared_status->{'permanent'} ) ) {
230 0 0       0 $declared_status->payload->{'permanent'} = $pt ? JSON::true : JSON::false;
231             } else {
232 2         6 $declared_status->payload->{'permanent'} = JSON::true;
233             }
234              
235             } else {
236              
237             #
238             # PARAMHASH was given
239             #
240 27         670 my %ARGS = validate( @ARGS, {
241             'level' => { type => SCALAR, default => 'ERR' },
242             'code' => { type => SCALAR|UNDEF, default => undef },
243             'explanation' => { type => SCALAR, default => '<NONE>' },
244             'permanent' => { type => SCALAR, default => 1 },
245             'args' => { type => ARRAYREF, optional => 1 },
246             } );
247 27 50       183 $ARGS{'args'} = [] unless $ARGS{'args'};
248             $declared_status = App::CELL::Status->new(
249             level => $ARGS{'level'},
250             code => $ARGS{'explanation'},
251             args => $ARGS{'args'},
252             payload => {
253             http_code => $ARGS{'code'}, # might be undef
254 27 100       151 permanent => ( $ARGS{'permanent'} )
255             ? JSON::true
256             : JSON::false,
257             },
258             );
259              
260             }
261              
262             # add standard properties to the payload
263 29         11222 $declared_status->payload->{'uri_path'} = $self->context->{'uri_path'};
264 29         157 $declared_status->payload->{'resource_name'} = $self->context->{'resource_name'};
265 29         117 $declared_status->payload->{'http_method'} = $self->context->{'method'};
266 29         244 $declared_status->payload->{'found_in'} = {
267             package => (caller)[0],
268             file => (caller)[1],
269             line => (caller)[2]+0,
270             };
271              
272             # the object is "done": push it onto the context
273 29         155 $self->push_onto_context( {
274             'declared_status' => $declared_status,
275             } );
276             }
277              
278              
279             =head3 mrest_declared_status_code
280              
281             Accessor method, gets just the HTTP status code (might be undef);
282             and allows setting the HTTP status code, as well, by providing an argument.
283              
284             =cut
285              
286             sub mrest_declared_status_code {
287 4     4 1 470 my ( $self, $arg ) = @_;
288 4 50       6 return unless ref( $self->context->{'declared_status'} ) eq 'App::CELL::Status';
289              
290 4         6 my $dsc = $self->context->{'declared_status'}->payload->{'http_code'};
291              
292 4 100       16 if ( $arg ) {
293 1   50     16 $log->warn( "Overriding previous declared status code ->" .
      50        
294             ( $dsc || 'undefined' ) .
295             "<- with new value -> " .
296             ( $arg || 'undefined' ) .
297             "<->" );
298 1         66 $self->context->{'declared_status'}->payload->{'http_code'} = $arg;
299 1         4 $dsc = $arg;
300             }
301              
302 4         10 return $dsc;
303             }
304              
305              
306             =head3 mrest_declared_status_explanation
307              
308             Accessor method, gets just the explanation (might be undef).
309             Does not allow changing the explanation - for this, nullify the
310             declared status and declare a new one.
311              
312             =cut
313              
314             sub mrest_declared_status_explanation {
315 2     2 1 469 my ( $self, $arg ) = @_;
316 2 50       5 return unless ref( $self->context->{'declared_status'} ) eq 'App::CELL::Status';
317              
318 2         5 return $self->context->{'declared_status'}->text;
319             }
320              
321             =head2 status_declared
322              
323             Boolean method - checks context for presence of 'declared_status' property. If
324             it is present, the value of that property is returned, just as if we had done
325             C<< $self->context->{'declared_status'} >>. Otherwise, undef (false) is returned.
326              
327             =cut
328              
329             sub status_declared {
330 141     141 1 157 my $self = shift;
331 141 100       225 if ( my $declared_status_object = $self->context->{'declared_status'} ) {
332             #$log->debug( "Declared status: " . Dumper( $declared_status_object ) );
333 34 50       91 if ( ref( $declared_status_object ) ne 'App::CELL::Status' ) {
334 0         0 die "AAAHAAHAAA! Declared status object is not an App::CELL::Status!";
335             }
336 34         82 return $declared_status_object;
337             }
338 107         225 return;
339             }
340              
341              
342             =head2 declared_status
343              
344             Synonym for C<status_declared>
345              
346             =cut
347              
348             sub declared_status {
349 3     3 1 389 my $self = shift;
350 3         4 return $self->status_declared;
351             }
352              
353              
354             =head2 nullify_declared_status
355              
356             This method nullifies any declared status that might be pending.
357              
358             =cut
359              
360             sub nullify_declared_status {
361 2     2 1 801 my $self = shift;
362 2         5 $log->debug( "Nullifying declared status: " . Dumper( $self->context->{'declared_status'} ) );
363 2         343 delete $self->context->{'declared_status'};
364 2         4 return;
365             }
366              
367              
368             =head2 FSM Part One
369              
370             The following methods override methods defined by L<Web::Machine::Resource>.
371             They correspond to what the L<Web::MREST> calls "Part One" of the FSM. To muffle
372             debug-level log messages from this part of the FSM, set $muffle{1} = 1 (above).
373              
374              
375             =head3 service_available (B13)
376              
377             This is the first method called on every incoming request.
378              
379             =cut
380              
381             sub service_available {
382 69     69 1 477925 my $self = shift;
383 69 50       583 $log->debug( "Entering " . __PACKAGE__ . "::service_available (B13)" ) unless $muffle{1};
384              
385 69 100 66     8999 $self->init_router unless ref( $router ) and $router->can( 'match' );
386              
387 69         4880 my $path = $self->request->path_info;
388 69         749 $path =~ s{^\/}{};
389 69 100       183 my $reported_path = ( $path eq '' )
390             ? 'the root resource'
391             : $path;
392 69         163 $log->info( "Incoming " . $self->request->method . " request for $reported_path" );
393 69         7469 $log->info( "Self is a " . ref( $self ) );
394 69         5918 $self->push_onto_context( {
395             'headers' => $self->request->headers,
396             'request' => $self->request,
397             'uri_path' => $path,
398             'method' => $self->request->method,
399             } );
400 69         264 return $self->mrest_service_available;
401             }
402              
403              
404             =head3 mrest_service_available
405              
406             Hook. If you overlay this and intend to return false, you should call
407             C<< $self->mrest_declare_status >> !!
408              
409             =cut
410              
411             sub mrest_service_available {
412 68     68 1 89 my $self = shift;
413 68 50       403 $log->debug( "Entering " . __PACKAGE__ . "::mrest_service_available" ) unless $muffle{1};
414 68         6901 return 1;
415             }
416              
417              
418             =head3 known_methods (B12)
419              
420             Returns the value of C<MREST_SUPPORTED_HTTP_METHODS> site parameter
421              
422             =cut
423              
424             sub known_methods {
425 68     68 1 1735 my $self = shift;
426 68 50       417 $log->debug( "Entering " . __PACKAGE__ . "::known_methods (B12)" ) unless $muffle{1};
427              
428 68         5829 my $method = $self->context->{'method'};
429 68   50     362 my $known_methods = $site->MREST_SUPPORTED_HTTP_METHODS || [ qw( GET POST PUT DELETE ) ];
430 68 50       1523 $log->debug( "The known methods are " . Dumper( $known_methods ) ) unless $muffle{1};
431              
432 68 100       10433 if ( ! grep { $method eq $_; } @$known_methods ) {
  476         502  
433 1 50       7 $log->debug( "$method is not among the known methods" ) unless $muffle{1};
434 1         89 $self->mrest_declare_status( explanation => "The request method $method is not one of the supported methods " . join( ', ', @$known_methods ) );
435             }
436 68         193 return $known_methods;
437             }
438              
439              
440             =head3 uri_too_long (B11)
441              
442             Is the URI too long?
443              
444             =cut
445              
446             sub uri_too_long {
447 67     67 1 14740 my ( $self, $uri ) = @_;
448 67 50       438 $log->debug( "Entering " . __PACKAGE__ . "::uri_too_long (B11)" ) unless $muffle{1};
449              
450 67   50     6195 my $max_len = $site->MREST_MAX_LENGTH_URI || 100;
451 67         1265 $max_len += 0;
452 67 100       255 if ( length $uri > $max_len ) {
453 1         13 $self->mrest_declare_status;
454 1         4 return 1;
455             }
456              
457 66         455 $self->push_onto_context( { 'uri' => $uri } );
458              
459 66         166 return 0;
460             }
461              
462              
463              
464             =head3 allowed_methods (B10)
465              
466             Determines which HTTP methods we recognize for this resource. We return these
467             methods in an array. If the requested method is not included in the array,
468             L<Web::Machine> will return the appropriate HTTP error code.
469              
470             RFC2616 on 405: "The response MUST include an Allow header containing a list of
471             valid methods for the requested resource." -> this is handled by Web::Machine,
472             but be aware that if the methods arrayref returned by allowed_methods does
473             not include the current request method, allow_methods gets called again.
474              
475             =cut
476              
477             sub allowed_methods {
478 66     66 1 1391 my ( $self ) = @_;
479 66 50       371 $log->debug( "Entering " . __PACKAGE__ . "::allowed_methods (B10)" ) unless $muffle{1};
480              
481             #
482             # Does the URI match a known resource?
483             #
484 66         5947 my $path = $self->context->{'uri_path'};
485 66         116 my $method = uc $self->context->{'method'};
486 66 50       462 $log->debug( "allowed_methods: path is $path, method is $method" ) unless $muffle{1};
487 66 50       5495 if ( my $match = $router->match( $path ) ) {
488             # path matches resource, but is it defined for this method?
489             #$log->debug( "match object: " . Dumper( $match ) );
490              
491 66         65877 my $resource_name = $match->route->target->{'resource_name'};
492 66 50       144 $resource_name = ( defined $resource_name )
493             ? $resource_name
494             : 'NONE_AAGH!';
495 66         218 $self->push_onto_context( {
496             'match_obj' => $match,
497             'resource_name' => $resource_name
498             } );
499 66         443 $log->info( "allowed_methods: $path matches resource ->$resource_name<-" );
500              
501 66         6886 my ( $def, @allowed_methods ) = $self->_extract_allowed_methods( $match->route->target );
502 66 100       148 if ( $def ) {
503             # method is allowed for this resource; push various values onto the context for later use
504 54         196 $self->_stash_resource_info( $match );
505 54         204 $self->_get_handler( $def );
506             } else {
507             # method not allowed for this resource
508 12         40 $self->mrest_declare_status( 'explanation' => "Method not allowed for this resource" );
509 12         51 return \@allowed_methods;
510             }
511              
512 54 50       5268 if ( $self->status_declared ) {
513             # something bad happened
514 0         0 return [];
515             }
516              
517             # success
518 54         230 return \@allowed_methods;
519              
520             }
521             # if path does not match, return an empty arrayref, which triggers a 405 status code
522 0         0 $self->mrest_declare_status( 'code' => 400, 'explanation' => "URI does not match a known resource" );
523 0         0 return [];
524             }
525              
526              
527             sub _extract_allowed_methods {
528 66     66   157 my ( $self, $target ) = @_;
529 66 50       402 $log->debug( "Entering " . __PACKAGE__ . "::_extract_allowed_methods" ) unless $muffle{1};
530             #$log->debug( "Target is: " . Dumper( $target ) );
531              
532             # ---------------------------------------------------------------
533             # FIXME: need to come up with a more reasonable way of doing this
534             # ---------------------------------------------------------------
535             #
536             # The keys of the $route->target hash are the allowed methods plus:
537             # - 'resource_name'
538             # - 'parent'
539             # - 'children'
540             # - 'documentation'
541             #
542             # So, using set theory we can say that the set of allowed methods
543             # is equal to the set of $route->target hash keys MINUS the set
544             # of keys listed above. (This is fine until someone decides to
545             # add another key to a resource definition and forgets to add it
546             # here as well.)
547             #
548             # ---------------------------------------------------------------
549              
550 66         6229 my @allowed_methods;
551 66         80 foreach my $method ( keys %{ $target } ) {
  66         249  
552 465 100       997 push( @allowed_methods, $method ) unless $method =~ m/(resource_name)|(parent)|(children)|(documentation)/;
553             }
554 66 50       482 $log->debug( "Allowed methods are " . join( ' ', @allowed_methods ) ) unless $muffle{1};
555              
556 66         5505 return ( $target->{ $self->context->{'method'} }, @allowed_methods );
557             }
558              
559              
560             sub _stash_resource_info {
561 54     54   67 my ( $self, $match ) = @_;
562 54 50       299 $log->debug( "Entering " . __PACKAGE__ . "::_stash_resource_info" ) unless $muffle{1};
563              
564             # N.B.: $uri is the base URI, not the path
565 54 50       4630 my $uri = $site->MREST_URI
566             ? $site->MREST_URI
567             : $self->request->base->as_string;
568              
569 54         7620 my $push_hash = {
570             'mapping' => $match->mapping, # mapping contains values of ':xyz' parts of path
571             'uri_base' => $uri, # base URI of the REST server
572             'components' => $match->route->components, # resource components
573             };
574 54         426 $self->push_onto_context( $push_hash );
575             #$log->debug( "allowed_methods: pushed onto context " . Dumper( $push_hash ) );
576             }
577              
578              
579             sub _get_handler {
580 54     54   66 my ( $self, $def ) = @_;
581 54 50       222 $log->debug( "Entering " . __PACKAGE__ . "::_get_handler with resource definition: " . Dumper( $def ) ) unless $muffle{1};
582              
583             # be idempotent
584 54 50       7685 if ( my $handler_from_context = $self->context->{'handler'} ) {
585 0         0 return $handler_from_context;
586             }
587              
588 54         64 my $status = 0;
589 54         47 my $handler_name;
590 54 50       252 if ( $handler_name = $def->{'handler'} ) {
591             # $handler_name is the name of a method that will hopefully be callable
592             # by doing $self->$handler_name
593 54         164 $self->push_onto_context( {
594             'handler' => $handler_name,
595             } );
596             } else {
597 0         0 $status = "No handler defined for this resource+method combination!";
598             }
599 54 50       128 if ( $status ) {
600 0         0 $self->mrest_declare_status( 'code' => '500', explanation => $status );
601 0         0 $log->err( "Leaving _get_handler with status $status" );
602             } else {
603 54         289 $log->info( "Leaving _get_handler (all green) - handler is ->$handler_name<-" );
604             }
605             }
606            
607              
608             =head3 malformed_request (B9)
609              
610             A true return value from this method aborts the FSM and triggers a "400 Bad
611             Request" response status.
612              
613             =cut
614              
615             sub malformed_request {
616 54     54 1 1117 my ( $self ) = @_;
617 54 50       517 $log->debug( "Entering " . __PACKAGE__ . "::malformed_request (B9)" ) unless $muffle{1};
618              
619             # we examing the request body on PUT and POST only (FIXME: make this configurable)
620 54         4883 my $method = $self->context->{'method'};
621 54 100       619 return 0 unless $method =~ m/^(PUT)|(POST)$/;
622             #$log->debug( "Method is $method" );
623              
624             # get content-type and content-length
625 23         75 my $content_type = $self->request->headers->header('Content-Type');
626 23 100       585 $content_type = '<NONE>' unless defined( $content_type );
627 23         59 my $content_length = $self->request->headers->header('Content-Length');
628 23 50       383 $content_length = '<NONE>' unless defined( $content_length );
629             #$log->debug( "Content-Type: $content_type, Content-Length: $content_length" );
630              
631             # no Content-Type and/or no Content-Length, yet request body present ->
632             # clearly a violation
633 23 100       56 if ( $self->request->content ) {
634 13 100 66     2934 if ( $content_type eq '<NONE>' or $content_length eq '<NONE>' ) {
635 2         11 $self->mrest_declare_status(
636             explanation => 'no Content-Type and/or no Content-Length, yet request body present'
637             );
638 2         5 return 1;
639             }
640             }
641              
642 21         2196 $self->push_onto_context( { 'headers' =>
643             {
644             'content-length' => $content_length,
645             'content-type' => $content_type,
646             }
647             } );
648              
649 21         112 return $self->mrest_malformed_request;
650             }
651              
652              
653             =head3 mrest_malformed_request
654              
655             Hook
656              
657             =cut
658              
659             sub mrest_malformed_request {
660 21     21 1 28 my $self = shift;
661 21 50       154 $log->debug( "Entering " . __PACKAGE__ . "::mrest_malformed_request (B9)" ) unless $muffle{1};
662            
663 21         2092 return 0;
664             }
665              
666              
667             =head3 is_authorized (B8)
668              
669             Authentication method - should be implemented in the application.
670              
671             =cut
672              
673             sub is_authorized {
674 48     48 1 1884 my $self = shift;
675 48 50       353 $log->debug( "Entering " . __PACKAGE__ . "::is_authorized (B8)" ) unless $muffle{1};
676 48         4045 return 1;
677             }
678              
679              
680             =head3 forbidden (B7)
681              
682             Authorization method - should be implemented in the application.
683              
684             =cut
685              
686             sub forbidden {
687 44     44 1 905 my $self = shift;
688 44 50       249 $log->debug( "Entering " . __PACKAGE__ . "::forbidden (B7)" ) unless $muffle{1};
689 44         3624 return 0;
690             }
691              
692              
693             =head3 valid_content_headers (B6)
694              
695             Receives a L<Hash::MultiValue> object containing all the C<Content-*> headers
696             in the request. Checks these against << $site->MREST_VALID_CONTENT_HEADERS >>,
697             returns false if the check fails, true if it passes.
698              
699             =cut
700              
701             sub valid_content_headers {
702 44     44 1 5661 my ( $self, $content_headers ) = @_;
703 44 50       274 $log->debug( "Entering " . __PACKAGE__ . "::valid_content_headers (B6)" ) unless $muffle{1};
704 44 50       4106 $log->debug( "Content headers: " . join( ', ', keys( %$content_headers ) ) ) unless $muffle{1};
705              
706             # get site param
707 44         3682 my $valid_content_headers = $site->MREST_VALID_CONTENT_HEADERS;
708 44 50       867 die "AAAAAHAHAAAAAHGGGG!! \$valid_content_headers is not an array reference!!"
709             unless ref( $valid_content_headers ) eq 'ARRAY';
710              
711             # check these content headers against it
712 44         90 my $valids = _b6_make_hash( $valid_content_headers );
713 44         95 foreach my $content_header ( keys( %$content_headers ) ) {
714 86 100       165 if ( not exists $valids->{$content_header} ) {
715 2         7 $self->mrest_declare_status( explanation =>
716             "Content header ->$content_header<- not found in MREST_VALID_CONTENT_HEADERS"
717             );
718 2         8 return 0;
719             }
720             }
721 42         139 return 1;
722             }
723              
724             sub _b6_make_hash {
725 44     44   53 my $ar = shift;
726 44         43 my %h;
727 44         86 foreach my $chn ( @$ar ) {
728 308 100       549 $chn = 'Content-' . $chn unless $chn =~ m/^Content-/;
729 308         414 $h{ $chn } = '';
730             }
731 44         134 return \%h;
732             }
733              
734              
735             =head3 known_content_type (B5)
736              
737             The assumption for C<PUT> and C<POST> requests is that they might have an
738             accompanying request entity, the type of which should be declared via a
739             C<Content-Type> header. If the content type is not recognized by the
740             application, return false from this method to trigger a "415 Unsupported Media
741             Type" response.
742              
743             The basic content-types (major portions only) accepted by the application
744             should be listed in C<< $site->MREST_SUPPORTED_CONTENT_TYPES >>. Override this
745             method if that's not good by you.
746              
747             =cut
748              
749             sub known_content_type {
750 42     42 1 1759 my ( $self, $content_type ) = @_;
751 42 50       244 $log->debug( "Entering " . __PACKAGE__ . "::known_content_type (B5)" ) unless $muffle{1};
752              
753 42 50       3750 return 1 if not $content_type;
754              
755             # if $content_type is a blessed object, deal with that
756 42         891 my $ct_isa = ref( $content_type );
757 42 50       83 if ( $ct_isa ) {
758 42 50       275 $log->debug( "\$content_type is a ->$ct_isa<-" ) unless $muffle{1};
759 42 50       3473 if ( $ct_isa ne 'HTTP::Headers::ActionPack::MediaType' ) {
760 0         0 $self->mrest_declare_status( code => '500',
761             explanation => "Bad content_type class ->$ct_isa<-" );
762 0         0 return 0;
763             }
764 42         128 $content_type = $content_type->type; # convert object to string
765             }
766              
767 42 50       391 $log->debug( "Content type of this request is ->$content_type<-" ) unless $muffle{1};
768              
769             # push it onto context
770 42         3369 $self->context->{'content_type'} = $content_type;
771              
772             # convert supported content types into a hash for easy lookup
773 42         53 my %types = map { ( $_ => '' ); } @{ $site->MREST_SUPPORTED_CONTENT_TYPES };
  42         792  
  42         199  
774 42 100       112 if ( exists $types{ $content_type } ) {
775 41         166 $log->info( "$content_type is supported" );
776 41         3625 return 1;
777             }
778 1         5 $self->mrest_declare_status( explanation => "Content type ->$content_type<- is not supported" );
779 1         4 return 0;
780             }
781              
782              
783             =head3 valid_entity_length (B4)
784              
785             Called by Web::Machine with one argument: the length of the request
786             body. Return true or false.
787              
788             =cut
789              
790             sub valid_entity_length {
791 41     41 1 847 my ( $self, $body_len ) = @_;
792 41         136 state $max_len = $site->MREST_MAX_LENGTH_REQUEST_ENTITY;
793 41 50       476 $log->debug( "Entering " . __PACKAGE__ . "::valid_entity_length, maximum request entity length is $max_len" ) unless $muffle{1};
794 41   100     3754 $body_len = $body_len || 0;
795 41         211 $log->info( "Request body is $body_len bytes long" );
796            
797 41 100       3567 if ( $body_len > $max_len ) {
798 1         10 $self->mrest_declare_status( explanation => "Request body is $body_len bytes long, which exceeds maximum length set in \$site->MREST_MAX_LENGTH_REQUEST_ENTITY" );
799 1         3 return 0;
800             }
801 40         87 return 1;
802             }
803              
804              
805             =head3 charsets_provided
806              
807             This method causes L<Web::Machine> to encode the response body (if any) in
808             UTF-8.
809              
810             =cut
811              
812             sub charsets_provided {
813 78     78 1 950 return [ qw( UTF-8 ) ];
814             }
815              
816              
817             #=head3 default_charset
818             #
819             #Really use UTF-8 all the time.
820             #
821             #=cut
822             #
823             #sub default_charset { 'utf8'; }
824              
825              
826             =head2 FSM Part Two (Content Negotiation)
827              
828             See L<Web::MREST::Entity>.
829              
830              
831             =head2 FSM Part Three (Resource Existence)
832              
833              
834             =head2 resource_exists (G7)
835              
836             The initial check for resource existence is the URI-to-resource mapping,
837             which has already taken place in C<allowed_methods>. Having made it to here,
838             we know that was successful.
839              
840             So, what we do here is call the handler function, which is expected to
841             return an L<App::CELL::Status> object. How this status is interpreted is
842             left up to the application: we pass the status object to the
843             C<mrest_resource_exists> method, which should return either true or false.
844              
845             For GET and POST, failure means 404 by default, but can be overrided
846             by calling C<mrest_declare_status> from within C<mrest_resource_exists>.
847              
848             For PUT, success means this is an update operation and failure means insert.
849              
850             For DELETE, failure means "202 Accepted" - i.e. a request to delete a
851             resource that doesn't exist is accepted, but nothing actually happens.
852              
853             =cut
854              
855             sub resource_exists {
856 39     39 1 1271 my $self = shift;
857 39         176 $log->debug( "Entering " . __PACKAGE__ . "::resource_exists" );
858             #$log->debug( "Context is " . Dumper( $self->context ) );
859            
860             # no handler is grounds for 500
861 39 50       3431 if ( not exists $self->context->{'handler'} ) {
862 0         0 $self->mrest_declare_status( code => '500',
863             explanation => 'AAAAAAAAAAGAHH!!! In resource_exists, no handler/mapping on context' );
864 0         0 return 0;
865             }
866              
867             #
868             # run handler (first pass) and push result onto context
869             #
870 39         69 my $handler = $self->context->{'handler'};
871 39         185 $log->debug( "resource_exists: Calling resource handler $handler for the first time" );
872 39         3243 my $bool;
873             try {
874 39     39   1081 $bool = $self->$handler(1);
875             } catch {
876 0     0   0 $self->mrest_declare_status( code => 500, explanation => $_ );
877 0         0 $bool = 0;
878 39         257 };
879 39         527 $self->push_onto_context( { 'resource_exists' => $bool } );
880 39 100       129 return 1 if $bool;
881              
882             # Application thinks the resource doesn't exist. Return value will be
883             # 0. For GET and DELETE, this should trigger 404 straightaway: make
884             # sure the status is declared so we don't send back a bare response.
885             # For POST, the next method will be 'allow_missing_post'.
886             # For PUT, it will be ...?...
887              
888 5 50       8 if ( not $self->status_declared ) {
889 5         8 my $method = $self->context->{'method'};
890 5         6 my $explanation = "Received request for non-existent resource";
891 5 100       18 if ( $method eq 'GET' ) {
    100          
892             # 404 will be assigned by Web::Machine
893 2         15 $self->mrest_declare_status( 'explanation' => $explanation );
894             } elsif ( $method eq 'DELETE' ) {
895             # for DELETE, Web::Machine would ordinarily return a 202 so
896             # we override that
897 1         3 $self->mrest_declare_status( 'code' => 404, 'explanation' => $explanation );
898             }
899             }
900 5         36 return 0;
901             }
902              
903              
904             =head2 allow_missing_post
905              
906             If the application wishes to allow POST to a non-existent resource, this
907             method will need to be overrided.
908              
909             =cut
910              
911             sub allow_missing_post {
912 1     1 1 93 my ( $self ) = @_;
913 1         6 $log->debug( "Entering " . __PACKAGE__ . "::allow_missing_post" );
914              
915             # we do not allow POST to a non-existent resource, so we declare 404
916 1 50       85 $self->mrest_declare_status( 'code' => 404, explanation =>
917             'Detected attempt to POST to non-existent resource' ) unless $self->status_declared;
918              
919 1         3 return 0;
920             }
921              
922              
923             =head2 post_is_create
924              
925             =cut
926              
927             sub post_is_create {
928 10     10 1 1372 my $self = shift;
929 10         48 $log->debug( "Entering " . __PACKAGE__ . "::post_is_create" );
930            
931 10         965 return $self->mrest_post_is_create;
932             }
933              
934              
935             =head2 mrest_post_is_create
936              
937             Looks for a 'post_is_create' property in the context and returns
938             1 or 0, as appropriate.
939              
940             =cut
941              
942             sub mrest_post_is_create {
943 10     10 1 14 my $self = shift;
944 10         46 $log->debug( "Entering " . __PACKAGE__ . "::mrest_post_is_create" );
945              
946 10         817 my $pic = $self->context->{'post_is_create'};
947 10 100       30 if ( ! defined( $pic ) ) {
948 9         65 $log->error( "post_is_create property is missing; defaults to false" );
949 9         866 return 0;
950             }
951 1 50       3 if ( $pic ) {
952 1         4 $log->info( "post_is_create property is true" );
953 1         87 return 1;
954             }
955 0         0 $log->info( "post_is_create property is false" );
956 0         0 return 0;
957             }
958              
959              
960             =head2 create_path
961              
962             =cut
963              
964             sub create_path {
965 1     1 1 10 my $self = shift;
966 1         5 $log->debug( "Entering " . __PACKAGE__ . "::create_path" );
967              
968             # if there is a declared status, return a dummy value
969 1 50       80 return "DUMMY" if $self->status_declared;
970              
971 1         6 return $self->mrest_create_path;
972             }
973              
974              
975             =head2 mrest_create_path
976              
977             This should always return _something_ (never undef)
978              
979             =cut
980              
981             sub mrest_create_path {
982 1     1 1 2 my $self = shift;
983 1         4 $log->debug( "Entering " . __PACKAGE__ . "::mrest_create_path" );
984              
985 1         80 my $create_path = $self->context->{'create_path'};
986 1 50       3 if ( ! defined( $create_path ) ) {
987 0         0 $site->mrest_declare_status( code => 500,
988             explanation => "Post is create, but create_path missing in handler status" );
989 0         0 return 'ERROR';
990             }
991 1         4 $log->debug( "Returning create_path " . Dumper( $create_path ) );
992 1         118 return $create_path;
993             }
994              
995              
996             =head2 create_path_after_handler
997              
998             This is set to true so we can set C<< $self->context->{'create_path'} >> in the handler.
999              
1000             =cut
1001              
1002 2     2 1 14 sub create_path_after_handler { 1 }
1003              
1004              
1005              
1006             =head2 process_post
1007              
1008             This is where we construct responses to POST requests that do not create
1009             a new resource. Since we expect our resource handlers to "do the needful",
1010             all we need to do is call the resource handler for pass two.
1011              
1012             The return value should be a Web::Machine/HTTP status code
1013             like, e.g., \200 - this ensures that Web::Machine does not attempt to
1014             encode the response body, as in our case this would introduce a double-
1015             encoding bug.
1016              
1017             =cut
1018              
1019             sub process_post {
1020 9     9 1 63 my $self = shift;
1021 9         43 $log->debug("Entering " . __PACKAGE__ . "::process_post" );
1022              
1023             # Call the request handler. This way is bad, because it ignores any
1024             # 'Accept' header provided in the request by the user agent. However, until
1025             # Web::Machine is patched we have no other way of knowing the request
1026             # handler's name so we have to hard-code it like this.
1027             #$self->_load_request_entity;
1028             #my $status = $self->mrest_process_request;
1029             #return $status if ref( $status ) eq 'SCALAR';
1030             #
1031             #return \200 if $self->context->{'handler_status'}->ok;
1032             #
1033             # if the handler status is not ok, there SHOULD be a declared status
1034             #return $self->mrest_declared_status_code || \500;
1035              
1036 9         804 my $status = $self->mrest_process_request;
1037 9         33 $log->debug( "Handler returned: " . Dumper( $status ) );
1038 9         1167 return $status;
1039             }
1040              
1041              
1042             =head2 delete_resource
1043              
1044             This method is called on DELETE requests and is supposed to tell L<Web::Machine>
1045             whether or not the DELETE operation was enacted. In our case, we call the
1046             resource handler (pass two).
1047              
1048             =cut
1049              
1050             sub delete_resource {
1051 1     1 1 117 my $self = shift;
1052 1         5 $log->debug("Entering " . __PACKAGE__ . "::delete_resource");
1053              
1054 1         87 my $status = $self->mrest_generate_response;
1055 1 50 33     7 return 0 if ref( $status ) eq 'SCALAR' or $self->context->{'handler_status'}->not_ok;
1056 1         11 return 1;
1057             };
1058              
1059              
1060              
1061             =head2 finish_request
1062              
1063             This overrides the Web::Machine method of the same name, and is called just
1064             before the final response is constructed and sent. We use it for adding certain
1065             headers in every response.
1066              
1067             =cut
1068              
1069             sub finish_request {
1070 69     69 1 33273 my ( $self, $metadata ) = @_;
1071 69         220 state $http_codes = $site->MREST_HTTP_CODES;
1072              
1073 69         514 $log->debug( "Entering " . __PACKAGE__ . "::finish_request with metadata: " . Dumper( $metadata ) );
1074              
1075 69 50       10550 if ( ! $site->MREST_CACHE_ENABLED ) {
1076             #
1077             # tell folks not to cache
1078             #
1079 69         1400 $self->response->header( 'Cache-Control' => $site->MREST_CACHE_CONTROL_HEADER );
1080 69         2692 $self->response->header( 'Pragma' => 'no-cache' );
1081             }
1082              
1083             #
1084             # when Web::Machine catches an exception, it sends us the text in the
1085             # metadata -- in practical terms, this means: if the metadata contains an
1086             # 'exception' property, something died somewhere
1087             #
1088 69 50       1360 if ( $metadata->{'exception'} ) {
1089 0         0 my $exception = $metadata->{'exception'};
1090 0         0 $exception =~ s/\n//g;
1091 0         0 $self->mrest_declare_status( code => '500', explanation => $exception );
1092             }
1093              
1094             #
1095             # if there is a declared status, we assume that it contains the entire
1096             # intended response and clobber $self->response->content with it
1097             #
1098 69 100       156 if ( $self->status_declared ) {
1099 26         48 my $declared_status = $self->context->{'declared_status'};
1100 26         75 $log->debug( "finish_request: declared status is " . Dumper( $declared_status ) );
1101 26 100       6031 if ( ! $declared_status->payload->{'http_code'} ) {
1102 23         165 $declared_status->payload->{'http_code'} = $self->response->code;
1103             } else {
1104 3         25 $self->response->code( $declared_status->payload->{'http_code'} );
1105             }
1106 26         308 my $json = $JSON->encode( $declared_status->expurgate );
1107 26         4603 $self->response->content( $json );
1108 26         245 $self->response->header( 'content-length' => length( $json ) );
1109             }
1110              
1111             # The return value is ignored, so any effect of this method must be by
1112             # modifying the response.
1113 69         736 $log->debug( "Response finalized: " . Dumper( $self->response ) );
1114 69         11699 return;
1115             }
1116              
1117             1;
1118