File Coverage

blib/lib/Dancer2/Core/Runner.pm
Criterion Covered Total %
statement 80 90 88.8
branch 23 30 76.6
condition 6 8 75.0
subroutine 18 18 100.0
pod 0 7 0.0
total 127 153 83.0


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 = '2.1.0';
4 155     155   1287111 use Moo;
  155         49253  
  155         1297  
5 155     155   76993 use Carp 'croak';
  155         363  
  155         12129  
6 155     155   4480 use Module::Runtime 'require_module';
  155         14496  
  155         1483  
7 155     155   12565 use Dancer2::Core::MIME;
  155         720  
  155         5982  
8 155     155   1177 use Dancer2::Core::Types;
  155         480  
  155         1653  
9 155     155   2425610 use Dancer2::Core::Dispatcher;
  155         988  
  155         6828  
10 155     155   92920 use Plack::Builder qw();
  155         456625  
  155         5584  
11 155     155   1486 use Ref::Util qw< is_ref is_regexpref >;
  155         314  
  155         249947  
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             has mime_type => (
24             is => 'ro',
25             isa => InstanceOf ['Dancer2::Core::MIME'],
26             default => sub { Dancer2::Core::MIME->new(); },
27             );
28              
29             has server => (
30             is => 'ro',
31             isa => InstanceOf['HTTP::Server::PSGI'],
32             lazy => 1,
33             builder => '_build_server',
34             handles => ['run'],
35             );
36              
37             has apps => (
38             is => 'ro',
39             isa => ArrayRef,
40             default => sub { [] },
41             );
42              
43             has postponed_hooks => (
44             is => 'ro',
45             isa => HashRef,
46             default => sub { +{} },
47             );
48              
49             has environment => (
50             is => 'ro',
51             isa => Str,
52             required => 1,
53             default => sub {
54             $ENV{DANCER_ENVIRONMENT} || $ENV{PLACK_ENV} || 'development'
55             },
56             );
57              
58             has host => (
59             is => 'ro',
60             lazy => 1,
61             default => sub { $_[0]->config->{'host'} },
62             );
63              
64             has port => (
65             is => 'ro',
66             lazy => 1,
67             default => sub { $_[0]->config->{'port'} },
68             );
69              
70             has timeout => (
71             is => 'ro',
72             lazy => 1,
73             default => sub { $_[0]->config->{'timeout'} },
74             );
75              
76             sub _build_server {
77 1     1   631 my $self = shift;
78              
79 1         12 require_module('HTTP::Server::PSGI');
80 1         41280 my %args = (
81             host => $self->host,
82             port => $self->port,
83             timeout => $self->timeout,
84             );
85              
86 1 50       230 if ( !$self->config->{'no_server_tokens'} ) {
87 1         36 $args{'server_software'} = "Perl Dancer2 " . Dancer2->VERSION;
88             }
89              
90 1         12 return HTTP::Server::PSGI->new(%args);
91             }
92              
93             sub _build_config {
94 165     165   2032 my $self = shift;
95              
96             $ENV{PLACK_ENV}
97 165 100       837 and $ENV{DANCER_APPHANDLER} = 'PSGI';
98              
99             return {
100             behind_proxy => 0,
101             apphandler => ( $ENV{DANCER_APPHANDLER} || 'Standalone' ),
102             traces => ( $ENV{DANCER_TRACES} || 0 ),
103             host => ( $ENV{DANCER_SERVER} || '0.0.0.0' ),
104             port => ( $ENV{DANCER_PORT} || '3000' ),
105             no_server_tokens => ( defined $ENV{DANCER_NO_SERVER_TOKENS} ?
106             $ENV{DANCER_NO_SERVER_TOKENS} :
107             0 ),
108             startup_info => ( defined $ENV{DANCER_STARTUP_INFO} ?
109             $ENV{DANCER_STARTUP_INFO} :
110 165 100 100     7713 1 ),
    100 100        
      50        
      50        
111             };
112             }
113              
114             sub BUILD {
115 165     165 0 4377 my $self = shift;
116              
117             # Enable traces if set by ENV var.
118 165 100       3725 if (my $traces = $self->config->{traces} ) {
119 1         22 require_module('Carp');
120 1 50       37 $Carp::Verbose = $traces ? 1 : 0;
121             };
122              
123             # set the global runner object if one doesn't exist yet
124             # this can happen if you create one without going through Dancer2
125             # which doesn't trigger the import that creates it
126 165 100       6257 defined $Dancer2::runner
127             or $Dancer2::runner = $self;
128             }
129              
130             sub register_application {
131 215     215 0 4050 my $self = shift;
132 215         542 my $app = shift;
133              
134 215         636 push @{ $self->apps }, $app;
  215         1464  
135              
136             # add postponed hooks to our psgi app
137 215         2064 $self->add_postponed_hooks( $app->name, $app->postponed_hooks );
138             }
139              
140             sub add_postponed_hooks {
141 215     215 0 634 my $self = shift;
142 215         613 my $name = shift;
143 215         558 my $hooks = shift;
144              
145             # merge postponed hooks
146 215         589 @{ $self->{'postponed_hooks'}{$name} }{ keys %{$hooks} } = values %{$hooks};
  215         1544  
  215         756  
  215         786  
147             }
148              
149             # decide what to start
150             # do we just return a PSGI app
151             # or do we actually start a development standalone server?
152             sub start {
153 2     2 0 917 my $self = shift;
154 2         8 my $app = $self->psgi_app;
155              
156             # we decide whether we return a PSGI coderef
157             # or spin a local development PSGI server
158 2 50       30 $self->config->{'apphandler'} eq 'PSGI'
159             and return $app;
160              
161 0         0 $self->start_server($app);
162             }
163              
164             sub start_server {
165 1     1 0 543 my $self = shift;
166 1         2 my $app = shift;
167              
168             # does not return
169 1         6 $self->print_banner;
170 1         30 $self->server->run($app);
171             }
172              
173             sub psgi_app {
174 33     33 0 107 my ($self, $apps) = @_;
175              
176 33         79 my @found_apps;
177 33 100       78 foreach my $app_req ( @{ $apps || [] } ) {
  33         236  
178 14 100       88 if ( is_regexpref($app_req) ) {
    100          
    50          
179             # Asked to find app via regex pattern on its name
180             push @found_apps,
181 6         14 grep +( $_->name =~ $app_req ), @{ $self->apps };
  6         113  
182             } elsif ( ref $app_req eq 'Dancer2::Core::App' ) {
183             # Given Dancer2 App instance
184 4         10 push @found_apps, $app_req;
185             } elsif ( !is_ref($app_req) ) {
186             # Strings of the app names
187             push @found_apps,
188 4         7 grep +( $_->name eq $app_req ), @{ $self->apps };
  4         36  
189             } else {
190 0         0 croak "Invalid input to psgi_app: $app_req";
191             }
192             }
193              
194             # if specific apps, dispatch to them
195             # otherwise, dispatch over all apps by default
196 33 100       226 $apps = @found_apps ? \@found_apps : $self->apps;
197              
198 33         701 my $dispatcher = Dancer2::Core::Dispatcher->new( apps => $apps );
199              
200             # initialize psgi_apps
201             # (calls ->finish on the apps and create their PSGI apps)
202             # the dispatcher caches that in the attribute
203             # so ->finish isn't actually called again if you run this method
204 33         49951 $dispatcher->apps_psgi;
205              
206             return sub {
207 78     78   663516 my $env = shift;
208              
209             # mark it as an old-style dispatching
210 78         503 $self->{'internal_dispatch'} = 1;
211              
212 78         521 my $response = $dispatcher->dispatch($env);
213              
214             # unmark it
215 78         455 delete $self->{'internal_dispatch'};
216              
217             # cleanup
218 78         237 delete $self->{'internal_sessions'};
219              
220 78         924 return $response;
221 33         1395 };
222             }
223              
224             sub print_banner {
225 1     1 0 2 my $self = shift;
226 1         4 my $pid = $$;
227              
228             # we only print the info if we need to
229 1 50       49 $self->config->{'startup_info'} or return;
230              
231             # bare minimum
232 0           print STDERR ">> Dancer2 v" . Dancer2->VERSION . " server $pid listening "
233             . 'on http://'
234             . $self->host . ':'
235             . $self->port . "\n";
236              
237             # all loaded plugins
238 0           foreach my $module ( grep { $_ =~ m{^Dancer2/Plugin/} } keys %INC ) {
  0            
239 0           $module =~ s{/}{::}g; # change / to ::
240 0           $module =~ s{\.pm$}{}; # remove .pm at the end
241 0           my $version = $module->VERSION;
242              
243 0 0         defined $version or $version = 'no version number defined';
244 0           print STDERR ">> $module ($version)\n";
245             }
246             }
247              
248             1;
249              
250             __END__