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