File Coverage

blib/lib/Dancer2/Core/Runner.pm
Criterion Covered Total %
statement 80 90 88.8
branch 20 26 76.9
condition 8 11 72.7
subroutine 18 18 100.0
pod 0 7 0.0
total 126 152 82.8


line stmt bran cond sub pod time code
1             package Dancer2::Core::Runner;
2             # ABSTRACT: Top-layer class to start a dancer app
3             $Dancer2::Core::Runner::VERSION = '0.400001';
4 142     142   204851 use Moo;
  142         20739  
  142         1133  
5 142     142   58113 use Carp 'croak';
  142         377  
  142         9347  
6 142     142   2957 use Module::Runtime 'require_module';
  142         7245  
  142         1345  
7 142     142   9288 use Dancer2::Core::MIME;
  142         395  
  142         6987  
8 142     142   1038 use Dancer2::Core::Types;
  142         373  
  142         1384  
9 142     142   1896818 use Dancer2::Core::Dispatcher;
  142         532  
  142         4945  
10 142     142   72870 use Plack::Builder qw();
  142         339470  
  142         3789  
11 142     142   1054 use Ref::Util qw< is_ref is_regexpref >;
  142         353  
  142         182163  
12              
13             # Hashref of configurable items for the runner.
14             # Defaults come from ENV vars. Updated via global triggers
15             # from app configs.
16             has config => (
17             is => 'ro',
18             isa => HashRef,
19             lazy => 1,
20             builder => '_build_config',
21             );
22              
23             # FIXME: i hate this
24             has mime_type => (
25             is => 'ro',
26             isa => InstanceOf ['Dancer2::Core::MIME'],
27             default => sub { Dancer2::Core::MIME->new(); },
28             );
29              
30             has server => (
31             is => 'ro',
32             isa => InstanceOf['HTTP::Server::PSGI'],
33             lazy => 1,
34             builder => '_build_server',
35             handles => ['run'],
36             );
37              
38             has apps => (
39             is => 'ro',
40             isa => ArrayRef,
41             default => sub { [] },
42             );
43              
44             has postponed_hooks => (
45             is => 'ro',
46             isa => HashRef,
47             default => sub { +{} },
48             );
49              
50             has environment => (
51             is => 'ro',
52             isa => Str,
53             required => 1,
54             default => sub {
55             $ENV{DANCER_ENVIRONMENT} || $ENV{PLACK_ENV} || 'development'
56             },
57             );
58              
59             has host => (
60             is => 'ro',
61             lazy => 1,
62             default => sub { $_[0]->config->{'host'} },
63             );
64              
65             has port => (
66             is => 'ro',
67             lazy => 1,
68             default => sub { $_[0]->config->{'port'} },
69             );
70              
71             has timeout => (
72             is => 'ro',
73             lazy => 1,
74             default => sub { $_[0]->config->{'timeout'} },
75             );
76              
77             sub _build_server {
78 1     1   530 my $self = shift;
79              
80 1         6 require_module('HTTP::Server::PSGI');
81 1         38638 HTTP::Server::PSGI->new(
82             host => $self->host,
83             port => $self->port,
84             timeout => $self->timeout,
85             server_software => "Perl Dancer2 " . Dancer2->VERSION,
86             );
87             }
88              
89             sub _build_config {
90 157     157   1771 my $self = shift;
91              
92             $ENV{PLACK_ENV}
93 157 100       746 and $ENV{DANCER_APPHANDLER} = 'PSGI';
94              
95             return {
96             behind_proxy => 0,
97             apphandler => ( $ENV{DANCER_APPHANDLER} || 'Standalone' ),
98             traces => ( $ENV{DANCER_TRACES} || 0 ),
99             host => ( $ENV{DANCER_SERVER} || '0.0.0.0' ),
100             port => ( $ENV{DANCER_PORT} || '3000' ),
101             no_server_tokens => ( defined $ENV{DANCER_NO_SERVER_TOKENS} ?
102             $ENV{DANCER_NO_SERVER_TOKENS} :
103             0 ),
104             startup_info => ( defined $ENV{DANCER_STARTUP_INFO} ?
105             $ENV{DANCER_STARTUP_INFO} :
106 157 100 100     6038 1 ),
    100 100        
      50        
      50        
107             };
108             }
109              
110             sub BUILD {
111 157     157 0 3646 my $self = shift;
112              
113             # Enable traces if set by ENV var.
114 157 100       3010 if (my $traces = $self->config->{traces} ) {
115 1         28 require_module('Carp');
116 1 50       40 $Carp::Verbose = $traces ? 1 : 0;
117             };
118              
119             # set the global runner object if one doesn't exist yet
120             # this can happen if you create one without going through Dancer2
121             # which doesn't trigger the import that creates it
122 157 100       5030 defined $Dancer2::runner
123             or $Dancer2::runner = $self;
124             }
125              
126             sub register_application {
127 200     200 0 3927 my $self = shift;
128 200         445 my $app = shift;
129              
130 200         417 push @{ $self->apps }, $app;
  200         1144  
131              
132             # add postponed hooks to our psgi app
133 200         1521 $self->add_postponed_hooks( $app->name, $app->postponed_hooks );
134             }
135              
136             sub add_postponed_hooks {
137 200     200 0 471 my $self = shift;
138 200         441 my $name = shift;
139 200         401 my $hooks = shift;
140              
141             # merge postponed hooks
142 200         446 @{ $self->{'postponed_hooks'}{$name} }{ keys %{$hooks} } = values %{$hooks};
  200         1126  
  200         547  
  200         701  
143             }
144              
145             # decide what to start
146             # do we just return a PSGI app
147             # or do we actually start a development standalone server?
148             sub start {
149 2     2 0 1042 my $self = shift;
150 2         8 my $app = $self->psgi_app;
151              
152             # we decide whether we return a PSGI coderef
153             # or spin a local development PSGI server
154 2 50       37 $self->config->{'apphandler'} eq 'PSGI'
155             and return $app;
156              
157             # FIXME: this should not include the server tokens
158             # since those are already added to the server itself
159 0         0 $self->start_server($app);
160             }
161              
162             sub start_server {
163 1     1 0 540 my $self = shift;
164 1         3 my $app = shift;
165              
166             # does not return
167 1         5 $self->print_banner;
168 1         28 $self->server->run($app);
169             }
170              
171             sub psgi_app {
172 31     31 0 151 my ($self, $apps) = @_;
173              
174 31 100 66     187 if ( $apps && @{$apps} ) {
  8         39  
175 8         25 my @found_apps = ();
176              
177 8         17 foreach my $app_req ( @{$apps} ) {
  8         21  
178 14 100       51 if ( is_regexpref($app_req) ) {
    100          
    50          
179             # find it in the apps registry
180             push @found_apps,
181 6         13 grep +( $_->name =~ $app_req ), @{ $self->apps };
  6         89  
182             } elsif ( ref $app_req eq 'Dancer2::Core::App' ) {
183             # use it directly
184 4         12 push @found_apps, $app_req;
185             } elsif ( !is_ref($app_req) ) {
186             # find it in the apps registry
187             push @found_apps,
188 4         9 grep +( $_->name eq $app_req ), @{ $self->apps };
  4         33  
189             } else {
190 0         0 croak "Invalid input to psgi_app: $app_req";
191             }
192             }
193              
194 8         26 $apps = \@found_apps;
195             } else {
196             # dispatch over all apps by default
197 23         137 $apps = $self->apps;
198             }
199              
200 31         548 my $dispatcher = Dancer2::Core::Dispatcher->new( apps => $apps );
201              
202             # initialize psgi_apps
203             # (calls ->finish on the apps and create their PSGI apps)
204             # the dispatcher caches that in the attribute
205             # so ->finish isn't actually called again if you run this method
206 31         43348 $dispatcher->apps_psgi;
207              
208             return sub {
209 74     74   476049 my $env = shift;
210              
211             # mark it as an old-style dispatching
212 74         325 $self->{'internal_dispatch'} = 1;
213              
214 74         379 my $response = $dispatcher->dispatch($env);
215              
216             # unmark it
217 74         297 delete $self->{'internal_dispatch'};
218              
219             # cleanup
220 74         185 delete $self->{'internal_sessions'};
221              
222 74         540 return $response;
223 31         1245 };
224             }
225              
226             sub print_banner {
227 1     1 0 3 my $self = shift;
228 1         3 my $pid = $$;
229              
230             # we only print the info if we need to
231 1 50       26 $self->config->{'startup_info'} or return;
232              
233             # bare minimum
234 0           print STDERR ">> Dancer2 v" . Dancer2->VERSION . " server $pid listening "
235             . 'on http://'
236             . $self->host . ':'
237             . $self->port . "\n";
238              
239             # all loaded plugins
240 0           foreach my $module ( grep { $_ =~ m{^Dancer2/Plugin/} } keys %INC ) {
  0            
241 0           $module =~ s{/}{::}g; # change / to ::
242 0           $module =~ s{\.pm$}{}; # remove .pm at the end
243 0           my $version = $module->VERSION;
244              
245 0 0         defined $version or $version = 'no version number defined';
246 0           print STDERR ">> $module ($version)\n";
247             }
248             }
249              
250             1;
251              
252             __END__
253              
254             =pod
255              
256             =encoding UTF-8
257              
258             =head1 NAME
259              
260             Dancer2::Core::Runner - Top-layer class to start a dancer app
261              
262             =head1 VERSION
263              
264             version 0.400001
265              
266             =head1 AUTHOR
267              
268             Dancer Core Developers
269              
270             =head1 COPYRIGHT AND LICENSE
271              
272             This software is copyright (c) 2023 by Alexis Sukrieh.
273              
274             This is free software; you can redistribute it and/or modify it under
275             the same terms as the Perl 5 programming language system itself.
276              
277             =cut