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   8869 use strict;
  22         52  
  22         612  
41 22     22   122 use warnings;
  22         48  
  22         566  
42 22     22   130 use feature "state";
  22         70  
  22         1911  
43              
44 22     22   144 use App::CELL qw( $CELL $log $meta $site );
  22         51  
  22         2135  
45 22     22   152 use App::CELL::Status;
  22         56  
  22         628  
46 22     22   119 use Data::Dumper;
  22         50  
  22         893  
47 22     22   143 use JSON;
  22         50  
  22         189  
48 22     22   2487 use Params::Validate qw( :all );
  22         54  
  22         3248  
49 22     22   5967 use Plack::Session;
  22         9262  
  22         619  
50 22     22   143 use Try::Tiny;
  22         44  
  22         1095  
51 22     22   2060 use Web::MREST::InitRouter qw( $router );
  22         60  
  22         2176  
52 22     22   2721 use Web::MREST::Util qw( $JSON );
  22         55  
  22         1910  
53              
54             # methods/attributes not defined in this module will be inherited from:
55 22     22   141 use parent 'Web::Machine::Resource';
  22         175  
  22         189  
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 3302 my $self = shift;
123 1962 100       4275 $self->{'context'} = shift if @_;
124 1962 100       4087 if ( ! $self->{'context'} ) {
125 69         215 $self->{'context'} = {};
126             }
127 1962         5132 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 4482 my $self = shift;
140 445         6275 my ( $hr ) = validate_pos( @_, { type => HASHREF } );
141              
142 445         1954 my $context = $self->context;
143 445         1605 foreach my $key ( keys %$hr ) {
144 894         2016 $context->{$key} = $hr->{$key};
145             }
146 445         1060 $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 753 my $self = shift;
194 29         98 my @ARGS = @_;
195 29         120 my @caller = caller;
196 29         151 $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       7798 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         67 my $declared_status;
209              
210 29 100 100     223 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         4 $declared_status = $ARGS[0];
216              
217             # make sure there is a payload and it is a hashref
218 2 50       7 if ( ! $declared_status->payload ) {
219 2         15 $declared_status->payload( {} );
220             }
221              
222             # if 'http_code' property given, move it to the payload
223 2 100       19 if ( my $hc = delete( $declared_status->{'http_code'} ) ) {
224 1         8 $log->debug( "mrest_declare_status: HTTP code is $hc" );
225 1         47 $declared_status->payload->{'http_code'} = $hc;
226             }
227              
228             # handle 'permanent' property
229 2 50       10 if ( my $pt = delete( $declared_status->{'permanent'} ) ) {
230 0 0       0 $declared_status->payload->{'permanent'} = $pt ? JSON::true : JSON::false;
231             } else {
232 2         10 $declared_status->payload->{'permanent'} = JSON::true;
233             }
234              
235             } else {
236              
237             #
238             # PARAMHASH was given
239             #
240 27         1053 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       318 $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       271 permanent => ( $ARGS{'permanent'} )
255             ? JSON::true
256             : JSON::false,
257             },
258             );
259              
260             }
261              
262             # add standard properties to the payload
263 29         21254 $declared_status->payload->{'uri_path'} = $self->context->{'uri_path'};
264 29         251 $declared_status->payload->{'resource_name'} = $self->context->{'resource_name'};
265 29         220 $declared_status->payload->{'http_method'} = $self->context->{'method'};
266 29         378 $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         259 $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 886 my ( $self, $arg ) = @_;
288 4 50       10 return unless ref( $self->context->{'declared_status'} ) eq 'App::CELL::Status';
289              
290 4         7 my $dsc = $self->context->{'declared_status'}->payload->{'http_code'};
291              
292 4 100       24 if ( $arg ) {
293 1   50     19 $log->warn( "Overriding previous declared status code ->" .
      50        
294             ( $dsc || 'undefined' ) .
295             "<- with new value -> " .
296             ( $arg || 'undefined' ) .
297             "<->" );
298 1         89 $self->context->{'declared_status'}->payload->{'http_code'} = $arg;
299 1         6 $dsc = $arg;
300             }
301              
302 4         13 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 865 my ( $self, $arg ) = @_;
316 2 50       7 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 508 my $self = shift;
331 141 100       401 if ( my $declared_status_object = $self->context->{'declared_status'} ) {
332             #$log->debug( "Declared status: " . Dumper( $declared_status_object ) );
333 34 50       125 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         128 return $declared_status_object;
337             }
338 107         336 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 627 my $self = shift;
350 3         7 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 1317 my $self = shift;
362 2         6 $log->debug( "Nullifying declared status: " . Dumper( $self->context->{'declared_status'} ) );
363 2         345 delete $self->context->{'declared_status'};
364 2         6 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 589336 my $self = shift;
383 69 50       1189 $log->debug( "Entering " . __PACKAGE__ . "::service_available (B13)" ) unless $muffle{1};
384              
385 69 100 66     17559 $self->init_router unless ref( $router ) and $router->can( 'match' );
386              
387 69         6118 my $path = $self->request->path_info;
388 69         1173 $path =~ s{^\/}{};
389 69 100       356 my $reported_path = ( $path eq '' )
390             ? 'the root resource'
391             : $path;
392 69         254 $log->info( "Incoming " . $self->request->method . " request for $reported_path" );
393 69         12801 $log->info( "Self is a " . ref( $self ) );
394 69         9864 $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         395 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 168 my $self = shift;
413 68 50       647 $log->debug( "Entering " . __PACKAGE__ . "::mrest_service_available" ) unless $muffle{1};
414 68         11359 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 2765 my $self = shift;
426 68 50       648 $log->debug( "Entering " . __PACKAGE__ . "::known_methods (B12)" ) unless $muffle{1};
427              
428 68         10495 my $method = $self->context->{'method'};
429 68   50     633 my $known_methods = $site->MREST_SUPPORTED_HTTP_METHODS || [ qw( GET POST PUT DELETE ) ];
430 68 50       2674 $log->debug( "The known methods are " . Dumper( $known_methods ) ) unless $muffle{1};
431              
432 68 100       16582 if ( ! grep { $method eq $_; } @$known_methods ) {
  476         1068  
433 1 50       9 $log->debug( "$method is not among the known methods" ) unless $muffle{1};
434 1         130 $self->mrest_declare_status( explanation => "The request method $method is not one of the supported methods " . join( ', ', @$known_methods ) );
435             }
436 68         277 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 26344 my ( $self, $uri ) = @_;
448 67 50       664 $log->debug( "Entering " . __PACKAGE__ . "::uri_too_long (B11)" ) unless $muffle{1};
449              
450 67   50     10691 my $max_len = $site->MREST_MAX_LENGTH_URI || 100;
451 67         1997 $max_len += 0;
452 67 100       393 if ( length $uri > $max_len ) {
453 1         26 $self->mrest_declare_status;
454 1         11 return 1;
455             }
456              
457 66         744 $self->push_onto_context( { 'uri' => $uri } );
458              
459 66         390 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 2385 my ( $self ) = @_;
479 66 50       553 $log->debug( "Entering " . __PACKAGE__ . "::allowed_methods (B10)" ) unless $muffle{1};
480              
481             #
482             # Does the URI match a known resource?
483             #
484 66         10524 my $path = $self->context->{'uri_path'};
485 66         177 my $method = uc $self->context->{'method'};
486 66 50       635 $log->debug( "allowed_methods: path is $path, method is $method" ) unless $muffle{1};
487 66 50       10231 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         66203 my $resource_name = $match->route->target->{'resource_name'};
492 66 50       273 $resource_name = ( defined $resource_name )
493             ? $resource_name
494             : 'NONE_AAGH!';
495 66         401 $self->push_onto_context( {
496             'match_obj' => $match,
497             'resource_name' => $resource_name
498             } );
499 66         828 $log->info( "allowed_methods: $path matches resource ->$resource_name<-" );
500              
501 66         12076 my ( $def, @allowed_methods ) = $self->_extract_allowed_methods( $match->route->target );
502 66 100       241 if ( $def ) {
503             # method is allowed for this resource; push various values onto the context for later use
504 54         338 $self->_stash_resource_info( $match );
505 54         333 $self->_get_handler( $def );
506             } else {
507             # method not allowed for this resource
508 12         77 $self->mrest_declare_status( 'explanation' => "Method not allowed for this resource" );
509 12         84 return \@allowed_methods;
510             }
511              
512 54 50       8639 if ( $self->status_declared ) {
513             # something bad happened
514 0         0 return [];
515             }
516              
517             # success
518 54         356 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   539 my ( $self, $target ) = @_;
529 66 50       635 $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         10261 my @allowed_methods;
551 66         154 foreach my $method ( keys %{ $target } ) {
  66         437  
552 465 100       1489 push( @allowed_methods, $method ) unless $method =~ m/(resource_name)|(parent)|(children)|(documentation)/;
553             }
554 66 50       830 $log->debug( "Allowed methods are " . join( ' ', @allowed_methods ) ) unless $muffle{1};
555              
556 66         9706 return ( $target->{ $self->context->{'method'} }, @allowed_methods );
557             }
558              
559              
560             sub _stash_resource_info {
561 54     54   152 my ( $self, $match ) = @_;
562 54 50       628 $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       8366 my $uri = $site->MREST_URI
566             ? $site->MREST_URI
567             : $self->request->base->as_string;
568              
569 54         13539 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         710 $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   160 my ( $self, $def ) = @_;
581 54 50       382 $log->debug( "Entering " . __PACKAGE__ . "::_get_handler with resource definition: " . Dumper( $def ) ) unless $muffle{1};
582              
583             # be idempotent
584 54 50       12122 if ( my $handler_from_context = $self->context->{'handler'} ) {
585 0         0 return $handler_from_context;
586             }
587              
588 54         149 my $status = 0;
589 54         109 my $handler_name;
590 54 50       208 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         250 $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       198 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         404 $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 2094 my ( $self ) = @_;
617 54 50       446 $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         8343 my $method = $self->context->{'method'};
621 54 100       345 return 0 unless $method =~ m/^(PUT)|(POST)$/;
622             #$log->debug( "Method is $method" );
623              
624             # get content-type and content-length
625 23         102 my $content_type = $self->request->headers->header('Content-Type');
626 23 100       1134 $content_type = '<NONE>' unless defined( $content_type );
627 23         82 my $content_length = $self->request->headers->header('Content-Length');
628 23 50       726 $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       76 if ( $self->request->content ) {
634 13 100 66     5045 if ( $content_type eq '<NONE>' or $content_length eq '<NONE>' ) {
635 2         15 $self->mrest_declare_status(
636             explanation => 'no Content-Type and/or no Content-Length, yet request body present'
637             );
638 2         10 return 1;
639             }
640             }
641              
642 21         3391 $self->push_onto_context( { 'headers' =>
643             {
644             'content-length' => $content_length,
645             'content-type' => $content_type,
646             }
647             } );
648              
649 21         183 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 53 my $self = shift;
661 21 50       238 $log->debug( "Entering " . __PACKAGE__ . "::mrest_malformed_request (B9)" ) unless $muffle{1};
662            
663 21         3404 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 3238 my $self = shift;
675 48 50       417 $log->debug( "Entering " . __PACKAGE__ . "::is_authorized (B8)" ) unless $muffle{1};
676 48         6991 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 1492 my $self = shift;
688 44 50       391 $log->debug( "Entering " . __PACKAGE__ . "::forbidden (B7)" ) unless $muffle{1};
689 44         6382 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 10353 my ( $self, $content_headers ) = @_;
703 44 50       408 $log->debug( "Entering " . __PACKAGE__ . "::valid_content_headers (B6)" ) unless $muffle{1};
704 44 50       7027 $log->debug( "Content headers: " . join( ', ', keys( %$content_headers ) ) ) unless $muffle{1};
705              
706             # get site param
707 44         6726 my $valid_content_headers = $site->MREST_VALID_CONTENT_HEADERS;
708 44 50       1382 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         156 my $valids = _b6_make_hash( $valid_content_headers );
713 44         176 foreach my $content_header ( keys( %$content_headers ) ) {
714 88 100       243 if ( not exists $valids->{$content_header} ) {
715 2         10 $self->mrest_declare_status( explanation =>
716             "Content header ->$content_header<- not found in MREST_VALID_CONTENT_HEADERS"
717             );
718 2         10 return 0;
719             }
720             }
721 42         189 return 1;
722             }
723              
724             sub _b6_make_hash {
725 44     44   106 my $ar = shift;
726 44         87 my %h;
727 44         148 foreach my $chn ( @$ar ) {
728 308 100       789 $chn = 'Content-' . $chn unless $chn =~ m/^Content-/;
729 308         739 $h{ $chn } = '';
730             }
731 44         130 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 3064 my ( $self, $content_type ) = @_;
751 42 50       350 $log->debug( "Entering " . __PACKAGE__ . "::known_content_type (B5)" ) unless $muffle{1};
752              
753 42 50       6218 return 1 if not $content_type;
754              
755             # if $content_type is a blessed object, deal with that
756 42         1839 my $ct_isa = ref( $content_type );
757 42 50       173 if ( $ct_isa ) {
758 42 50       393 $log->debug( "\$content_type is a ->$ct_isa<-" ) unless $muffle{1};
759 42 50       6143 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         262 $content_type = $content_type->type; # convert object to string
765             }
766              
767 42 50       707 $log->debug( "Content type of this request is ->$content_type<-" ) unless $muffle{1};
768              
769             # push it onto context
770 42         6114 $self->context->{'content_type'} = $content_type;
771              
772             # convert supported content types into a hash for easy lookup
773 42         95 my %types = map { ( $_ => '' ); } @{ $site->MREST_SUPPORTED_CONTENT_TYPES };
  42         1289  
  42         296  
774 42 100       182 if ( exists $types{ $content_type } ) {
775 41         273 $log->info( "$content_type is supported" );
776 41         6443 return 1;
777             }
778 1         6 $self->mrest_declare_status( explanation => "Content type ->$content_type<- is not supported" );
779 1         7 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 1433 my ( $self, $body_len ) = @_;
792 41         199 state $max_len = $site->MREST_MAX_LENGTH_REQUEST_ENTITY;
793 41 50       724 $log->debug( "Entering " . __PACKAGE__ . "::valid_entity_length, maximum request entity length is $max_len" ) unless $muffle{1};
794 41   100     6402 $body_len = $body_len || 0;
795 41         337 $log->info( "Request body is $body_len bytes long" );
796            
797 41 100       6133 if ( $body_len > $max_len ) {
798 1         17 $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         127 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 1119 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 2275 my $self = shift;
857 39         260 $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       6226 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         109 my $handler = $self->context->{'handler'};
871 39         331 $log->debug( "resource_exists: Calling resource handler $handler for the first time" );
872 39         5846 my $bool;
873             try {
874 39     39   2500 $bool = $self->$handler(1);
875             } catch {
876 0     0   0 $self->mrest_declare_status( code => 500, explanation => $_ );
877 0         0 $bool = 0;
878 39         493 };
879 39         883 $self->push_onto_context( { 'resource_exists' => $bool } );
880 39 100       192 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       23 if ( not $self->status_declared ) {
889 5         26 my $method = $self->context->{'method'};
890 5         15 my $explanation = "Received request for non-existent resource";
891 5 100       29 if ( $method eq 'GET' ) {
    100          
892             # 404 will be assigned by Web::Machine
893 2         40 $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         13 $self->mrest_declare_status( 'code' => 404, 'explanation' => $explanation );
898             }
899             }
900 5         27 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 138 my ( $self ) = @_;
913 1         7 $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       229 $self->mrest_declare_status( 'code' => 404, explanation =>
917             'Detected attempt to POST to non-existent resource' ) unless $self->status_declared;
918              
919 1         4 return 0;
920             }
921              
922              
923             =head2 post_is_create
924              
925             =cut
926              
927             sub post_is_create {
928 10     10 1 2741 my $self = shift;
929 10         76 $log->debug( "Entering " . __PACKAGE__ . "::post_is_create" );
930            
931 10         1638 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 25 my $self = shift;
944 10         62 $log->debug( "Entering " . __PACKAGE__ . "::mrest_post_is_create" );
945              
946 10         1409 my $pic = $self->context->{'post_is_create'};
947 10 100       38 if ( ! defined( $pic ) ) {
948 9         84 $log->error( "post_is_create property is missing; defaults to false" );
949 9         1393 return 0;
950             }
951 1 50       4 if ( $pic ) {
952 1         5 $log->info( "post_is_create property is true" );
953 1         196 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 12 my $self = shift;
966 1         6 $log->debug( "Entering " . __PACKAGE__ . "::create_path" );
967              
968             # if there is a declared status, return a dummy value
969 1 50       122 return "DUMMY" if $self->status_declared;
970              
971 1         7 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 3 my $self = shift;
983 1         6 $log->debug( "Entering " . __PACKAGE__ . "::mrest_create_path" );
984              
985 1         120 my $create_path = $self->context->{'create_path'};
986 1 50       4 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         3 $log->debug( "Returning create_path " . Dumper( $create_path ) );
992 1         196 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 25 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 114 my $self = shift;
1021 9         59 $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         1382 my $status = $self->mrest_process_request;
1037 9         46 $log->debug( "Handler returned: " . Dumper( $status ) );
1038 9         1681 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 263 my $self = shift;
1052 1         8 $log->debug("Entering " . __PACKAGE__ . "::delete_resource");
1053              
1054 1         149 my $status = $self->mrest_generate_response;
1055 1 50 33     8 return 0 if ref( $status ) eq 'SCALAR' or $self->context->{'handler_status'}->not_ok;
1056 1         16 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 54449 my ( $self, $metadata ) = @_;
1071 69         344 state $http_codes = $site->MREST_HTTP_CODES;
1072              
1073 69         893 $log->debug( "Entering " . __PACKAGE__ . "::finish_request with metadata: " . Dumper( $metadata ) );
1074              
1075 69 50       16556 if ( ! $site->MREST_CACHE_ENABLED ) {
1076             #
1077             # tell folks not to cache
1078             #
1079 69         2332 $self->response->header( 'Cache-Control' => $site->MREST_CACHE_CONTROL_HEADER );
1080 69         4587 $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       2592 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       349 if ( $self->status_declared ) {
1099 26         84 my $declared_status = $self->context->{'declared_status'};
1100 26         116 $log->debug( "finish_request: declared status is " . Dumper( $declared_status ) );
1101 26 100       9129 if ( ! $declared_status->payload->{'http_code'} ) {
1102 23         272 $declared_status->payload->{'http_code'} = $self->response->code;
1103             } else {
1104 3         40 $self->response->code( $declared_status->payload->{'http_code'} );
1105             }
1106 26         521 my $json = $JSON->encode( $declared_status->expurgate );
1107 26         7763 $self->response->content( $json );
1108 26         403 $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         1394 $log->debug( "Response finalized: " . Dumper( $self->response ) );
1114 69         17489 return;
1115             }
1116              
1117             1;
1118