File Coverage

blib/lib/Web/Simple/Application.pm
Criterion Covered Total %
statement 58 82 70.7
branch 14 28 50.0
condition 10 24 41.6
subroutine 14 18 77.7
pod 5 5 100.0
total 101 157 64.3


line stmt bran cond sub pod time code
1             package Web::Simple::Application;
2              
3 15     15   74 use Scalar::Util 'weaken';
  15         23  
  15         875  
4              
5 15     15   70 use Moo;
  15         23  
  15         89  
6              
7             has 'config' => (
8             is => 'ro',
9             default => sub {
10             my ($self) = @_;
11             +{ $self->default_config }
12             },
13             trigger => sub {
14             my ($self, $value) = @_;
15             my %default = $self->default_config;
16             my @not = grep !exists $value->{$_}, keys %default;
17             @{$value}{@not} = @default{@not};
18             }
19             );
20              
21 14     14 1 351 sub default_config { () }
22              
23             has '_dispatcher' => (is => 'lazy');
24              
25             sub _build__dispatcher {
26 15     15   6317 my $self = shift;
27 15         12673 require Web::Dispatch;
28 15         213 my $final = $self->_build_final_dispatcher;
29              
30             # We need to weaken both the copy of $self that the
31             # app parameter will close over and the copy that'll
32             # be passed through as a node argument.
33             #
34             # To ensure that this doesn't then result in us being
35             # DESTROYed unexpectedly early, our to_psgi_app method
36             # closes back over $self
37              
38 15         68 weaken($self);
39             my %dispatch_args = (
40 65     65   302 dispatch_app => sub { $self->dispatch_request(@_), $final },
41 15         103 dispatch_object => $self
42             );
43 15         94 weaken($dispatch_args{dispatch_object});
44 15         94 Web::Dispatch->new(%dispatch_args);
45             }
46              
47             sub _build_final_dispatcher {
48 15     15   71 [ 404, [ 'Content-type', 'text/plain' ], [ 'Not found' ] ]
49             }
50              
51             sub run_if_script {
52             # ->to_psgi_app is true for require() but also works for plackup
53 1 50   1 1 12 return $_[0]->to_psgi_app if caller(1);
54 0 0       0 my $self = ref($_[0]) ? $_[0] : $_[0]->new;
55 0         0 $self->run(@_);
56             }
57              
58             sub _run_cgi {
59 2     2   5 my $self = shift;
60 2         902 require Plack::Handler::CGI;
61 2         1254 Plack::Handler::CGI->new->run($self->to_psgi_app);
62             }
63              
64             sub _run_fcgi {
65 0     0   0 my $self = shift;
66 0         0 require Plack::Handler::FCGI;
67 0         0 Plack::Handler::FCGI->new->run($self->to_psgi_app);
68             }
69              
70             sub to_psgi_app {
71 66 100   66 1 251 my $self = ref($_[0]) ? $_[0] : $_[0]->new;
72 66         1371 my $app = $self->_dispatcher->to_app;
73              
74             # Close over $self to keep $self alive even though
75             # we weakened the copies the dispatcher has; the
76             # if 0 causes the ops to be optimised away to
77             # minimise the performance impact and avoid void
78             # context warnings while still doing the closing
79             # over part. As Mithaldu said: "Gnarly." ...
80              
81 66     65   476 return sub { $self if 0; goto &$app; };
  65         82048  
  65         244  
82             }
83              
84             sub run {
85 2     2 1 20807 my $self = shift;
86 2 50 33     48 if (
    50 33        
      33        
      33        
87             $ENV{PHP_FCGI_CHILDREN} || $ENV{FCGI_ROLE} || $ENV{FCGI_SOCKET_PATH}
88             || ( -S STDIN && !$ENV{GATEWAY_INTERFACE} )
89             # If STDIN is a socket, almost certainly FastCGI, except for mod_cgid
90             ) {
91 0         0 return $self->_run_fcgi;
92             } elsif ($ENV{GATEWAY_INTERFACE}) {
93 2         19 return $self->_run_cgi;
94             }
95 0 0 0     0 unless (@ARGV && $ARGV[0] =~ m{(^[A-Z/])|\@}) {
96 0         0 return $self->_run_cli(@ARGV);
97             }
98              
99 0         0 my @args = @ARGV;
100              
101 0 0       0 unshift(@args, 'GET') if $args[0] !~ /^[A-Z]/;
102              
103 0         0 $self->_run_cli_test_request(@args);
104             }
105              
106             sub _test_request_spec_to_http_request {
107 60     60   134 my ($self, $method, $path, @rest) = @_;
108              
109             # if it's a reference, assume a request object
110 60 100       232 return $method if ref($method);
111              
112 47 100       167 if ($path =~ s/^(.*?)\@//) {
113 2         5 my $basic = $1;
114 2         7 require MIME::Base64;
115 2         56 unshift @rest, 'Authorization:', 'Basic '.MIME::Base64::encode($basic);
116             }
117              
118 47         270 my $request = HTTP::Request->new($method => $path);
119              
120 47         52953 my @params;
121              
122 47         193 while (my ($header, $value) = splice(@rest, 0, 2)) {
123 4 100       19 unless ($header =~ s/:$//) {
124 2         6 push @params, $header, $value;
125             }
126 4         9 $header =~ s/_/-/g;
127 4 50       13 if ($header eq 'Content') {
128 0         0 $request->content($value);
129             } else {
130 4         15 $request->headers->push_header($header, $value);
131             }
132             }
133              
134 47 100 100     439 if (($method eq 'POST' or $method eq 'PUT') and @params) {
      100        
135 2         4 my $content = do {
136 2         11 require URI;
137 2         8 my $url = URI->new('http:');
138 2         104 $url->query_form(@params);
139 2         215 $url->query;
140             };
141 2         27 $request->header('Content-Type' => 'application/x-www-form-urlencoded');
142 2         87 $request->header('Content-Length' => length($content));
143 2         76 $request->content($content);
144             }
145              
146 47         130 return $request;
147             }
148              
149             sub run_test_request {
150 60     60 1 1472388 my ($self, @req) = @_;
151              
152 60         5557 require HTTP::Request;
153              
154 60         163434 require Plack::Test;
155              
156 60         3761 my $request = $self->_test_request_spec_to_http_request(@req);
157              
158             Plack::Test::test_psgi(
159 60     60   296693 $self->to_psgi_app, sub { shift->($request) }
160 60         229 );
161             }
162              
163             sub _run_cli_test_request {
164 0     0     my ($self, @req) = @_;
165 0           my $response = $self->run_test_request(@req);
166              
167 0           binmode(STDOUT); binmode(STDERR); # for win32
  0            
168              
169 0           print STDERR $response->status_line."\n";
170 0           print STDERR $response->headers_as_string("\n")."\n";
171 0           my $content = $response->content;
172 0 0 0       $content .= "\n" if length($content) and $content !~ /\n\z/;
173 0 0         print STDOUT $content if $content;
174             }
175              
176             sub _run_cli {
177 0     0     my $self = shift;
178 0           die $self->_cli_usage;
179             }
180              
181             sub _cli_usage {
182 0     0     "To run this script in CGI test mode, pass a URL path beginning with /:\n".
183             "\n".
184             " $0 /some/path\n".
185             " $0 /\n"
186             }
187              
188             1;
189              
190             =head1 NAME
191              
192             Web::Simple::Application - A base class for your Web-Simple application
193              
194             =head1 DESCRIPTION
195              
196             This is a base class for your L application. You probably don't
197             need to construct this class yourself, since L does the 'heavy
198             lifting' for you in that regards.
199              
200             =head1 METHODS
201              
202             This class exposes the following public methods.
203              
204             =head2 default_config
205              
206             Merges with the C initializer to provide configuration information for
207             your application. For example:
208              
209             sub default_config {
210             (
211             title => 'Bloggery',
212             posts_dir => $FindBin::Bin.'/posts',
213             );
214             }
215              
216             Now, the C attribute of C<$self> will be set to a HashRef
217             containing keys 'title' and 'posts_dir'.
218              
219             The keys from default_config are merged into any config supplied, so
220             if you construct your application like:
221              
222             MyWebSimpleApp::Web->new(
223             config => { title => 'Spoon', environment => 'dev' }
224             )
225              
226             then C will contain:
227              
228             {
229             title => 'Spoon',
230             posts_dir => '/path/to/myapp/posts',
231             environment => 'dev'
232             }
233              
234             =head2 run_if_script
235              
236             The run_if_script method is designed to be used at the end of the script
237             or .pm file where your application class is defined - for example:
238              
239             ## my_web_simple_app.pl
240             #!/usr/bin/env perl
241             use Web::Simple 'HelloWorld';
242              
243             {
244             package HelloWorld;
245              
246             sub dispatch_request {
247             sub (GET) {
248             [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ]
249             },
250             sub () {
251             [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ]
252             }
253             }
254             }
255              
256             HelloWorld->run_if_script;
257              
258             This returns a true value, so your file is now valid as a module - so
259              
260             require 'my_web_simple_app.pl';
261              
262             my $hw = HelloWorld->new;
263              
264             will work fine (and you can rename it to lib/HelloWorld.pm later to make it
265             a real use-able module).
266              
267             However, it detects if it's being run as a script (via testing $0) and if
268             so attempts to do the right thing.
269              
270             If run under a CGI environment, your application will execute as a CGI.
271              
272             If run under a FastCGI environment, your application will execute as a
273             FastCGI process (this works both for dynamic shared-hosting-style FastCGI
274             and for apache FastCgiServer style setups).
275              
276             If run from the commandline with a URL path, it runs a GET request against
277             that path -
278              
279             $ perl -Ilib examples/hello-world/hello-world.cgi /
280             200 OK
281             Content-Type: text/plain
282            
283             Hello world!
284              
285             You can also provide a method name -
286              
287             $ perl -Ilib examples/hello-world/hello-world.cgi POST /
288             405 Method Not Allowed
289             Content-Type: text/plain
290            
291             Method not allowed
292              
293             For a POST or PUT request, pairs on the command line will be treated
294             as form variables. For any request, pairs on the command line ending in :
295             are treated as headers, and 'Content:' will set the request body -
296              
297             $ ./myapp POST / Accept: text/html form_field_name form_field_value
298            
299             $ ./myapp POST / Content-Type: text/json Content: '{ "json": "here" }'
300              
301             The body of the response is sent to STDOUT and the headers to STDERR, so
302              
303             $ ./myapp GET / >index.html
304              
305             will generally do the right thing.
306              
307             To send basic authentication credentials, use user:pass@ syntax -
308              
309             $ ./myapp GET bob:secret@/protected/path
310              
311             Additionally, you can treat the file as though it were a standard PSGI
312             application file (*.psgi). For example you can start up up with C
313              
314             plackup my_web_simple_app.pl
315              
316             or C
317              
318             starman my_web_simple_app.pl
319              
320             =head2 to_psgi_app
321              
322             This method is called by L to create the L app coderef
323             for use via L and L. If you want to globally add middleware,
324             you can override this method:
325              
326             use Web::Simple 'HelloWorld';
327             use Plack::Builder;
328            
329             {
330             package HelloWorld;
331              
332            
333             around 'to_psgi_app', sub {
334             my ($orig, $self) = (shift, shift);
335             my $app = $self->$orig(@_);
336             builder {
337             enable ...; ## whatever middleware you want
338             $app;
339             };
340             };
341             }
342              
343             This method can also be used to mount a Web::Simple application within
344             a separate C<*.psgi> file -
345              
346             use strictures 1;
347             use Plack::Builder;
348             use WSApp;
349             use AnotherWSApp;
350              
351             builder {
352             mount '/' => WSApp->to_psgi_app;
353             mount '/another' => AnotherWSApp->to_psgi_app;
354             };
355              
356             This method can be called as a class method, in which case it implicitly
357             calls ->new, or as an object method ... in which case it doesn't.
358              
359             =head2 run
360              
361             Used for running your application under stand-alone CGI and FCGI modes.
362              
363             I should document this more extensively but run_if_script will call it when
364             you need it, so don't worry about it too much.
365              
366             =head2 run_test_request
367              
368             my $res = $app->run_test_request(GET => '/' => %headers);
369              
370             my $res = $app->run_test_request(POST => '/' => %headers_or_form);
371              
372             my $res = $app->run_test_request($http_request);
373              
374             Accepts either an L object or ($method, $path) and runs that
375             request against the application, returning an L object.
376              
377             If the HTTP method is POST or PUT, then a series of pairs can be passed after
378             this to create a form style message body. If you need to test an upload, then
379             create an L object by hand or use the C subroutine
380             provided by L.
381              
382             If you prefix the URL with 'user:pass@' this will be converted into
383             an Authorization header for HTTP basic auth:
384              
385             my $res = $app->run_test_request(
386             GET => 'bob:secret@/protected/resource'
387             );
388              
389             If pairs are passed where the key ends in :, it is instead treated as a
390             headers, so:
391              
392             my $res = $app->run_test_request(
393             POST => '/',
394             'Accept:' => 'text/html',
395             some_form_key => 'value'
396             );
397              
398             will do what you expect. You can also pass a special key of Content: to
399             set the request body:
400              
401             my $res = $app->run_test_request(
402             POST => '/',
403             'Content-Type:' => 'text/json',
404             'Content:' => '{ "json": "here" }',
405             );
406              
407             =head1 AUTHORS
408              
409             See L for authors.
410              
411             =head1 COPYRIGHT AND LICENSE
412              
413             See L for the copyright and license.
414              
415             =cut