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 = '1.0.0';
4 143     143   208187 use Moo;
  143         21696  
  143         1273  
5 143     143   60969 use Carp 'croak';
  143         531  
  143         8777  
6 143     143   3259 use Module::Runtime 'require_module';
  143         7643  
  143         1471  
7 143     143   9354 use Dancer2::Core::MIME;
  143         466  
  143         6952  
8 143     143   1268 use Dancer2::Core::Types;
  143         503  
  143         1408  
9 143     143   1981585 use Dancer2::Core::Dispatcher;
  143         560  
  143         5022  
10 143     143   79944 use Plack::Builder qw();
  143         371878  
  143         4034  
11 143     143   1176 use Ref::Util qw< is_ref is_regexpref >;
  143         418  
  143         194902  
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   413 my $self = shift;
79              
80 1         5 require_module('HTTP::Server::PSGI');
81 1         39645 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 158     158   1880 my $self = shift;
91              
92             $ENV{PLACK_ENV}
93 158 100       871 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 158 100 100     7385 1 ),
    100 100        
      50        
      50        
107             };
108             }
109              
110             sub BUILD {
111 158     158 0 4172 my $self = shift;
112              
113             # Enable traces if set by ENV var.
114 158 100       3125 if (my $traces = $self->config->{traces} ) {
115 1         42 require_module('Carp');
116 1 50       41 $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 158 100       5275 defined $Dancer2::runner
123             or $Dancer2::runner = $self;
124             }
125              
126             sub register_application {
127 201     201 0 3815 my $self = shift;
128 201         448 my $app = shift;
129              
130 201         432 push @{ $self->apps }, $app;
  201         1138  
131              
132             # add postponed hooks to our psgi app
133 201         1466 $self->add_postponed_hooks( $app->name, $app->postponed_hooks );
134             }
135              
136             sub add_postponed_hooks {
137 201     201 0 465 my $self = shift;
138 201         434 my $name = shift;
139 201         365 my $hooks = shift;
140              
141             # merge postponed hooks
142 201         402 @{ $self->{'postponed_hooks'}{$name} }{ keys %{$hooks} } = values %{$hooks};
  201         1148  
  201         558  
  201         765  
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 887 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       35 $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 453 my $self = shift;
164 1         3 my $app = shift;
165              
166             # does not return
167 1         5 $self->print_banner;
168 1         27 $self->server->run($app);
169             }
170              
171             sub psgi_app {
172 31     31 0 121 my ($self, $apps) = @_;
173              
174 31 100 66     195 if ( $apps && @{$apps} ) {
  8         34  
175 8         18 my @found_apps = ();
176              
177 8         18 foreach my $app_req ( @{$apps} ) {
  8         20  
178 14 100       60 if ( is_regexpref($app_req) ) {
    100          
    50          
179             # find it in the apps registry
180             push @found_apps,
181 6         12 grep +( $_->name =~ $app_req ), @{ $self->apps };
  6         85  
182             } elsif ( ref $app_req eq 'Dancer2::Core::App' ) {
183             # use it directly
184 4         10 push @found_apps, $app_req;
185             } elsif ( !is_ref($app_req) ) {
186             # find it in the apps registry
187             push @found_apps,
188 4         8 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         23 $apps = \@found_apps;
195             } else {
196             # dispatch over all apps by default
197 23         132 $apps = $self->apps;
198             }
199              
200 31         557 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         43566 $dispatcher->apps_psgi;
207              
208             return sub {
209 74     74   474375 my $env = shift;
210              
211             # mark it as an old-style dispatching
212 74         311 $self->{'internal_dispatch'} = 1;
213              
214 74         361 my $response = $dispatcher->dispatch($env);
215              
216             # unmark it
217 74         306 delete $self->{'internal_dispatch'};
218              
219             # cleanup
220 74         156 delete $self->{'internal_sessions'};
221              
222 74         499 return $response;
223 31         1333 };
224             }
225              
226             sub print_banner {
227 1     1 0 2 my $self = shift;
228 1         3 my $pid = $$;
229              
230             # we only print the info if we need to
231 1 50       31 $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 1.0.0
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