Commit | Line | Data |
5c33dda5 |
1 | package Web::Simple::Application; |
2 | |
8bd060f4 |
3 | use Moo; |
5c33dda5 |
4 | |
876e62e1 |
5 | has 'config' => ( |
6 | is => 'ro', |
7 | default => sub { |
8 | my ($self) = @_; |
9 | +{ $self->default_config } |
10 | }, |
11 | trigger => sub { |
12 | my ($self, $value) = @_; |
13 | my %default = $self->default_config; |
14 | my @not = grep !exists $value->{$_}, keys %default; |
15 | @{$value}{@not} = @default{@not}; |
16 | } |
17 | ); |
5c33dda5 |
18 | |
445b3ea0 |
19 | sub default_config { () } |
39119082 |
20 | |
445b3ea0 |
21 | has '_dispatcher' => (is => 'lazy'); |
5c33dda5 |
22 | |
445b3ea0 |
23 | sub _build__dispatcher { |
24 | my $self = shift; |
25 | require Web::Dispatch; |
26 | require Web::Simple::DispatchNode; |
27 | my $final = $self->_build_final_dispatcher; |
28 | Web::Dispatch->new( |
29 | app => sub { $self->dispatch_request(@_), $final }, |
30 | node_class => 'Web::Simple::DispatchNode', |
31 | node_args => { app_object => $self } |
32 | ); |
5c33dda5 |
33 | } |
34 | |
3583ca04 |
35 | sub _build_final_dispatcher { |
4ed4fb42 |
36 | [ 404, [ 'Content-type', 'text/plain' ], [ 'Not found' ] ] |
5c33dda5 |
37 | } |
38 | |
5c33dda5 |
39 | sub run_if_script { |
b9e047ef |
40 | # ->to_psgi_app is true for require() but also works for plackup |
445b3ea0 |
41 | return $_[0]->to_psgi_app if caller(1); |
e27ab5c5 |
42 | my $self = ref($_[0]) ? $_[0] : $_[0]->new; |
5c33dda5 |
43 | $self->run(@_); |
44 | } |
45 | |
913a9cf9 |
46 | sub _run_cgi { |
5c33dda5 |
47 | my $self = shift; |
e27ab5c5 |
48 | require Plack::Server::CGI; |
b9e047ef |
49 | Plack::Server::CGI->run($self->to_psgi_app); |
d3a61609 |
50 | } |
51 | |
e27ab5c5 |
52 | sub _run_fcgi { |
53 | my $self = shift; |
54 | require Plack::Server::FCGI; |
b9e047ef |
55 | Plack::Server::FCGI->run($self->to_psgi_app); |
e27ab5c5 |
56 | } |
57 | |
445b3ea0 |
58 | sub to_psgi_app { |
bc57805c |
59 | my $self = ref($_[0]) ? $_[0] : $_[0]->new; |
445b3ea0 |
60 | $self->_dispatcher->to_app; |
5c33dda5 |
61 | } |
62 | |
913a9cf9 |
63 | sub run { |
64 | my $self = shift; |
e27ab5c5 |
65 | if ($ENV{PHP_FCGI_CHILDREN} || $ENV{FCGI_ROLE} || $ENV{FCGI_SOCKET_PATH}) { |
66 | return $self->_run_fcgi; |
67 | } elsif ($ENV{GATEWAY_INTERFACE}) { |
2514ad17 |
68 | return $self->_run_cgi; |
913a9cf9 |
69 | } |
4ba6d891 |
70 | unless (@ARGV && $ARGV[0] =~ m{^[A-Z/]}) { |
db2899c3 |
71 | return $self->_run_cli(@ARGV); |
d104fb1d |
72 | } |
73 | |
4ba6d891 |
74 | my @args = @ARGV; |
913a9cf9 |
75 | |
4ba6d891 |
76 | unshift(@args, 'GET') if $args[0] =~ m{^/}; |
77 | |
78 | $self->_run_test_request(@args); |
79 | } |
80 | |
81 | sub _run_test_request { |
82 | my ($self, $method, $path, @rest) = @_; |
83 | |
84 | require HTTP::Request; |
e27ab5c5 |
85 | require Plack::Test; |
913a9cf9 |
86 | |
4ba6d891 |
87 | my $request = HTTP::Request->new($method => $path); |
88 | if ($method eq 'POST' or $method eq 'PUT' and @rest) { |
89 | my $content = do { |
90 | require URI; |
91 | my $url = URI->new('http:'); |
92 | $url->query_form(@rest); |
93 | $url->query; |
94 | }; |
95 | $request->header('Content-Type' => 'application/x-www-form-urlencoded'); |
96 | $request->header('Content-Length' => length($content)); |
97 | $request->content($content); |
98 | } |
e27ab5c5 |
99 | my $response; |
4ba6d891 |
100 | Plack::Test::test_psgi( |
101 | $self->to_psgi_app, sub { $response = shift->($request) } |
102 | ); |
baabba33 |
103 | print STDERR $response->status_line."\n"; |
104 | print STDERR $response->headers_as_string("\n")."\n"; |
f9d0d382 |
105 | my $content = $response->content; |
106 | $content .= "\n" if length($content) and $content !~ /\n\z/; |
107 | print STDOUT $content if $content; |
913a9cf9 |
108 | } |
109 | |
d104fb1d |
110 | sub _run_cli { |
111 | my $self = shift; |
112 | die $self->_cli_usage; |
113 | } |
114 | |
115 | sub _cli_usage { |
116 | "To run this script in CGI test mode, pass a URL path beginning with /:\n". |
117 | "\n". |
118 | " $0 /some/path\n". |
119 | " $0 /\n" |
120 | } |
121 | |
5c33dda5 |
122 | 1; |
32d29dcd |
123 | |
124 | =head1 NAME |
125 | |
6a4808bf |
126 | Web::Simple::Application - A base class for your Web-Simple application |
32d29dcd |
127 | |
128 | =head1 DESCRIPTION |
129 | |
130 | This is a base class for your L<Web::Simple> application. You probably don't |
131 | need to construct this class yourself, since L<Web::Simple> does the 'heavy |
132 | lifting' for you in that regards. |
133 | |
134 | =head1 METHODS |
135 | |
6a4808bf |
136 | This class exposes the following public methods. |
32d29dcd |
137 | |
138 | =head2 default_config |
139 | |
6a4808bf |
140 | Merges with the C<config> initializer to provide configuration information for |
141 | your application. For example: |
32d29dcd |
142 | |
143 | sub default_config { |
144 | ( |
145 | title => 'Bloggery', |
146 | posts_dir => $FindBin::Bin.'/posts', |
147 | ); |
148 | } |
149 | |
6a4808bf |
150 | Now, the C<config> attribute of C<$self> will be set to a HashRef |
32d29dcd |
151 | containing keys 'title' and 'posts_dir'. |
152 | |
12b3e9a3 |
153 | The keys from default_config are merged into any config supplied, so |
154 | if you construct your application like: |
6a4808bf |
155 | |
12b3e9a3 |
156 | MyWebSimpleApp::Web->new( |
157 | config => { title => 'Spoon', environment => 'dev' } |
158 | ) |
6a4808bf |
159 | |
12b3e9a3 |
160 | then C<config> will contain: |
6a4808bf |
161 | |
12b3e9a3 |
162 | { |
163 | title => 'Spoon', |
164 | posts_dir => '/path/to/myapp/posts', |
165 | environment => 'dev' |
166 | } |
32d29dcd |
167 | |
12b3e9a3 |
168 | =head2 run_if_script |
32d29dcd |
169 | |
12b3e9a3 |
170 | The run_if_script method is designed to be used at the end of the script |
171 | or .pm file where your application class is defined - for example: |
32d29dcd |
172 | |
173 | ## my_web_simple_app.pl |
174 | #!/usr/bin/env perl |
175 | use Web::Simple 'HelloWorld'; |
176 | |
177 | { |
178 | package HelloWorld; |
179 | |
180 | sub dispatch_request { |
181 | sub (GET) { |
182 | [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ] |
183 | }, |
184 | sub () { |
185 | [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ] |
186 | } |
187 | } |
188 | } |
189 | |
190 | HelloWorld->run_if_script; |
191 | |
12b3e9a3 |
192 | This returns a true value, so your file is now valid as a module - so |
6a4808bf |
193 | |
12b3e9a3 |
194 | require 'my_web_simple_app.pl'; |
6a4808bf |
195 | |
12b3e9a3 |
196 | my $hw = HelloWorld->new; |
6a4808bf |
197 | |
12b3e9a3 |
198 | will work fine (and you can rename it to lib/HelloWorld.pm later to make it |
199 | a real use-able module). |
6a4808bf |
200 | |
12b3e9a3 |
201 | However, it detects if it's being run as a script (via testing $0) and if |
202 | so attempts to do the right thing. |
32d29dcd |
203 | |
12b3e9a3 |
204 | If run under a CGI environment, your application will execute as a CGI. |
32d29dcd |
205 | |
12b3e9a3 |
206 | If run under a FastCGI environment, your application will execute as a |
207 | FastCGI process (this works both for dynamic shared-hosting-style FastCGI |
208 | and for apache FastCgiServer style setups). |
32d29dcd |
209 | |
12b3e9a3 |
210 | If run from the commandline with a URL path, it runs a GET request against |
211 | that path - |
32d29dcd |
212 | |
12b3e9a3 |
213 | $ perl -Ilib examples/hello-world/hello-world.cgi / |
214 | 200 OK |
215 | Content-Type: text/plain |
216 | |
217 | Hello world! |
32d29dcd |
218 | |
12b3e9a3 |
219 | Additionally, you can treat the file as though it were a standard PSGI |
220 | application file (*.psgi). For example you can start up up with C<plackup> |
221 | |
222 | plackup my_web_simple_app.pl |
32d29dcd |
223 | |
12b3e9a3 |
224 | or C<starman> |
32d29dcd |
225 | |
12b3e9a3 |
226 | starman my_web_simple_app.pl |
227 | |
228 | =head2 to_psgi_app |
229 | |
230 | This method is called by L</run_if_script> to create the L<PSGI> app coderef |
231 | for use via L<Plack> and L<plackup>. If you want to globally add middleware, |
232 | you can override this method: |
6a4808bf |
233 | |
234 | use Web::Simple 'HelloWorld'; |
235 | use Plack::Builder; |
236 | |
237 | { |
238 | package HelloWorld; |
239 | |
240 | |
241 | around 'to_psgi_app', sub { |
242 | my ($orig, $self) = (shift, shift); |
243 | my $app = $self->$orig(@_); |
244 | builder { |
245 | enable ...; ## whatever middleware you want |
246 | $app; |
247 | }; |
248 | }; |
249 | } |
250 | |
12b3e9a3 |
251 | This method can also be used to mount a Web::Simple application within |
252 | a separate C<*.psgi> file - |
253 | |
254 | use strictures 1; |
255 | use Plack::Builder; |
256 | use WSApp; |
257 | use AnotherWSApp; |
258 | |
259 | builder { |
260 | mount '/' => WSApp->to_psgi_app; |
261 | mount '/another' => AnotherWSApp->to_psgi_app; |
262 | }; |
263 | |
264 | This method can be called as a class method, in which case it implicitly |
265 | calls ->new, or as an object method ... in which case it doesn't. |
32d29dcd |
266 | |
267 | =head2 run |
268 | |
269 | Used for running your application under stand-alone CGI and FCGI modes. Also |
270 | useful for testing: |
271 | |
272 | my $app = MyWebSimpleApp::Web->new; |
273 | my $c = HTTP::Request::AsCGI->new(@args)->setup; |
274 | $app->run; |
275 | |
7e103e8e |
276 | =head1 AUTHORS |
32d29dcd |
277 | |
7e103e8e |
278 | See L<Web::Simple> for authors. |
32d29dcd |
279 | |
7e103e8e |
280 | =head1 COPYRIGHT AND LICENSE |
32d29dcd |
281 | |
7e103e8e |
282 | See L<Web::Simple> for the copyright and license. |
32d29dcd |
283 | |
284 | =cut |