File Coverage

blib/lib/Dancer2/Core/App.pm
Criterion Covered Total %
statement 609 639 95.3
branch 189 226 83.6
condition 67 109 61.4
subroutine 98 103 95.1
pod 15 45 33.3
total 978 1122 87.1


line stmt bran cond sub pod time code
1             # ABSTRACT: encapsulation of Dancer2 packages
2             $Dancer2::Core::App::VERSION = '0.400000';
3             use Moo;
4 142     142   245861 use Carp qw<croak carp>;
  142         700828  
  142         749  
5 142     142   159648 use Scalar::Util 'blessed';
  142         337  
  142         6530  
6 142     142   753 use Module::Runtime 'is_module_name';
  142         324  
  142         4884  
7 142     142   1911 use Safe::Isa;
  142         4767  
  142         782  
8 142     142   50877 use Sub::Quote;
  142         48840  
  142         17831  
9 142     142   49615 use File::Spec;
  142         603877  
  142         9009  
10 142     142   1078 use Module::Runtime 'use_module';
  142         284  
  142         3454  
11 142     142   702 use List::Util ();
  142         261  
  142         868  
12 142     142   5088 use Ref::Util qw< is_ref is_globref is_scalarref >;
  142         263  
  142         2472  
13 142     142   41368  
  142         126669  
  142         8228  
14             use Plack::App::File;
15 142     142   54859 use Plack::Middleware::FixMissingBodyInRedirect;
  142         2012273  
  142         5370  
16 142     142   53019 use Plack::Middleware::Head;
  142         922383  
  142         5454  
17 142     142   55688 use Plack::Middleware::Conditional;
  142         33304  
  142         3899  
18 142     142   47774 use Plack::Middleware::ConditionalGET;
  142         31629  
  142         3858  
19 142     142   49724  
  142         72061  
  142         4386  
20             use Dancer2::FileUtils 'path';
21 142     142   48527 use Dancer2::Core;
  142         419  
  142         7340  
22 142     142   1819 use Dancer2::Core::Cookie;
  142         287  
  142         2387  
23 142     142   50721 use Dancer2::Core::Error;
  142         455  
  142         5270  
24 142     142   68953 use Dancer2::Core::Types;
  142         461  
  142         5121  
25 142     142   1033 use Dancer2::Core::Route;
  142         301  
  142         1110  
26 142     142   1064561 use Dancer2::Core::Hook;
  142         453  
  142         4759  
27 142     142   54250 use Dancer2::Core::Request;
  142         531  
  142         4268  
28 142     142   81727 use Dancer2::Core::Factory;
  142         515  
  142         5855  
29 142     142   51873  
  142         368  
  142         3793  
30             use Dancer2::Handler::File;
31 142     142   52191  
  142         519  
  142         966041  
32             our $EVAL_SHIM; $EVAL_SHIM ||= sub {
33             my $code = shift;
34             $code->(@_);
35             };
36              
37              
38             # we have hooks here
39             with qw<
40             Dancer2::Core::Role::Hookable
41             Dancer2::Core::Role::ConfigReader
42             >;
43              
44              
45 262     262 0 2010 my ( $self, @plugins ) = @_;
46             return map $self->_with_plugin($_), @plugins;
47              
48 36     36 1 109 }
49 36         163  
50             my( $self, $plugin ) = @_;
51              
52             if ( is_ref($plugin) ) {
53             # passing the plugin as an already-created object
54 36     36   101  
55             # already loaded?
56 36 100       147 if( my ( $already ) = grep { ref($plugin) eq ref $_; } @{ $self->plugins } ) {
57             die "trying to load two different objects for plugin ". ref $plugin
58             if refaddr($plugin) != refaddr $already ;
59              
60 1 50       2 }
  2         15  
  1         15  
61 0 0       0 else {
62             push @{ $self->plugins }, $plugin;
63             }
64              
65             return $plugin;
66 1         2 }
  1         14  
67              
68             # short plugin names get Dancer2::Plugin:: prefix
69 1         8 # plugin names starting with a '+' are full package names
70             if ( $plugin !~ s/^\+// ) {
71             $plugin =~ s/^(?!Dancer2::Plugin::)/Dancer2::Plugin::/;
72             }
73              
74 35 100       187 # check if it's already there
75 7         37 if( my ( $already ) = grep { $plugin eq ref $_ } @{ $self->plugins } ) {
76             return $already;
77             }
78              
79 35 100       83 push @{ $self->plugins },
  12         117  
  35         694  
80 2         10 $plugin = use_module($plugin)->new( app => $self );
81              
82             return $plugin;
83 33         631 }
  33         469  
84              
85             my( $self, $plugin ) = @_;
86 33         463  
87             croak "expected a single argument"
88             unless @_ == 2;
89              
90 36     36 1 7526 ( $self->with_plugins($plugin) )[0];
91             }
92 36 50       126  
93             has _factory => (
94             is => 'ro',
95 36         153 isa => InstanceOf['Dancer2::Core::Factory'],
96             lazy => 1,
97             default => sub { Dancer2::Core::Factory->new },
98             );
99              
100             has logger_engine => (
101             is => 'ro',
102             isa => ConsumerOf['Dancer2::Core::Role::Logger'],
103             lazy => 1,
104             builder => '_build_logger_engine',
105             writer => 'set_logger_engine',
106             );
107              
108             has session_engine => (
109             is => 'ro',
110             isa => ConsumerOf['Dancer2::Core::Role::SessionFactory'],
111             lazy => 1,
112             builder => '_build_session_engine',
113             writer => 'set_session_engine',
114             );
115              
116             has template_engine => (
117             is => 'ro',
118             isa => ConsumerOf['Dancer2::Core::Role::Template'],
119             lazy => 1,
120             builder => '_build_template_engine',
121             writer => 'set_template_engine',
122             );
123              
124             has serializer_engine => (
125             is => 'ro',
126             isa => ConsumerOf['Dancer2::Core::Role::Serializer'],
127             lazy => 1,
128             builder => '_build_serializer_engine',
129             writer => 'set_serializer_engine',
130             predicate => 'has_serializer_engine',
131             );
132              
133             has '+local_triggers' => (
134             default => sub {
135             my $self = shift;
136             my $triggers = {
137             # general triggers we want to allow, besides engines
138             views => sub {
139             my $self = shift;
140             my $value = shift;
141             $self->template_engine->views($value);
142             },
143              
144             layout => sub {
145             my $self = shift;
146             my $value = shift;
147             $self->template_engine->layout($value);
148             },
149              
150             layout_dir => sub {
151             my $self = shift;
152             my $value = shift;
153             $self->template_engine->layout_dir($value);
154             },
155              
156             log => sub {
157             my ( $self, $value, $config ) = @_;
158              
159             # This will allow to set the log level
160             # using: set log => warning
161             $self->logger_engine->log_level($value);
162             },
163             };
164              
165             foreach my $engine ( @{ $self->supported_engines } ) {
166             $triggers->{$engine} = sub {
167             my $self = shift;
168             my $value = shift;
169             my $config = shift;
170              
171             is_ref($value) and return $value;
172              
173             my $build_method = "_build_${engine}_engine";
174             my $setter_method = "set_${engine}_engine";
175             my $engine_instance = $self->$build_method( $value, $config );
176              
177             # set the engine with the new value from the builder
178             $self->$setter_method($engine_instance);
179              
180             return $engine_instance;
181             };
182             }
183              
184             return $triggers;
185             },
186             );
187              
188             my $self = shift;
189             my $value = shift;
190             my $config = shift;
191              
192             defined $config or $config = $self->config;
193             defined $value or $value = $config->{logger};
194 251     251   4822  
195 251         517 is_ref($value) and return $value;
196 251         509  
197             # XXX This is needed for the tests that create an app without
198 251 100       4375 # a runner.
199 251 100       3102 defined $value or $value = 'console';
200              
201 251 50       1028 is_module_name($value)
202             or croak "Cannot load logger engine '$value': illegal module name";
203              
204             my $engine_options =
205 251 50       1026 $self->_get_config_for_engine( logger => $value, $config );
206              
207 251 100       1147 my $logger = $self->_factory->create(
208             logger => $value,
209             %{$engine_options},
210 250         4547 location => $self->config_location,
211             environment => $self->environment,
212             app_name => $self->name,
213             postponed_hooks => $self->postponed_hooks
214             );
215 250         4545  
  250         5716  
216             exists $config->{log} and $logger->log_level($config->{log});
217              
218             return $logger;
219             }
220              
221             my $self = shift;
222 250 100       4682 my $value = shift;
223             my $config = shift;
224 250         6445  
225             defined $config or $config = $self->config;
226             defined $value or $value = $config->{'session'} || 'simple';
227              
228 173     173   29600 is_ref($value) and return $value;
229 173         376  
230 173         353 is_module_name($value)
231             or croak "Cannot load session engine '$value': illegal module name";
232 173 100       2764  
233 173 100 100     2315 my $engine_options =
234             $self->_get_config_for_engine( session => $value, $config );
235 173 100       762  
236             Scalar::Util::weaken( my $weak_self = $self );
237 170 100       897  
238             # Note that engine options will replace the default session_dir (if provided).
239             return $self->_factory->create(
240 169         3524 session => $value,
241             session_dir => path( $self->config->{appdir}, 'sessions' ),
242             %{$engine_options},
243 169         1047 postponed_hooks => $self->postponed_hooks,
244              
245             log_cb => sub { $weak_self->log(@_) },
246             );
247             }
248              
249 169         2205 my $self = shift;
250             my $value = shift;
251             my $config = shift;
252 3     3   32  
253 169         3213 defined $config or $config = $self->config;
254             defined $value or $value = $config->{'template'};
255              
256             defined $value or return;
257 162     162   4075 is_ref($value) and return $value;
258 162         372  
259 162         337 is_module_name($value)
260             or croak "Cannot load template engine '$value': illegal module name";
261 162 100       2763  
262 162 100       1720 my $engine_options =
263             $self->_get_config_for_engine( template => $value, $config );
264 162 50       579  
265 162 50       602 my $engine_attrs = {
266             config => $engine_options,
267 162 100       832 layout => $config->{layout},
268             layout_dir => ( $config->{layout_dir} || 'layouts' ),
269             views => $config->{views},
270 161         3233 };
271              
272             Scalar::Util::weaken( my $weak_self = $self );
273              
274             return $self->_factory->create(
275             template => $value,
276             %{$engine_attrs},
277             postponed_hooks => $self->postponed_hooks,
278 161   50     1780  
279             log_cb => sub { $weak_self->log(@_) },
280 161         1203 );
281             }
282              
283             my $self = shift;
284 161         3418 my $value = shift;
285             my $config = shift;
286              
287 7     7   42 defined $config or $config = $self->config;
288 161         2971 defined $value or $value = $config->{serializer};
289              
290             defined $value or return;
291             is_ref($value) and return $value;
292 19     19   45  
293 19         42 my $engine_options =
294 19         38 $self->_get_config_for_engine( serializer => $value, $config );
295              
296 19 50       64 Scalar::Util::weaken( my $weak_self = $self );
297 19 50       64  
298             return $self->_factory->create(
299 19 50       60 serializer => $value,
300 19 50       66 config => $engine_options,
301             postponed_hooks => $self->postponed_hooks,
302 19         82  
303             log_cb => sub { $weak_self->log(@_) },
304             );
305 19         133 }
306              
307             my $self = shift;
308             my $engine = shift;
309             my $name = shift;
310             my $config = shift;
311              
312 5     5   39 defined $config->{'engines'} && defined $config->{'engines'}{$engine}
313 19         398 or return {};
314              
315             # try both camelized name and regular name
316             my $engine_config = {};
317 604     604   2063 foreach my $engine_name ( $name, Dancer2::Core::camelize($name) ) {
318 604         1192 if ( defined $config->{'engines'}{$engine}{$engine_name} ) {
319 604         1056 $engine_config = $config->{'engines'}{$engine}{$engine_name};
320 604         1179 last;
321             }
322 604 100 100     3669 }
323              
324             return $engine_config;
325             }
326 23         60  
327 23         181 has postponed_hooks => (
328 30 100       111 is => 'ro',
329 19         53 isa => HashRef,
330 19         62 default => sub { {} },
331             );
332              
333             # TODO I'd be happier with a HashRef, really
334 23         63 has plugins => (
335             is => 'rw',
336             isa => ArrayRef,
337             default => sub { [] },
338             );
339              
340             has route_handlers => (
341             is => 'rw',
342             isa => ArrayRef,
343             default => sub { [] },
344             );
345              
346             has name => (
347             is => 'ro',
348             isa => Str,
349             default => sub { (caller(1))[0] },
350             );
351              
352             has request => (
353             is => 'ro',
354             isa => InstanceOf['Dancer2::Core::Request'],
355             writer => '_set_request',
356             clearer => 'clear_request',
357             predicate => 'has_request',
358             );
359              
360             my ($self, $request, $defined_engines) = @_;
361             # typically this is passed in as an optimization within the
362             # dispatch loop but may be called elsewhere
363             $defined_engines ||= $self->defined_engines;
364             # populate request in app and all engines
365             $self->_set_request($request);
366             Scalar::Util::weaken( my $weak_request = $request );
367             $_->set_request( $weak_request ) for @{$defined_engines};
368             }
369              
370             has response => (
371 668     668 0 1587 is => 'ro',
372             isa => InstanceOf['Dancer2::Core::Response'],
373             lazy => 1,
374 668   66     2307 writer => 'set_response',
375             clearer => 'clear_response',
376 668         14634 builder => '_build_response',
377 668         19761 predicate => 'has_response',
378 668         1001 );
  668         11393  
379              
380             has with_return => (
381             is => 'ro',
382             predicate => 1,
383             writer => 'set_with_return',
384             clearer => 'clear_with_return',
385             );
386              
387             has session => (
388             is => 'ro',
389             isa => InstanceOf['Dancer2::Core::Session'],
390             lazy => 1,
391             builder => '_build_session',
392             writer => 'set_session',
393             clearer => 'clear_session',
394             predicate => '_has_session',
395             );
396              
397             around _build_config => sub {
398             my ( $orig, $self ) = @_;
399             my $config = $self->$orig;
400              
401             if ( $config && $config->{'engines'} ) {
402             $self->_validate_engine($_) for keys %{ $config->{'engines'} };
403             }
404              
405             return $config;
406             };
407              
408             my $self = shift;
409             return Dancer2::Core::Response->new(
410             server_tokens => !$self->config->{'no_server_tokens'},
411             $self->has_serializer_engine
412             ? ( serializer => $self->serializer_engine )
413             : (),
414             );
415             }
416              
417             my $self = shift;
418             my $session;
419              
420 638     638   6015 # Find the session engine
421             my $engine = $self->session_engine;
422 638 100       9016  
423             # find the session cookie if any
424             if ( !$self->has_destroyed_session ) {
425             my $session_id;
426             my $session_cookie = $self->cookie( $engine->cookie_name );
427             defined $session_cookie and
428             $session_id = $session_cookie->value;
429              
430 93     93   1023 # if we have a session cookie, try to retrieve the session
431 93         156 if ( defined $session_id ) {
432             eval {
433             $EVAL_SHIM->(sub {
434 93         1352 $session = $engine->retrieve( id => $session_id );
435             });
436             1;
437 93 100       888 }
438 88         244 or do {
439 88         403 my $err = $@ || "Zombie Error";
440 88 100       1109 if ( $err !~ /Unable to retrieve session/ ) {
441             croak "Failed to retrieve session: $err"
442             } else {
443             # XXX we throw away the error entirely? Why?
444 88 100       280 }
445             };
446             }
447 53     53   316 }
448 53         375  
449 50         341 # create the session if none retrieved
450             return $session ||= $engine->create();
451 53 100       105 }
452 3   50     138  
453 3 50       19 my $self = shift;
454 0         0  
455             my $engine = $self->session_engine;
456              
457             return $self->_has_session
458             || ( $self->cookie( $engine->cookie_name )
459             && !$self->has_destroyed_session );
460             }
461              
462             has destroyed_session => (
463 93   66     1334 is => 'ro',
464             isa => InstanceOf ['Dancer2::Core::Session'],
465             predicate => 1,
466             writer => 'set_destroyed_session',
467 899     899 1 1327 clearer => 'clear_destroyed_session',
468             );
469 899         11826  
470             has 'prep_apps' => (
471 899   66     13032 'is' => 'ro',
472             'isa' => ArrayRef,
473             'default' => sub { [] },
474             );
475              
476             my ( $self, $name ) = @_;
477             my $plugin = List::Util::first { ref($_) eq $name } @{ $self->plugins };
478             $plugin or return;
479             return $plugin;
480             }
481              
482             my $self = shift;
483              
484             # Find the session engine
485             my $engine = $self->session_engine;
486              
487             # Expire session, set the expired cookie and destroy the session
488             # Setting the cookie ensures client gets an expired cookie unless
489             # a new session is created and supercedes it
490             my $session = $self->session;
491 2     2 0 6 $session->expires(-86400); # yesterday
492 2     2   8 $engine->destroy( id => $session->id );
  2         20  
  2         41  
493 2 100       20  
494 1         4 # Invalidate session cookie in request
495             # and clear session in app and engines
496             $self->set_destroyed_session($session);
497             $self->clear_session;
498 17     17 1 43 $_->clear_session for @{ $self->defined_engines };
499              
500             return;
501 17         259 }
502              
503             my $self = shift;
504              
505             for my $engine ( @{ $self->defined_engines } ) {
506 17         360 $self->has_session ?
507 17         642 $engine->set_session( $self->session ) :
508 17         1596 $engine->clear_session;
509             }
510             }
511              
512 17         299 my $self = shift;
513 17         732  
514 17         93 my $session = $self->session;
  17         57  
515              
516 17         1630 # Find the session engine
517             my $engine = $self->session_engine;
518              
519             if ($engine->can('_change_id')) {
520 118     118 0 200  
521             # session engine can change session ID
522 118         175 $engine->change_id( session => $session );
  118         325  
523 354 50       13923 }
524             else {
525              
526             # Method order is important in here...
527             #
528             # On session build if there is no destroyed session then the session
529             # builder tries to recreate the session using the existing session
530 5     5 1 12 # cookie. We really don't want to do that in this case so it is
531             # important to create the new session before the
532 5         104 # clear_destroyed_session method is called.
533             #
534             # This sucks.
535 5         188 #
536             # Sawyer suggested:
537 5 100       72 #
538             # What if you take the session cookie logic out of that attribute into
539             # another attribute and clear that attribute?
540 3         14 # That would force the session rebuilt to rebuilt the attribute and
541             # get a different cookie value, no?
542             #
543             # TODO: think about this some more.
544              
545             # grab data, destroy session and store data again
546             my %data = %{$session->data};
547              
548             # destroy existing session
549             $self->destroy_session;
550              
551             # get new session
552             $session = $self->session;
553              
554             # write data from old session into new
555             # Some engines add session id to data so skip id.
556             while (my ($key, $value) = each %data ) {
557             $session->write($key => $value) unless $key eq 'id';
558             }
559              
560             # clear out destroyed session - no longer relevant
561             $self->clear_destroyed_session;
562             }
563              
564 2         4 return $session->id;
  2         347  
565             }
566              
567 2         29 has prefix => (
568             is => 'rw',
569             isa => Maybe [Dancer2Prefix],
570 2         26 predicate => 1,
571             coerce => sub {
572             my $prefix = shift;
573             defined($prefix) and $prefix eq "/" and return;
574 2         51 return $prefix;
575 2 50       15 },
576             );
577              
578             # routes registry, stored by method:
579 2         46 has routes => (
580             is => 'rw',
581             isa => HashRef,
582 5         100 default => sub {
583             { get => [],
584             head => [],
585             post => [],
586             put => [],
587             del => [],
588             options => [],
589             };
590             },
591             );
592              
593             # add_hook will add the hook to the first "hook candidate" it finds that support
594             # it. If none, then it will try to add the hook to the current application.
595             around add_hook => sub {
596             my $orig = shift;
597             my $self = shift;
598              
599             # saving caller information
600             my ( $package, $file, $line ) = caller(4); # deep to 4 : user's app code
601             my $add_hook_caller = [ $package, $file, $line ];
602              
603             my ($hook) = @_;
604             my $name = $hook->name;
605             my $hook_aliases = $self->all_hook_aliases;
606              
607             # look for an alias
608             defined $hook_aliases->{$name} and $name = $hook_aliases->{$name};
609             $hook->name($name);
610              
611             # if that hook belongs to the app, register it now and return
612             $self->has_hook($name) and return $self->$orig(@_);
613              
614             # at this point the hook name must be formatted like:
615             # '$type.$candidate.$name', eg: 'engine.template.before_render' or
616             # 'plugin.database.before_dbi_connect'
617             my ( $hookable_type, $hookable_name, $hook_name ) = split( /\./, $name );
618              
619             ( defined $hookable_name && defined $hook_name )
620             or croak "Invalid hook name `$name'";
621              
622             grep /^$hookable_type$/, qw(core engine handler plugin)
623             or croak "Unknown hook type `$hookable_type'";
624              
625             # register the hooks for existing hookable candidates
626             foreach my $hookable ( $self->hook_candidates ) {
627             $hookable->has_hook($name) and $hookable->add_hook(@_);
628             }
629              
630             # we register the hook for upcoming objects;
631             # that way, each components that can claim the hook will have a chance
632             # to register it.
633              
634             my $postponed_hooks = $self->postponed_hooks;
635              
636             # Hmm, so the hook was not claimed, at this point we'll cache it and
637             # register it when the owner is instantiated
638             $postponed_hooks->{$hookable_type}{$hookable_name} ||= {};
639             $postponed_hooks->{$hookable_type}{$hookable_name}{$name} ||= {};
640             $postponed_hooks->{$hookable_type}{$hookable_name}{$name}{hook} = $hook;
641             $postponed_hooks->{$hookable_type}{$hookable_name}{$name}{caller} =
642             $add_hook_caller;
643              
644             };
645              
646             around execute_hook => sub {
647             my $orig = shift;
648             my $self = shift;
649              
650             local $Dancer2::Core::Route::REQUEST = $self->request;
651             local $Dancer2::Core::Route::RESPONSE = $self->response;
652              
653             my ( $hook, @args ) = @_;
654             if ( !$self->has_hook($hook) ) {
655             foreach my $cand ( $self->hook_candidates ) {
656             $cand->has_hook($hook) and return $cand->execute_hook(@_);
657             }
658             }
659              
660             return $self->$orig(@_);
661             };
662              
663             my $self = shift;
664              
665             my $public = $ENV{DANCER_PUBLIC} || path( $self->location, 'public' );
666             return {
667             content_type => ( $ENV{DANCER_CONTENT_TYPE} || 'text/html' ),
668             charset => ( $ENV{DANCER_CHARSET} || '' ),
669             logger => ( $ENV{DANCER_LOGGER} || 'console' ),
670             views => ( $ENV{DANCER_VIEWS}
671             || path( $self->location, 'views' ) ),
672             environment => $self->environment,
673             appdir => $self->location,
674             public_dir => $public,
675             template => 'Tiny',
676             route_handlers => [
677             [
678             AutoPage => 1
679             ],
680             ],
681             };
682 234     234   2886 }
683              
684 234   33     1861 my $self = shift;
685              
686             # Hook to flush the session at the end of the request,
687             # this way, we're sure we flush only once per request
688             #
689             # Note: we create a weakened copy $self
690 234   50     3596 # before closing over the weakened copy
      50        
      50        
      33        
691             # to avoid circular memory refs.
692             Scalar::Util::weaken(my $app = $self);
693              
694             $self->add_hook(
695             Dancer2::Core::Hook->new(
696             name => 'core.app.after_request',
697             code => sub {
698             my $response = $Dancer2::Core::Route::RESPONSE;
699              
700             # make sure an engine is defined, if not, nothing to do
701             my $engine = $app->session_engine;
702             defined $engine or return;
703              
704 233     233   559 # if a session has been instantiated or we already had a
705             # session, first flush the session so cookie-based sessions can
706             # update the session ID if needed, then set the session cookie
707             # in the response
708             #
709             # if there is NO session object but the request has a cookie with
710             # a session key, create a dummy session with the same ID (without
711             # actually retrieving and flushing immediately) and generate the
712 233         842 # cookie header from the dummy session. Lazy Sessions FTW!
713              
714             if ( $app->has_session ) {
715             my $session;
716             if ( $app->_has_session ) { # Session object exists
717             $session = $app->session;
718 460     460   833 $session->is_dirty and $engine->flush( session => $session );
719             }
720             else { # Cookie header exists. Create a dummy session object
721 460         8700 my $cookie = $app->cookie( $engine->cookie_name );
722 460 50       3697 my $session_id = $cookie->value;
723             $session = Dancer2::Core::Session->new( id => $session_id );
724             }
725             $engine->set_cookie_header(
726             response => $response,
727             session => $session
728             );
729             }
730             elsif ( $app->has_destroyed_session ) {
731             my $session = $app->destroyed_session;
732             $engine->set_cookie_header(
733             response => $response,
734 460 100       3178 session => $session,
    100          
735 76         156 destroyed => 1
736 76 100       268 );
737 73         1079 }
738 73 100       1466 },
739             )
740             );
741 3         12 }
742 3         48  
743 3         71 qw/
744             core.app.before_request
745 76         795 core.app.after_request
746             core.app.route_exception
747             core.app.before_file_render
748             core.app.after_file_render
749             core.error.before
750             core.error.after
751 12         45 core.error.init
752 12         150 /;
753             }
754              
755             my $self = shift;
756             $self->{'hook_aliases'} ||= {
757             before => 'core.app.before_request',
758             before_request => 'core.app.before_request',
759             after => 'core.app.after_request',
760 233         4550 after_request => 'core.app.after_request',
761             init_error => 'core.error.init',
762             before_error => 'core.error.before',
763             after_error => 'core.error.after',
764 438     438 0 4328 on_route_exception => 'core.app.route_exception',
765              
766             before_file_render => 'core.app.before_file_render',
767             after_file_render => 'core.app.after_file_render',
768             before_handler_file_render => 'handler.file.before_render',
769             after_handler_file_render => 'handler.file.after_render',
770              
771              
772             # compatibility from Dancer1
773             before_error_render => 'core.error.before',
774             after_error_render => 'core.error.after',
775             before_error_init => 'core.error.init',
776              
777 1750     1750 0 3681 # TODO: call $engine->hook_aliases as needed
778 1750   100     13764 # But.. currently there are use cases where hook_aliases
779             # are needed before the engines are intiialized :(
780             before_template_render => 'engine.template.before_render',
781             after_template_render => 'engine.template.after_render',
782             before_layout_render => 'engine.template.before_layout_render',
783             after_layout_render => 'engine.template.after_layout_render',
784             before_serializer => 'engine.serializer.before',
785             after_serializer => 'engine.serializer.after',
786             };
787             }
788              
789             my $self = shift;
790             return [
791             $self->template_engine,
792             $self->session_engine,
793             $self->logger_engine,
794             $self->has_serializer_engine
795             ? $self->serializer_engine
796             : (),
797             ];
798             }
799              
800             # FIXME not needed anymore, I suppose...
801              
802             my $self = shift;
803             my $plugin = shift;
804              
805             $self->log( core => "Registered $plugin");
806              
807             push @{ $self->plugins }, $plugin;
808             }
809              
810             # This method overrides the default one from Role::ConfigReader
811             my $self = shift;
812 1541     1541 0 2630 +{ %{ Dancer2::runner()->config }, %{ $self->config } };
813             }
814 1541 100       22294  
815             my $self = shift;
816             $self->clear_request;
817             $self->clear_response;
818             $self->clear_session;
819             $self->clear_destroyed_session;
820             # Clear engine attributes
821             for my $engine ( @{ $self->defined_engines } ) {
822             $engine->clear_session;
823             $engine->clear_request;
824 0     0 0 0 }
825             }
826              
827 0     0 1 0 my $self = shift;
828 0         0 my $name = shift;
829              
830 0         0 grep +( $_ eq $name ), @{ $self->supported_engines }
831             or croak "Engine '$name' is not supported.";
832 0         0 }
  0         0  
833              
834             my $self = shift;
835             my $name = shift;
836              
837 883     883 0 1689 $self->_validate_engine($name);
838 883         1411  
  883         2380  
  883         20882  
839             my $attr_name = "${name}_engine";
840             return $self->$attr_name;
841             }
842 658     658 0 1111  
843 658         11505 my $self = shift;
844 658         11638  
845 658         11953 my $template = $self->template_engine;
846 658         11231 $template->set_settings( $self->config );
847              
848 658         2992 # A session will not exist if there is no request (global keyword)
  658         1656  
849 2054         58027 #
850 2054         31992 # A session may exist but the route code may not have instantiated
851             # the session object (sessions are lazy). If this is the case, do
852             # that now, so the templates have the session data for rendering.
853             $self->has_request && $self->has_session && ! $template->has_session
854             and $self->setup_session;
855 28     28   61  
856 28         59 # return content
857             return $template->process( @_ );
858 28 100       51 }
  28         93  
859              
860             my $self = shift;
861              
862             my @engines = @{ $self->defined_engines };
863 26     26 0 892  
864 26         53 my @route_handlers;
865             for my $handler ( @{ $self->route_handlers } ) {
866 26         97 my $handler_code = $handler->{handler};
867             blessed $handler_code and $handler_code->can('supported_hooks')
868 25         86 and push @route_handlers, $handler_code;
869 25         506 }
870              
871             # TODO : get the list of all plugins registered
872             my @plugins = @{ $self->plugins };
873 30     30 0 600  
874             ( @route_handlers, @engines, @plugins );
875 30         508 }
876 30         639  
877             my $self = shift;
878              
879             my $aliases = $self->hook_aliases;
880             for my $plugin ( grep { $_->can('hook_aliases') } @{ $self->plugins } ) {
881             $aliases = { %{$aliases}, %{ $plugin->hook_aliases } };
882             }
883 30 100 100     1870  
      100        
884             return $aliases;
885             }
886              
887 30         403 my $self = shift;
888             my $runner = Dancer2::runner();
889              
890             exists $self->config->{default_mime_type}
891 45     45 0 76 ? $runner->mime_type->default( $self->config->{default_mime_type} )
892             : $runner->mime_type->reset_default;
893 45         89  
  45         173  
894             $runner->mime_type;
895 45         3121 }
896 45         77  
  45         657  
897 45         324 my $self = shift;
898 45 50 33     476 my $level = shift;
899              
900             my $logger = $self->logger_engine
901             or croak "No logger defined";
902              
903 45         80 $logger->$level(@_);
  45         690  
904             }
905 45         332  
906             my $self = shift;
907             my ( $type, $data, $options ) = @_;
908             $options ||= {};
909 313     313 0 710  
910             $type or croak "Can not send_as using an undefined type";
911 313         1260  
912 313         717 if ( lc($type) eq 'html' || lc($type) eq 'plain' ) {
  13         142  
  313         5221  
913 13         26 if ( $type ne lc $type ) {
  13         81  
  13         44  
914             local $Carp::CarpLevel = 2;
915             carp sprintf( "Please use %s as the type for 'send_as', not %s", lc($type), $type );
916 313         2747 }
917              
918             $options->{charset} = $self->config->{charset} || 'UTF-8';
919             my $content = Encode::encode( $options->{charset}, $data );
920 10     10 0 24 $options->{content_type} ||= join '/', 'text', lc $type;
921 10         48 $self->send_file( \$content, %$options ); # returns from sub
922             }
923              
924             # Try and load the serializer class
925 10 100       224 my $serializer_class = "Dancer2::Serializer::$type";
926             eval {
927 10         460 $EVAL_SHIM->(sub {
928             require_module( $serializer_class );
929             });
930             1;
931 2312     2312 0 28646 } or do {
932 2312         3563 my $err = $@ || "Zombie Error";
933             croak "Unable to load serializer class for $type: $err";
934 2312 50       33807 };
935              
936             # load any serializer engine config
937 2312         39386 my $engine_options =
938             $self->_get_config_for_engine( serializer => $type, $self->config ) || {};
939             my $serializer = $serializer_class->new( config => $engine_options );
940             my $content = $serializer->serialize( $data );
941 8     8 0 11 $options->{content_type} ||= $serializer->content_type;
942 8         15 $self->send_file( \$content, %$options );
943 8   100     28 }
944              
945 8 100       137 my $self = shift;
946             my ( $message, $status ) = @_;
947 7 100 100     30  
948 2 50       6 my $err = Dancer2::Core::Error->new(
949 0         0 message => $message,
950 0         0 app => $self,
951             ( status => $status )x!! $status,
952              
953 2   50     25 $self->has_serializer_engine
954 2         25 ? ( serializer => $self->serializer_engine )
955 2   33     66 : (),
956 2         7 )->throw;
957              
958             # Immediately return to dispatch if with_return coderef exists
959             $self->has_with_return && $self->with_return->($err);
960 5         10 return $err;
961             }
962              
963 5     5   23 my $self = shift;
964 5         20 my $thing = shift;
965 3         55 my %options = @_;
966 5 100       7  
967 2   50     394 my ($content_type, $charset, $file_path);
968 2         197  
969             # are we're given a filehandle? (based on what Plack::Middleware::Lint accepts)
970             my $is_filehandle = Plack::Util::is_real_fh($thing)
971             || ( is_globref($thing) && *{$thing}{IO} && *{$thing}{IO}->can('getline') )
972 3   50     48 || ( Scalar::Util::blessed($thing) && $thing->can('getline') );
973             my ($fh) = ($thing)x!! $is_filehandle;
974 3         31  
975 3         50 # if we're given an IO::Scalar object, DTRT (take the scalar ref from it)
976 3   66     15 if (Scalar::Util::blessed($thing) && $thing->isa('IO::Scalar')) {
977 3         10 $thing = $thing->sref;
978             }
979              
980             # if we're given a SCALAR reference, build a filehandle to it
981 8     8 0 17 if ( is_scalarref($thing) ) {
982 8         30 ## no critic qw(InputOutput::RequireCheckedOpen)
983             open $fh, "<", $thing;
984 8 100       143 }
985              
986             # If we haven't got a filehandle, create one to the requested content
987             if (! $fh) {
988             my $path = $thing;
989             # remove prefix from given path (if not a filehandle)
990             my $prefix = $self->prefix;
991             if ( $prefix && $prefix ne '/' ) {
992             $path =~ s/^\Q$prefix\E//;
993             }
994             # static file dir - either system root or public_dir
995 8 50       157 my $dir = $options{system_path}
996 0         0 ? File::Spec->rootdir
997             : $ENV{DANCER_PUBLIC}
998             || $self->config->{public_dir}
999             || path( $self->location, 'public' );
1000 16     16 0 24  
1001 16         27 $file_path = Dancer2::Handler::File->merge_paths( $path, $dir );
1002 16         40 my $err_response = sub {
1003             my $status = shift;
1004 16         25 $self->response->status($status);
1005             $self->response->header( 'Content-Type', 'text/plain' );
1006             $self->response->content( Dancer2::Core::HTTP->status_message($status) );
1007             $self->with_return->( $self->response );
1008 16   33     62 };
1009             $err_response->(403) if !defined $file_path;
1010 16         637 $err_response->(404) if !-f $file_path;
1011             $err_response->(403) if !-r $file_path;
1012              
1013 16 50 33     45 # Read file content as bytes
1014 0         0 $fh = Dancer2::FileUtils::open_file( "<", $file_path );
1015             binmode $fh;
1016             $content_type = Dancer2::runner()->mime_type->for_file($file_path) || 'text/plain';
1017             if ( $content_type =~ m!^text/! ) {
1018 16 100       37 $charset = $self->config->{charset} || "utf-8";
1019             }
1020 6         60 }
1021              
1022             # Now we are sure we can render the file...
1023             $self->execute_hook( 'core.app.before_file_render', $file_path );
1024 16 100       45  
1025 9         14 # response content type and charset
1026             ( exists $options{'content_type'} ) and $content_type = $options{'content_type'};
1027 9         183 ( exists $options{'charset'} ) and $charset = $options{'charset'};
1028 9 50 33     72 $content_type .= "; charset=$charset" if $content_type and $charset;
1029 0         0 ( defined $content_type )
1030             and $self->response->header('Content-Type' => $content_type );
1031              
1032             # content disposition
1033             ( exists $options{filename} )
1034             and $self->response->header( 'Content-Disposition' =>
1035             ($options{content_disposition} || "attachment") . "; filename=\"$options{filename}\"" );
1036 9 100 33     126  
1037             # use a delayed response unless server does not support streaming
1038 9         95 my $use_streaming = exists $options{streaming} ? $options{streaming} : 1;
1039             my $response;
1040 0     0   0 my $env = $self->request->env;
1041 0         0 if ( $env->{'psgi.streaming'} && $use_streaming ) {
1042 0         0 my $cb = sub {
1043 0         0 my $responder = $Dancer2::Core::Route::RESPONDER;
1044 0         0 my $res = $Dancer2::Core::Route::RESPONSE;
1045 9         43 return $responder->(
1046 9 50       32 [ $res->status, $res->headers_to_array, $fh ]
1047 9 50       161 );
1048 9 50       118 };
1049              
1050             Scalar::Util::weaken( my $weak_self = $self );
1051 9         50  
1052 9         38 $response = Dancer2::Core::Response::Delayed->new(
1053 9   50     31 error_cb => sub { $weak_self->logger_engine->log( warning => @_ ) },
1054 9 100       138 cb => $cb,
1055 5   50     96 request => $Dancer2::Core::Route::REQUEST,
1056             response => $Dancer2::Core::Route::RESPONSE,
1057             );
1058             }
1059             else {
1060 16         381 $response = $self->response;
1061             # direct assignment to hash element, avoids around modifier
1062             # trying to serialise this this content.
1063 16 100       139 $response->{content} = Dancer2::FileUtils::read_glob_content($fh);
1064 16 100       34 $response->is_encoded(1); # bytes are already encoded
1065 16 100 100     68 }
1066 16 100       212  
1067             $self->execute_hook( 'core.app.after_file_render', $response );
1068             $self->with_return->( $response );
1069             }
1070              
1071             my $self = shift;
1072 16 100 100     1634 $self->init_route_handlers();
1073             $self->_init_hooks();
1074              
1075 16 100       203 $self->log(core => 'Built config from files: ' . join(' ', @{$self->config_files}));
1076 16         21 }
1077 16         66  
1078 16 100 66     128 my $self = shift;
1079              
1080 15     15   28 # normalize some values that require calculations
1081 15         30 defined $self->config->{'static_handler'}
1082 15         258 or $self->config->{'static_handler'} = -d $self->config->{'public_dir'};
1083              
1084             $self->register_route_handlers;
1085 15         71 $self->compile_hooks;
1086              
1087 15         46 @{$self->plugins}
1088             && $self->plugins->[0]->can('_add_postponed_plugin_hooks')
1089             && $self->plugins->[0]->_add_postponed_plugin_hooks(
1090 0     0   0 $self->postponed_hooks
1091 15         261 );
1092              
1093             foreach my $prep_cb ( @{ $self->prep_apps } ) {
1094             $prep_cb->($self);
1095             }
1096             }
1097 1         16  
1098             my $self = shift;
1099              
1100 1         9 my $handlers_config = $self->config->{route_handlers};
1101 1         24 for my $handler_data ( @{$handlers_config} ) {
1102             my ($handler_name, $config) = @{$handler_data};
1103             $config = {} if !is_ref($config);
1104 16         11989  
1105 16         154 my $handler = $self->_factory->create(
1106             Handler => $handler_name,
1107             app => $self,
1108             %$config,
1109             postponed_hooks => $self->postponed_hooks,
1110             );
1111              
1112             push @{ $self->route_handlers }, {
1113             name => $handler_name,
1114             handler => $handler,
1115             };
1116             }
1117 202     202 0 510 }
1118              
1119             my $self = shift;
1120             for my $handler ( @{$self->route_handlers} ) {
1121 202 100       2843 my $handler_code = $handler->{handler};
1122             $handler_code->register($self);
1123 202         11060 }
1124 202         2271 }
1125              
1126 202 50 66     1406 my ($self) = @_;
  202         3484  
1127              
1128             for my $position ( $self->supported_hooks ) {
1129             my $compiled_hooks = [];
1130             for my $hook ( @{ $self->hooks->{$position} } ) {
1131             Scalar::Util::weaken( my $app = $self );
1132 202         2372 my $compiled = sub {
  202         1076  
1133 4         16 # don't run the filter if halt has been used
1134             $Dancer2::Core::Route::RESPONSE &&
1135             $Dancer2::Core::Route::RESPONSE->is_halted
1136             and return;
1137              
1138 234     234 0 495 eval { $EVAL_SHIM->($hook,@_); 1; }
1139             or do {
1140 234         3811 my $err = $@ || "Zombie Error";
1141 233         7213 $app->cleanup;
  233         827  
1142 232         480 $app->log('error', "Exception caught in '$position' filter: $err");
  232         675  
1143 232 50       955 croak "Exception caught in '$position' filter: $err";
1144             };
1145 232         4307 };
1146              
1147             push @{$compiled_hooks}, $compiled;
1148             }
1149             $self->replace_hook( $position, $compiled_hooks );
1150             }
1151             }
1152 232         218480  
  232         4454  
1153             my $self = shift;
1154             my $prefix = shift;
1155             my $cb = shift;
1156              
1157             $prefix eq '/' and undef $prefix;
1158              
1159             # save the app prefix
1160 202     202 0 477 my $app_prefix = $self->prefix;
1161 202         403  
  202         3976  
1162 200         1852 # alter the prefix for the callback
1163 200         1158 my $new_prefix =
1164             ( defined $app_prefix ? $app_prefix : '' )
1165             . ( defined $prefix ? $prefix : '' );
1166              
1167             # if the new prefix is empty, it's a meaningless prefix, just ignore it
1168 205     205 0 557 length $new_prefix and $self->prefix($new_prefix);
1169              
1170 205         864 my $err;
1171 1640         8912 my $ok= eval { $EVAL_SHIM->($cb); 1 }
1172 1640         1934 or do { $err = $@ || "Zombie Error"; };
  1640         20234  
1173 247         2311  
1174             # restore app prefix
1175             $self->prefix($app_prefix);
1176 674 100 66 674   2517  
1177             $ok or croak "Unable to run the callback for prefix '$prefix': $err";
1178             }
1179              
1180 673         2195 my $self = shift;
  664         4348  
1181 673 100       4695 my %route_attrs = @_;
1182 4   50     147  
1183 4         31 my $route = Dancer2::Core::Route->new(
1184 4         45 type_library => $self->config->{type_library},
1185 4         386 %route_attrs,
1186             prefix => $self->prefix,
1187 247         1312 );
1188              
1189 247         493 my $method = $route->method;
  247         662  
1190              
1191 1640         10369 push @{ $self->routes->{$method} }, $route;
1192              
1193             return $route;
1194             }
1195              
1196 5     5 1 1723 my $self = shift;
1197 5         11 my $route = shift;
1198 5         10  
1199             my $routes = $self->routes->{ $route->method };
1200 5 100       19  
1201             foreach my $existing_route (@$routes) {
1202             $existing_route->spec_route eq $route->spec_route
1203 5         149 and return 1;
1204             }
1205              
1206 5 100       51 return 0;
    100          
1207             }
1208              
1209             my $self = shift;
1210             my $method = shift;
1211 5 100       65  
1212             return [ map $_->regexp, @{ $self->routes->{$method} } ];
1213 5         110 }
1214 5         20  
  4         16  
1215 5 100 50     10 my $self = shift;
  1         22  
1216              
1217             @_ == 1 and return $self->request->cookies->{ $_[0] };
1218 5         74  
1219             # writer
1220 5 100       390 my ( $name, $value, %options ) = @_;
1221             my $c =
1222             Dancer2::Core::Cookie->new( name => $name, value => $value, %options );
1223             $self->response->push_header( 'Set-Cookie' => $c->to_header );
1224 607     607 1 1595 }
1225 607         2128  
1226             my $self = shift;
1227             my $destination = shift;
1228             my $status = shift;
1229 607         10597  
1230             if ($destination =~ m{^/(?!/)}) {
1231             # If the app is mounted to something other than "/", we must
1232             # preserve its path.
1233 607         47492 my $script_name = $self->request->script_name;
1234             $script_name =~ s{/$}{}; # Remove trailing slash (if present).
1235 607         1113 $destination = $script_name . $destination;
  607         9993  
1236             }
1237 607         8121  
1238             $self->response->redirect( $destination, $status );
1239              
1240             # Short circuit any remaining before hook / route code
1241 0     0 1 0 # ('pass' and after hooks are still processed)
1242 0         0 $self->has_with_return
1243             and $self->with_return->($self->response);
1244 0         0 }
1245              
1246 0         0 my $self = shift;
1247 0 0       0 $self->response->halt( @_ );
1248              
1249             # Short citcuit any remaining hook/route code
1250             $self->has_with_return
1251 0         0 and $self->with_return->($self->response);
1252             }
1253              
1254             my $self = shift;
1255 1     1 1 746 $self->response->pass;
1256 1         3  
1257             # Short citcuit any remaining hook/route code
1258 1         2 $self->has_with_return
  1         21  
1259             and $self->with_return->($self->response);
1260             }
1261              
1262 538     538 0 1037 my $self = shift;
1263             my $url = shift;
1264 538 50       3206 my $params = shift;
1265             my $options = shift;
1266              
1267 0         0 my $new_request = $self->make_forward_to( $url, $params, $options );
1268 0         0  
1269             $self->has_with_return
1270 0         0 and $self->with_return->($new_request);
1271              
1272             # nothing else will run after this
1273             }
1274 25     25 1 37  
1275 25         39 # Create a new request which is a clone of the current one, apart
1276 25         34 # from the path location, which points instead to the new location
1277             my $self = shift;
1278 25 100       76 my $url = shift;
1279             my $params = shift;
1280             my $options = shift;
1281 12         99  
1282 12         78 my $overrides = { PATH_INFO => $url };
1283 12         41 exists $options->{method} and
1284             $overrides->{REQUEST_METHOD} = $options->{method};
1285              
1286 25         362 # "clone" the existing request
1287             my $new_request = $self->request->_shallow_clone( $params, $overrides );
1288              
1289             # If a session object was created during processing of the original request
1290 25 50       2823 # i.e. a session object exists but no cookie existed
1291             # add a cookie so the dispatcher can assign the session to the appropriate app
1292             my $engine = $self->session_engine;
1293             $engine && $self->_has_session or return $new_request;
1294             my $name = $engine->cookie_name;
1295 7     7 1 16 exists $new_request->cookies->{$name} and return $new_request;
1296 7         144 $new_request->cookies->{$name} =
1297             Dancer2::Core::Cookie->new( name => $name, value => $self->session->id );
1298              
1299 7 50       308 return $new_request;
1300             }
1301              
1302              
1303             # DISPATCHER
1304 2     2 1 5 my $self = shift;
1305 2         35  
1306             # build engines
1307             {
1308 2 50       89 for ( qw<logger session template> ) {
1309             my $attr = "${_}_engine";
1310             $self->$attr;
1311             }
1312              
1313 44     44 1 64 # the serializer engine does not have a default
1314 44         70 # and is the only engine that can actually not have a value
1315 44         64 if ( $self->config->{'serializer'} ) {
1316 44         73 $self->serializer_engine;
1317             }
1318 44         113 }
1319              
1320 44 50       226 $self->finish;
1321              
1322             my $psgi = sub {
1323             my $env = shift;
1324              
1325             # pre-request sanity check
1326             my $method = uc $env->{'REQUEST_METHOD'};
1327             $Dancer2::Core::Types::supported_http_methods{$method}
1328             or return [
1329 53     53 0 256 405,
1330 53         80 [ 'Content-Type' => 'text/plain' ],
1331 53         70 [ "Method Not Allowed\n\n$method is not supported." ]
1332 53         82 ];
1333              
1334 53         127 my $response;
1335             eval {
1336 53 100       143 $EVAL_SHIM->(sub{ $response = $self->dispatch($env)->to_psgi });
1337             1;
1338             } or do {
1339 53         224 my $err = $@ || "Zombie Error";
1340             return [
1341             500,
1342             [ 'Content-Type' => 'text/plain' ],
1343             [ "Internal Server Error\n\n$err" ],
1344 53         1137 ];
1345 53 100 66     1355 };
1346 11         25  
1347 11 100       34 return $response;
1348 10         152 };
1349              
1350             # Only add static content handler if required
1351 10         30 if ( $self->config->{'static_handler'} ) {
1352             # Use App::File to "serve" the static content
1353             my $static_app = Plack::App::File->new(
1354 1     1 1 1689 root => $self->config->{public_dir},
1355             content_type => sub { $self->mime_type->for_file( $_[0] ) },
1356             )->to_app;
1357             # Conditionally use the static handler wrapped with ConditionalGET
1358 200     200 0 837 # when the file exists. Otherwise the request passes into our app.
1359             $psgi = Plack::Middleware::Conditional->wrap(
1360             $psgi,
1361             condition => sub { -f path( $self->config->{public_dir}, shift->{PATH_INFO} ) },
1362 200         375 builder => sub { Plack::Middleware::ConditionalGET->wrap( $static_app ) },
  200         577  
1363 600         15375 );
1364 600         10370 }
1365              
1366             # Wrap with common middleware
1367             if ( ! $self->config->{'no_default_middleware'} ) {
1368             # FixMissingBodyInRedirect
1369 200 100       15407 $psgi = Plack::Middleware::FixMissingBodyInRedirect->wrap( $psgi );
1370 20         494 # Apply Head. After static so a HEAD request on static content DWIM.
1371             $psgi = Plack::Middleware::Head->wrap( $psgi );
1372             }
1373              
1374 200         2709 return $psgi;
1375             }
1376              
1377 584     584   1098244 my $self = shift;
1378             my $env = shift;
1379              
1380 584         1667 my $runner = Dancer2::runner();
1381 584 100       2394 my $request;
1382             my $request_built_successfully = eval {
1383             $EVAL_SHIM->(sub {
1384             $request = $runner->{'internal_request'} || $self->build_request($env);
1385             });
1386             1;
1387             };
1388 582         1004 # Catch bad content causing deserialization to fail when building the request
1389             if ( ! $request_built_successfully ) {
1390 582         4166 my $err = $@;
  582         2024  
1391 582         6803 Scalar::Util::weaken(my $app = $self);
1392 582 50       1077 return Dancer2::Core::Error->new(
1393 0   0     0 app => $app,
1394             message => $err,
1395 0         0 status => 400, # 400 Bad request (dont send again), rather than 500
1396             )->throw;
1397             }
1398              
1399             my $cname = $self->session_engine->cookie_name;
1400             my $defined_engines = $self->defined_engines;
1401 582         6437  
1402 200         1136 DISPATCH:
1403             while (1) {
1404             my $http_method = lc $request->method;
1405 200 100       3028 my $path_info = $request->path_info;
1406              
1407             # Add request to app and engines
1408             $self->set_request($request, $defined_engines);
1409 8     8   1661  
1410 111         2235 $self->log( core => "looking for $http_method $path_info" );
1411              
1412             ROUTE:
1413             foreach my $route ( @{ $self->routes->{$http_method} } ) {
1414             #warn "testing route " . $route->regexp . "\n";
1415 419     419   1898466 # TODO store in route cache
1416 111     111   7478  
1417 111         6072 # go to the next route if no match
1418             my $match = $route->match($request)
1419             or next ROUTE;
1420              
1421 200 100       7347 $request->_set_route_params($match);
1422             $request->_set_route_parameters($match);
1423 196         3032 $request->_set_route($route);
1424              
1425 196         7674 # Add session to app *if* we have a session and the request
1426             # has the appropriate cookie header for _this_ app.
1427             if ( my $sess = $runner->{'internal_sessions'}{$cname} ) {
1428 200         6473 $self->set_session($sess);
1429             }
1430              
1431             # calling the actual route
1432 582     582 0 978 my $response;
1433 582         944  
1434             # this is very evil, but allows breaking out of multiple stack
1435 582         2274 # frames without throwing an exception. Avoiding exceptions means
1436 582         899 # a naive eval won't swallow our flow control mechanisms, and
1437 582         964 # avoids __DIE__ handlers. It also prevents some cleanup routines
1438             # from working, since they are expecting control to return to them
1439 582   100 582   3487 # after an eval.
1440 582         2749 DANCER2_CORE_APP_ROUTE_RETURN: {
1441 578         2312 if (!$self->has_with_return) {
1442             $self->set_with_return(sub {
1443             $response = shift;
1444 582 100       2023 no warnings 'exiting';
1445 4         11 last DANCER2_CORE_APP_ROUTE_RETURN;
1446 4         21 });
1447 4         35 }
1448             $response = $self->_dispatch_route($route);
1449             };
1450              
1451             # ensure we clear the with_return handler
1452             $self->clear_with_return;
1453              
1454 578         13044 # handle forward requests
1455 578         6772 if ( ref $response eq 'Dancer2::Core::Request' ) {
1456             # this is actually a request, not response
1457             # however, we need to clean up the request & response
1458 578         25162 $self->clear_request;
1459 605         2505 $self->clear_response;
1460 605         4930  
1461             # this is in case we're asked for an old-style dispatching
1462             if ( $runner->{'internal_dispatch'} ) {
1463 605         4358 # Get the session object from the app before we clean up
1464             # the request context, so we can propagate this to the
1465 605         55625 # next dispatch cycle (if required).
1466             $self->_has_session
1467             and $runner->{'internal_sessions'}{$cname} =
1468 605         1123 $self->session;
  605         8965  
1469              
1470             $runner->{'internal_forward'} = 1;
1471             $runner->{'internal_request'} = $response;
1472             return $self->response_not_found($request);
1473 1159 100       7456 }
1474              
1475             $request = $response;
1476 547         16566 next DISPATCH;
1477 547         2364 }
1478 547         18071  
1479             # from here we assume the response is a Dancer2::Core::Response
1480              
1481             # halted response, don't process further
1482 547 100       2057 if ( $response->is_halted ) {
1483 6         105 $self->cleanup;
1484             delete $runner->{'internal_request'};
1485             return $response;
1486             }
1487 547         1018  
1488             # pass the baton if the response says so...
1489             if ( $response->has_passed ) {
1490             ## A previous route might have used splat, failed
1491             ## this needs to be cleaned from the request.
1492             exists $request->{_params}{splat}
1493             and delete $request->{_params}{splat};
1494              
1495             $response->has_passed(0); # clear for the next round
1496 547 50       825  
  547         2458  
1497             # clear the content because if you pass it,
1498 102     102   382 # the next route is in charge of catching it
1499 142     142   1856 $response->clear_content;
  142         341  
  142         109941  
1500 102         566 next ROUTE;
1501 547         3391 }
1502              
1503 547         1736 # it's just a regular response
1504             $self->execute_hook( 'core.app.after_request', $response );
1505             $self->cleanup;
1506             delete $runner->{'internal_request'};
1507 547         10064  
1508             return $response;
1509             }
1510 547 100       4689  
1511             # we don't actually want to continue the loop
1512             last;
1513 44         656 }
1514 44         729  
1515             # No response! ensure Core::Dispatcher recognizes this failure
1516             # so it can try the next Core::App
1517 44 100       348 # and set the created request so we don't create it again
1518             # (this is important so we don't ignore the previous body)
1519             if ( $runner->{'internal_dispatch'} ) {
1520             $runner->{'internal_404'} = 1;
1521             $runner->{'internal_request'} = $request;
1522 17 100       148 }
1523              
1524             # Render 404 response, cleanup, and return the response.
1525 17         75 my $response = $self->response_not_found($request);
1526 17         27 $self->cleanup;
1527 17         39 return $response;
1528             }
1529              
1530 27         46 my ( $self, $env ) = @_;
1531 27         72  
1532             # If we have an app, send the serialization engine
1533             my $request = Dancer2::Core::Request->new(
1534             env => $env,
1535             is_behind_proxy => $self->settings->{'behind_proxy'} || 0,
1536              
1537 503 100       9280 $self->has_serializer_engine
1538 34         323 ? ( serializer => $self->serializer_engine )
1539 34         442 : (),
1540 34         417 );
1541              
1542             return $request;
1543             }
1544 469 100       8528  
1545             # Call any before hooks then the matched route.
1546             my ( $self, $route ) = @_;
1547              
1548 9 100       71 local $@;
1549             eval {
1550 9         160 $EVAL_SHIM->(sub {
1551             $self->execute_hook( 'core.app.before_request', $self );
1552             });
1553             1;
1554 9         302 } or do {
1555 9         62 my $err = $@ || "Zombie Error";
1556             return $self->response_internal_error($err);
1557             };
1558             my $response = $self->response;
1559 460         9829  
1560 460         1744 if ( $response->is_halted ) {
1561 460         2561 return $self->_prep_response( $response );
1562             }
1563 460         5318  
1564             eval {
1565             $EVAL_SHIM->(sub{ $response = $route->execute($self) });
1566             1;
1567 67         176 } or do {
1568             my $err = $@ || "Zombie Error";
1569             return $self->response_internal_error($err);
1570             };
1571              
1572             return $response;
1573             }
1574 67 100       192  
1575 51         97 my ( $self, $response, $content ) = @_;
1576 51         97  
1577             # The response object has no back references to the content or app
1578             # Update the default_content_type of the response if any value set in
1579             # config so it can be applied when the response is encoded/returned.
1580 67         191 my $config = $self->config;
1581 67         185 if ( exists $config->{content_type}
1582 67         499 and my $ct = $config->{content_type} ) {
1583             $response->default_content_type($ct);
1584             }
1585              
1586 533     533 0 1345 # if we were passed any content, set it in the response
1587             defined $content && $response->content($content);
1588             return $response;
1589             }
1590              
1591 533 100 100     1926 my ( $self, $error ) = @_;
1592              
1593             $self->execute_hook( 'core.app.route_exception', $self, $error );
1594             $self->log( error => "Route exception: $error" );
1595              
1596             local $Dancer2::Core::Route::REQUEST = $self->request;
1597             local $Dancer2::Core::Route::RESPONSE = $self->response;
1598 529         3151  
1599             return Dancer2::Core::Error->new(
1600             app => $self,
1601             status => 500,
1602             exception => $error,
1603 547     547   1171 )->throw;
1604             }
1605 547         840  
1606             my ( $self, $request ) = @_;
1607              
1608 547     547   12315 $self->set_request($request);
1609 547         2425  
1610 538         6452 local $Dancer2::Core::Route::REQUEST = $self->request;
1611 547 100       1026 local $Dancer2::Core::Route::RESPONSE = $self->response;
1612 4   50     147  
1613 4         23 my $response = Dancer2::Core::Error->new(
1614             app => $self,
1615 538         7993 status => 404,
1616             message => $request->path,
1617 538 100       10176 )->throw;
1618 2         21  
1619             $self->cleanup;
1620              
1621             return $response;
1622 536     536   4494 }
  536         2244  
1623 426         2126  
1624 536 100       3815 1;
1625 13   50     665  
1626 13         77  
1627             =pod
1628              
1629 426         1104 =encoding UTF-8
1630              
1631             =head1 NAME
1632              
1633 449     449   1187 Dancer2::Core::App - encapsulation of Dancer2 packages
1634              
1635             =head1 VERSION
1636              
1637             version 0.400000
1638 449         7346  
1639 449 50 33     5066 =head1 DESCRIPTION
1640              
1641 449         7072 Everything a package that uses Dancer2 does is encapsulated into a
1642             C<Dancer2::Core::App> instance. This class defines all that can be done in such
1643             objects.
1644              
1645 449 100       19834 Mainly, it will contain all the route handlers, the configuration settings and
1646 449         42540 the hooks that are defined in the calling package.
1647              
1648             Note that with Dancer2, everything that is done within a package is scoped to
1649             that package, thanks to that encapsulation.
1650 17     17 0 48  
1651             =head1 ATTRIBUTES
1652 17         478  
1653 17         250 =head2 plugins
1654              
1655 17         221 =head2 runner_config
1656 17         258  
1657             =head2 default_config
1658 17         327  
1659             =head2 with_return
1660              
1661             Used to cache the coderef that will return from back to the dispatcher, across
1662             an arbitrary number of stack frames.
1663              
1664             =head2 destroyed_session
1665              
1666 93     93 0 191 We cache a destroyed session here; once this is set we must not attempt to
1667             retrieve the session from the cookie in the request. If no new session is
1668 93         233 created, this is set (with expiration) as a cookie to force the browser to
1669             expire the cookie.
1670 93         7325  
1671 93         1312 =head1 METHODS
1672              
1673 93         5795 =head2 has_session
1674              
1675             Returns true if session engine has been defined and if either a session
1676             object has been instantiated or if a session cookie was found and not
1677             subsequently invalidated.
1678              
1679 93         961 =head2 change_session_id
1680              
1681 93         731 Changes the session ID used by the current session. This should be used on
1682             any change of privilege level, for example on login. Returns the new session
1683             ID.
1684              
1685             =head2 destroy_session
1686              
1687             Destroys the current session and ensures any subsequent session is created
1688             from scratch and not from the request session cookie
1689              
1690             =head2 register_plugin
1691              
1692             =head2 with_plugins( @plugin_names )
1693              
1694             Creates instances of the given plugins and tie them to the app.
1695             The plugin classes are automatically loaded.
1696             Returns the newly created plugins.
1697              
1698             The plugin names are expected to be without the leading C<Dancer2::Plugin>.
1699             I.e., use C<Foo> to mean C<Dancer2::Plugin::Foo>.
1700              
1701             If a given plugin is already tied to the app, the already-existing
1702             instance will be used and returned by C<with_plugins> (think of it
1703             as using a role).
1704              
1705             my @plugins = $app->with_plugins( 'Foo', 'Bar' );
1706              
1707             # now $app uses the plugins Dancer2::Plugin::Foo
1708             # and Dancer2::Plugin::Bar
1709              
1710             =head2 with_plugin( $plugin_name )
1711              
1712             Just like C<with_plugin>, but for a single plugin.
1713              
1714             my $plugin = $app->with_plugin('Foo');
1715              
1716             =head2 add_route
1717              
1718             Register a new route handler.
1719              
1720             $app->add_route(
1721             method => 'get',
1722             regexp => '/somewhere',
1723             code => sub { ... },
1724             options => $conditions,
1725             );
1726              
1727             Returns a new L<< Dancer2::Core::Route >> object created with the passed
1728             arguments.
1729              
1730             =head2 route_exists
1731              
1732             Returns a true value if a route already exists, otherwise false.
1733              
1734             my $route = Dancer2::Core::Route->new(...);
1735             if ($app->route_exists($route)) {
1736             ...
1737             }
1738              
1739             =head2 routes_regexps_for
1740              
1741             Sugar for getting the ordered list of all registered route regexps by method.
1742              
1743             my $regexps = $app->routes_regexps_for( 'get' );
1744              
1745             Returns an ArrayRef with the results.
1746              
1747             =head2 redirect($destination, $status)
1748              
1749             Sets a redirect in the response object. If $destination is not an absolute URI, then it will
1750             be made into an absolute URI, relative to the URI in the request.
1751              
1752             =head2 halt
1753              
1754             Flag the response object as 'halted'.
1755              
1756             If called during request dispatch, immediately returns the response
1757             to the dispatcher and after hooks will not be run.
1758              
1759             =head2 pass
1760              
1761             Flag the response object as 'passed'.
1762              
1763             If called during request dispatch, immediately returns the response
1764             to the dispatcher.
1765              
1766             =head2 forward
1767              
1768             Create a new request which is a clone of the current one, apart
1769             from the path location, which points instead to the new location.
1770             This is used internally to chain requests using the forward keyword.
1771              
1772             This method takes 3 parameters: the url to forward to, followed by an
1773             optional hashref of parameters added to the current request parameters,
1774             followed by a hashref of options regarding the redirect, such as
1775             C<method> to change the request method.
1776              
1777             For example:
1778              
1779             forward '/login', { login_failed => 1 }, { method => 'GET' });
1780              
1781             =head2 app
1782              
1783             Returns itself. This is simply available as a shim to help transition from
1784             a previous version in which hooks were sent a context object (originally
1785             C<Dancer2::Core::Context>) which has since been removed.
1786              
1787             # before
1788             hook before => sub {
1789             my $ctx = shift;
1790             my $app = $ctx->app;
1791             };
1792              
1793             # after
1794             hook before => sub {
1795             my $app = shift;
1796             };
1797              
1798             This meant that C<< $app->app >> would fail, so this method has been provided
1799             to make it work.
1800              
1801             # now
1802             hook before => sub {
1803             my $WannaBeCtx = shift;
1804             my $app = $WannaBeContext->app; # works
1805             };
1806              
1807             =head2 lexical_prefix
1808              
1809             Allow for setting a lexical prefix
1810              
1811             $app->lexical_prefix('/blog', sub {
1812             ...
1813             });
1814              
1815             All the route defined within the callback will have a prefix appended to the
1816             current one.
1817              
1818             =head2 C< $SIG{__DIE__} > Compatibility via C< $Dancer2::Core::App::EVAL_SHIM >
1819              
1820             If an installation wishes to use C< $SIG{__DIE__} > hooks to enhance
1821             their error handling then it may be required to ensure that certain
1822             bookkeeping code is executed within every C<eval BLOCK> that Dancer2
1823             performs. This can be accomplished by overriding the global variable
1824             C<$Dancer2::Core::App::EVAL_SHIM> with a subroutine which does whatever
1825             logic is required.
1826              
1827             This routine must perform the equivalent of the following subroutine:
1828              
1829             our $EVAL_SHIM = sub {
1830             my $code = shift;
1831             return $code->(@_);
1832             };
1833              
1834             An example of overriding this sub might be as follows:
1835              
1836             $Dancer2::Core::App::EVAL_SHIM = sub {
1837             my $code = shift;
1838             local $IGNORE_EVAL_COUNTER = $IGNORE_EVAL_COUNTER + 1;
1839             return $code->(@_);
1840             };
1841              
1842             B<Note:> that this is a GLOBAL setting, which must be set up before
1843             any form of dispatch or use of Dancer2.
1844              
1845             =head1 AUTHOR
1846              
1847             Dancer Core Developers
1848              
1849             =head1 COPYRIGHT AND LICENSE
1850              
1851             This software is copyright (c) 2022 by Alexis Sukrieh.
1852              
1853             This is free software; you can redistribute it and/or modify it under
1854             the same terms as the Perl 5 programming language system itself.
1855              
1856             =cut