add files
|
1 | |
2 |
=encoding utf8 |
|
3 | ||
4 |
=head1 NAME |
|
5 | ||
6 |
Mojolicious::Guides::Cookbook - Cookbook |
|
7 | ||
8 |
=head1 OVERVIEW |
|
9 | ||
10 |
This document contains many fun recipes for cooking with L<Mojolicious>. |
|
11 | ||
12 |
=head1 DEPLOYMENT |
|
13 | ||
14 |
Getting L<Mojolicious> and L<Mojolicious::Lite> applications running on |
|
15 |
different platforms. Note that many real-time web features are based on the |
|
16 |
L<Mojo::IOLoop> event loop, and therefore require one of the built-in web |
|
17 |
servers to be able to use them to their full potential. |
|
18 | ||
19 |
=head2 Built-in web server |
|
20 | ||
21 |
L<Mojolicious> contains a very portable non-blocking I/O HTTP and WebSocket |
|
22 |
server with L<Mojo::Server::Daemon>. It is usually used during development and |
|
23 |
in the construction of more advanced web servers, but is solid and fast enough |
|
24 |
for small to mid sized applications. |
|
25 | ||
26 |
$ ./script/myapp daemon |
|
27 |
Server available at http://127.0.0.1:3000. |
|
28 | ||
29 |
It has many configuration options and is known to work on every platform Perl |
|
30 |
works on. |
|
31 | ||
32 |
$ ./script/myapp daemon -h |
|
33 |
...List of available options... |
|
34 | ||
35 |
Another huge advantage is that it supports TLS and WebSockets out of the box, |
|
36 |
a development certificate for testing purposes is built right in, so it just |
|
37 |
works, but you can specify all listen locations supported by |
|
38 |
L<Mojo::Server::Daemon/"listen">. |
|
39 | ||
40 |
$ ./script/myapp daemon -l https://*:3000 |
|
41 |
Server available at https://127.0.0.1:3000. |
|
42 | ||
43 |
On UNIX platforms you can also add preforking with L<Mojo::Server::Prefork>. |
|
44 | ||
45 |
$ ./script/myapp prefork |
|
46 |
Server available at http://127.0.0.1:3000. |
|
47 | ||
48 |
Since all built-in web servers are based on the L<Mojo::IOLoop> event loop, |
|
49 |
they scale best with non-blocking operations. But if your application for some |
|
50 |
reason needs to perform many blocking operations, you can improve performance |
|
51 |
by increasing the number of worker processes and decreasing the number of |
|
52 |
concurrent connections each worker is allowed to handle. |
|
53 | ||
54 |
$ ./script/myapp prefork -m production -w 10 -c 1 |
|
55 |
Server available at http://127.0.0.1:3000. |
|
56 | ||
57 |
Your application is preloaded in the manager process during startup, to run |
|
58 |
code whenever a new worker process has been forked you can use L<Mojo::IOLoop> |
|
59 |
timers. |
|
60 | ||
61 |
use Mojolicious::Lite; |
|
62 | ||
63 |
Mojo::IOLoop->timer(0 => sub { |
|
64 |
app->log->info("Worker $$ star...ALL GLORY TO THE HYPNOTOAD!"); |
|
65 |
}); |
|
66 | ||
67 |
get '/' => {text => 'Hello Wor...ALL GLORY TO THE HYPNOTOAD!'}; |
|
68 | ||
69 |
app->start; |
|
70 | ||
71 |
=head2 Morbo |
|
72 | ||
73 |
After reading the L<Mojolicious::Lite> tutorial, you should already be |
|
74 |
familiar with L<Mojo::Server::Morbo>. |
|
75 | ||
76 |
Mojo::Server::Morbo |
|
77 |
+- Mojo::Server::Daemon |
|
78 | ||
79 |
It is basically a restarter that forks a new L<Mojo::Server::Daemon> web |
|
80 |
server whenever a file in your project changes, and should therefore only be |
|
81 |
used during development. |
|
82 | ||
83 |
$ morbo script/myapp |
|
84 |
Server available at http://127.0.0.1:3000. |
|
85 | ||
86 |
=head2 Hypnotoad |
|
87 | ||
88 |
For bigger applications L<Mojolicious> contains the UNIX optimized preforking |
|
89 |
web server L<Mojo::Server::Hypnotoad> that will allow you to take advantage of |
|
90 |
multiple CPU cores and copy-on-write. |
|
91 | ||
92 |
Mojo::Server::Hypnotoad |
|
93 |
|- Mojo::Server::Daemon [1] |
|
94 |
|- Mojo::Server::Daemon [2] |
|
95 |
|- Mojo::Server::Daemon [3] |
|
96 |
+- Mojo::Server::Daemon [4] |
|
97 | ||
98 |
It is based on the L<Mojo::Server::Prefork> web server, which adds preforking |
|
99 |
to L<Mojo::Server::Daemon>, but optimized specifically for production |
|
100 |
environments out of the box. |
|
101 | ||
102 |
$ hypnotoad script/myapp |
|
103 |
Server available at http://127.0.0.1:8080. |
|
104 | ||
105 |
You can tweak many configuration settings right from within your application |
|
106 |
with L<Mojo/"config">, for a full list see |
|
107 |
L<Mojo::Server::Hypnotoad/"SETTINGS">. |
|
108 | ||
109 |
use Mojolicious::Lite; |
|
110 | ||
111 |
app->config(hypnotoad => {listen => ['http://*:80']}); |
|
112 | ||
113 |
get '/' => {text => 'Hello Wor...ALL GLORY TO THE HYPNOTOAD!'}; |
|
114 | ||
115 |
app->start; |
|
116 | ||
117 |
Or just add a C<hypnotoad> section to your L<Mojolicious::Plugin::Config> or |
|
118 |
L<Mojolicious::Plugin::JSONConfig> configuration file. |
|
119 | ||
120 |
# myapp.conf |
|
121 |
{ |
|
122 |
hypnotoad => { |
|
123 |
listen => ['https://*:443?cert=/etc/server.crt&key=/etc/server.key'], |
|
124 |
workers => 10 |
|
125 |
} |
|
126 |
}; |
|
127 | ||
128 |
But one of its biggest advantages is the support for effortless zero downtime |
|
129 |
software upgrades (hot deployment). That means you can upgrade L<Mojolicious>, |
|
130 |
Perl or even system libraries at runtime without ever stopping the server or |
|
131 |
losing a single incoming connection, just by running the command above again. |
|
132 | ||
133 |
$ hypnotoad script/myapp |
|
134 |
Starting hot deployment for Hypnotoad server 31841. |
|
135 | ||
136 |
You might also want to enable proxy support if you're using Hypnotoad behind a |
|
137 |
reverse proxy. This allows L<Mojolicious> to automatically pick up the |
|
138 |
C<X-Forwarded-For> and C<X-Forwarded-HTTPS> headers. |
|
139 | ||
140 |
# myapp.conf |
|
141 |
{hypnotoad => {proxy => 1}}; |
|
142 | ||
143 |
=head2 Zero downtime software upgrades |
|
144 | ||
145 |
Hypnotoad makes zero downtime software upgrades (hot deployment) very simple, |
|
146 |
as you can see above, but on modern operating systems that support the |
|
147 |
C<SO_REUSEPORT> socket option, there is also another method available that |
|
148 |
works with all built-in web servers. |
|
149 | ||
150 |
$ ./script/myapp prefork -P /tmp/first.pid -l http://*:8080?reuse=1 |
|
151 |
Server available at http://127.0.0.1:8080. |
|
152 | ||
153 |
All you have to do is start a second web server listening to the same port and |
|
154 |
stop the first web server gracefully afterwards. |
|
155 | ||
156 |
$ ./script/myapp prefork -P /tmp/second.pid -l http://*:8080?reuse=1 |
|
157 |
Server available at http://127.0.0.1:8080. |
|
158 |
$ kill -s TERM `cat /tmp/first.pid` |
|
159 | ||
160 |
Just remember that both web servers need to be started with the C<reuse> |
|
161 |
parameter. |
|
162 | ||
163 |
=head2 Nginx |
|
164 | ||
165 |
One of the most popular setups these days is Hypnotoad behind an Nginx reverse |
|
166 |
proxy, which even supports WebSockets in newer versions. |
|
167 | ||
168 |
upstream myapp { |
|
169 |
server 127.0.0.1:8080; |
|
170 |
} |
|
171 |
server { |
|
172 |
listen 80; |
|
173 |
server_name localhost; |
|
174 |
location / { |
|
175 |
proxy_pass http://myapp; |
|
176 |
proxy_http_version 1.1; |
|
177 |
proxy_set_header Upgrade $http_upgrade; |
|
178 |
proxy_set_header Connection "upgrade"; |
|
179 |
proxy_set_header Host $host; |
|
180 |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|
181 |
proxy_set_header X-Forwarded-HTTPS 0; |
|
182 |
} |
|
183 |
} |
|
184 | ||
185 |
=head2 Apache/mod_proxy |
|
186 | ||
187 |
Another good reverse proxy is Apache with C<mod_proxy>, the configuration |
|
188 |
looks quite similar to the Nginx one above. |
|
189 | ||
190 |
<VirtualHost *:80> |
|
191 |
ServerName localhost |
|
192 |
<Proxy *> |
|
193 |
Order deny,allow |
|
194 |
Allow from all |
|
195 |
</Proxy> |
|
196 |
ProxyRequests Off |
|
197 |
ProxyPreserveHost On |
|
198 |
ProxyPass / http://localhost:8080/ keepalive=On |
|
199 |
ProxyPassReverse / http://localhost:8080/ |
|
200 |
RequestHeader set X-Forwarded-HTTPS "0" |
|
201 |
</VirtualHost> |
|
202 | ||
203 |
=head2 Apache/CGI |
|
204 | ||
205 |
C<CGI> is supported out of the box and your L<Mojolicious> application will |
|
206 |
automatically detect that it is executed as a C<CGI> script. |
|
207 | ||
208 |
ScriptAlias / /home/sri/myapp/script/myapp/ |
|
209 | ||
210 |
=head2 PSGI/Plack |
|
211 | ||
212 |
L<PSGI> is an interface between Perl web frameworks and web servers, and |
|
213 |
L<Plack> is a Perl module and toolkit that contains L<PSGI> middleware, |
|
214 |
helpers and adapters to web servers. L<PSGI> and L<Plack> are inspired by |
|
215 |
Python's WSGI and Ruby's Rack. L<Mojolicious> applications are ridiculously |
|
216 |
simple to deploy with L<Plack>. |
|
217 | ||
218 |
$ plackup ./script/myapp |
|
219 |
HTTP::Server::PSGI: Accepting connections at http://0:5000/ |
|
220 | ||
221 |
L<Plack> provides many server and protocol adapters for you to choose from, |
|
222 |
such as C<FCGI>, C<uWSGI> and C<mod_perl>. |
|
223 | ||
224 |
$ plackup ./script/myapp -s FCGI -l /tmp/myapp.sock |
|
225 | ||
226 |
If an older server adapter is not be able to correctly detect the application |
|
227 |
home directory, you can simply use the MOJO_HOME environment variable. |
|
228 | ||
229 |
$ MOJO_HOME=/home/sri/myapp plackup ./script/myapp |
|
230 |
HTTP::Server::PSGI: Accepting connections at http://0:5000/ |
|
231 | ||
232 |
There is no need for a C<.psgi> file, just point the server adapter at your |
|
233 |
application script, it will automatically act like one if it detects the |
|
234 |
presence of a C<PLACK_ENV> environment variable. |
|
235 | ||
236 |
=head2 Plack middleware |
|
237 | ||
238 |
Wrapper scripts like C<myapp.fcgi> are a great way to separate deployment and |
|
239 |
application logic. |
|
240 | ||
241 |
#!/usr/bin/env plackup -s FCGI |
|
242 |
use Plack::Builder; |
|
243 | ||
244 |
builder { |
|
245 |
enable 'Deflater'; |
|
246 |
require 'myapp.pl'; |
|
247 |
}; |
|
248 | ||
249 |
But you could even use middleware right in your application. |
|
250 | ||
251 |
use Mojolicious::Lite; |
|
252 |
use Plack::Builder; |
|
253 | ||
254 |
get '/welcome' => sub { |
|
255 |
my $self = shift; |
|
256 |
$self->render(text => 'Hello Mojo!'); |
|
257 |
}; |
|
258 | ||
259 |
builder { |
|
260 |
enable 'Deflater'; |
|
261 |
app->start; |
|
262 |
}; |
|
263 | ||
264 |
=head2 Rewriting |
|
265 | ||
266 |
Sometimes you might have to deploy your application in a blackbox environment |
|
267 |
where you can't just change the server configuration or behind a reverse proxy |
|
268 |
that passes along additional information with C<X-*> headers. In such cases |
|
269 |
you can use the hook L<Mojolicious/"before_dispatch"> to rewrite incoming |
|
270 |
requests. |
|
271 | ||
272 |
# Change scheme if "X-Forwarded-Protocol" header is set to "https" |
|
273 |
app->hook(before_dispatch => sub { |
|
274 |
my $c = shift; |
|
275 |
$c->req->url->base->scheme('https') |
|
276 |
if $c->req->headers->header('X-Forwarded-Protocol') eq 'https'; |
|
277 |
}); |
|
278 | ||
279 |
Since reverse proxies generally don't pass along information about path |
|
280 |
prefixes your application might be deployed under, rewriting the base path of |
|
281 |
incoming requests is also quite common. |
|
282 | ||
283 |
# Move first part and slash from path to base path in production mode |
|
284 |
app->hook(before_dispatch => sub { |
|
285 |
my $c = shift; |
|
286 |
push @{$c->req->url->base->path->trailing_slash(1)}, |
|
287 |
shift @{$c->req->url->path->leading_slash(0)}; |
|
288 |
}) if app->mode eq 'production'; |
|
289 | ||
290 |
L<Mojo::URL> objects are very easy to manipulate, just make sure that the URL |
|
291 |
(C<foo/bar?baz=yada>), which represents the routing destination, is always |
|
292 |
relative to the base URL (C<http://example.com/myapp/>), which represents the |
|
293 |
deployment location of your application. |
|
294 | ||
295 |
=head2 Application embedding |
|
296 | ||
297 |
From time to time you might want to reuse parts of L<Mojolicious> applications |
|
298 |
like configuration files, database connection or helpers for other scripts, |
|
299 |
with this little L<Mojo::Server> based mock server you can just embed them. |
|
300 | ||
301 |
use Mojo::Server; |
|
302 | ||
303 |
# Load application with mock server |
|
304 |
my $server = Mojo::Server->new; |
|
305 |
my $app = $server->load_app('./myapp.pl'); |
|
306 | ||
307 |
# Access fully initialized application |
|
308 |
say for @{$app->static->paths}; |
|
309 |
say $app->config->{secret_identity}; |
|
310 |
say $app->dumper({just => 'a helper test'}); |
|
311 | ||
312 |
=head2 Web server embedding |
|
313 | ||
314 |
You can also use L<Mojo::IOLoop/"one_tick"> to embed the built-in web server |
|
315 |
L<Mojo::Server::Daemon> into alien environments like foreign event loops that |
|
316 |
for some reason can't just be integrated with a new reactor backend. |
|
317 | ||
318 |
use Mojolicious::Lite; |
|
319 |
use Mojo::IOLoop; |
|
320 |
use Mojo::Server::Daemon; |
|
321 | ||
322 |
# Normal action |
|
323 |
get '/' => {text => 'Hello World!'}; |
|
324 | ||
325 |
# Connect application with web server and start accepting connections |
|
326 |
my $daemon |
|
327 |
= Mojo::Server::Daemon->new(app => app, listen => ['http://*:8080']); |
|
328 |
$daemon->start; |
|
329 | ||
330 |
# Call "one_tick" repeatedly from the alien environment |
|
331 |
Mojo::IOLoop->one_tick while 1; |
|
332 | ||
333 |
=head1 REAL-TIME WEB |
|
334 | ||
335 |
The real-time web is a collection of technologies that include Comet |
|
336 |
(long-polling), EventSource and WebSockets, which allow content to be pushed |
|
337 |
to consumers with long-lived connections as soon as it is generated, instead |
|
338 |
of relying on the more traditional pull model. All built-in web servers use |
|
339 |
non-blocking I/O and are based on the L<Mojo::IOLoop> event loop, which |
|
340 |
provides many very powerful features that allow real-time web applications to |
|
341 |
scale up to thousands of clients. |
|
342 | ||
343 |
=head2 Backend web services |
|
344 | ||
345 |
Since L<Mojo::UserAgent> is also based on the L<Mojo::IOLoop> event loop, it |
|
346 |
won't block the built-in web servers when used non-blocking, even for high |
|
347 |
latency backend web services. |
|
348 | ||
349 |
use Mojolicious::Lite; |
|
350 | ||
351 |
# Search MetaCPAN for "mojolicious" |
|
352 |
get '/' => sub { |
|
353 |
my $self = shift; |
|
354 |
$self->ua->get('api.metacpan.org/v0/module/_search?q=mojolicious' => sub { |
|
355 |
my ($ua, $tx) = @_; |
|
356 |
$self->render('metacpan', hits => $tx->res->json->{hits}{hits}); |
|
357 |
}); |
|
358 |
}; |
|
359 | ||
360 |
app->start; |
|
361 |
__DATA__ |
|
362 | ||
363 |
@@ metacpan.html.ep |
|
364 |
<!DOCTYPE html> |
|
365 |
<html> |
|
366 |
<head><title>MetaCPAN results for "mojolicious"</title></head> |
|
367 |
<body> |
|
368 |
% for my $hit (@$hits) { |
|
369 |
<p><%= $hit->{_source}{release} %></p> |
|
370 |
% } |
|
371 |
</body> |
|
372 |
</html> |
|
373 | ||
374 |
Multiple events such as parallel requests can be easily synchronized with |
|
375 |
L<Mojo::IOLoop/"delay">. |
|
376 | ||
377 |
use Mojolicious::Lite; |
|
378 |
use Mojo::IOLoop; |
|
379 |
use Mojo::URL; |
|
380 | ||
381 |
# Search MetaCPAN for "mojo" and "mango" |
|
382 |
get '/' => sub { |
|
383 |
my $self = shift; |
|
384 | ||
385 |
# Prepare response in two steps |
|
386 |
Mojo::IOLoop->delay( |
|
387 | ||
388 |
# Parallel requests |
|
389 |
sub { |
|
390 |
my $delay = shift; |
|
391 |
my $url = Mojo::URL->new('api.metacpan.org/v0/module/_search'); |
|
392 |
$url->query({sort => 'date:desc'}); |
|
393 |
$self->ua->get($url->clone->query({q => 'mojo'}) => $delay->begin); |
|
394 |
$self->ua->get($url->clone->query({q => 'mango'}) => $delay->begin); |
|
395 |
}, |
|
396 | ||
397 |
# Delayed rendering |
|
398 |
sub { |
|
399 |
my ($delay, $mojo, $mango) = @_; |
|
400 |
$self->render(json => { |
|
401 |
mojo => $mojo->res->json('/hits/hits/0/_source/release'), |
|
402 |
mango => $mango->res->json('/hits/hits/0/_source/release') |
|
403 |
}); |
|
404 |
} |
|
405 |
); |
|
406 |
}; |
|
407 | ||
408 |
app->start; |
|
409 | ||
410 |
=head2 Timers |
|
411 | ||
412 |
Another primary feature of the L<Mojo::IOLoop> event loop are timers, which |
|
413 |
can for example be used to delay rendering of a response, and unlike C<sleep>, |
|
414 |
won't block any other requests that might be processed in parallel. |
|
415 | ||
416 |
use Mojolicious::Lite; |
|
417 |
use Mojo::IOLoop; |
|
418 | ||
419 |
# Wait 3 seconds before rendering a response |
|
420 |
get '/' => sub { |
|
421 |
my $self = shift; |
|
422 |
Mojo::IOLoop->timer(3 => sub { |
|
423 |
$self->render(text => 'Delayed by 3 seconds!'); |
|
424 |
}); |
|
425 |
}; |
|
426 | ||
427 |
app->start; |
|
428 | ||
429 |
Recurring timers are slightly more powerful, but need to be stopped manually, |
|
430 |
or they would just keep getting emitted. |
|
431 | ||
432 |
use Mojolicious::Lite; |
|
433 |
use Mojo::IOLoop; |
|
434 | ||
435 |
# Count to 5 in 1 second steps |
|
436 |
get '/' => sub { |
|
437 |
my $self = shift; |
|
438 | ||
439 |
# Start recurring timer |
|
440 |
my $i = 1; |
|
441 |
my $id = Mojo::IOLoop->recurring(1 => sub { |
|
442 |
$self->write_chunk($i); |
|
443 |
$self->finish if $i++ == 5; |
|
444 |
}); |
|
445 | ||
446 |
# Stop recurring timer |
|
447 |
$self->on(finish => sub { Mojo::IOLoop->remove($id) }); |
|
448 |
}; |
|
449 | ||
450 |
app->start; |
|
451 | ||
452 |
Timers are not tied to a specific request or connection, and can even be |
|
453 |
created at startup time. |
|
454 | ||
455 |
use Mojolicious::Lite; |
|
456 |
use Mojo::IOLoop; |
|
457 | ||
458 |
# Count seconds since startup |
|
459 |
my $i = 0; |
|
460 |
Mojo::IOLoop->recurring(1 => sub { $i++ }); |
|
461 | ||
462 |
# Show counter |
|
463 |
get '/' => sub { |
|
464 |
my $self = shift; |
|
465 |
$self->render(text => "About $i seconds running!"); |
|
466 |
}; |
|
467 | ||
468 |
app->start; |
|
469 | ||
470 |
Since timers and other low level event watchers are also independent from |
|
471 |
applications, errors can't get logged automatically, you can change that by |
|
472 |
subscribing to the event L<Mojo::Reactor/"error">. |
|
473 | ||
474 |
# Forward error messages to the application log |
|
475 |
Mojo::IOLoop->singleton->reactor->on(error => sub { |
|
476 |
my ($reactor, $err) = @_; |
|
477 |
app->log->error($err); |
|
478 |
}); |
|
479 | ||
480 |
Just remember that all events are processed cooperatively, so your callbacks |
|
481 |
shouldn't block for too long. |
|
482 | ||
483 |
=head2 WebSocket web service |
|
484 | ||
485 |
The WebSocket protocol offers full bi-directional low-latency communication |
|
486 |
channels between clients and servers. Receive messages just by subscribing to |
|
487 |
events such as L<Mojo::Transaction::WebSocket/"message"> with the method |
|
488 |
L<Mojolicious::Controller/"on"> and return them with |
|
489 |
L<Mojolicious::Controller/"send">. |
|
490 | ||
491 |
use Mojolicious::Lite; |
|
492 |
use Mojo::IOLoop; |
|
493 | ||
494 |
# Template with browser-side code |
|
495 |
get '/' => 'index'; |
|
496 | ||
497 |
# WebSocket echo service |
|
498 |
websocket '/echo' => sub { |
|
499 |
my $self = shift; |
|
500 | ||
501 |
# Opened |
|
502 |
$self->app->log->debug('WebSocket opened.'); |
|
503 | ||
504 |
# Increase inactivity timeout for connection a bit |
|
505 |
Mojo::IOLoop->stream($self->tx->connection)->timeout(300); |
|
506 | ||
507 |
# Incoming message |
|
508 |
$self->on(message => sub { |
|
509 |
my ($self, $msg) = @_; |
|
510 |
$self->send("echo: $msg"); |
|
511 |
}); |
|
512 | ||
513 |
# Closed |
|
514 |
$self->on(finish => sub { |
|
515 |
my ($self, $code, $reason) = @_; |
|
516 |
$self->app->log->debug("WebSocket closed with status $code."); |
|
517 |
}); |
|
518 |
}; |
|
519 | ||
520 |
app->start; |
|
521 |
__DATA__ |
|
522 | ||
523 |
@@ index.html.ep |
|
524 |
<!DOCTYPE html> |
|
525 |
<html> |
|
526 |
<head><title>Echo</title></head> |
|
527 |
<body> |
|
528 |
<script> |
|
529 |
var ws = new WebSocket('<%= url_for('echo')->to_abs %>'); |
|
530 | ||
531 |
// Incoming messages |
|
532 |
ws.onmessage = function(event) { |
|
533 |
document.body.innerHTML += event.data + '<br/>'; |
|
534 |
}; |
|
535 | ||
536 |
// Outgoing messages |
|
537 |
window.setInterval(function() { |
|
538 |
ws.send('Hello Mojo!'); |
|
539 |
}, 1000); |
|
540 |
</script> |
|
541 |
</body> |
|
542 |
</html> |
|
543 | ||
544 |
The event L<Mojo::Transaction::WebSocket/"finish"> will be emitted right after |
|
545 |
the WebSocket connection has been closed. |
|
546 | ||
547 |
=head2 Testing WebSocket web services |
|
548 | ||
549 |
While the message flow on WebSocket connections can be rather dynamic, it |
|
550 |
more often than not is quite predictable, which allows this rather pleasant |
|
551 |
L<Test::Mojo> API to be used. |
|
552 | ||
553 |
use Test::More; |
|
554 |
use Test::Mojo; |
|
555 | ||
556 |
# Include application |
|
557 |
use FindBin; |
|
558 |
require "$FindBin::Bin/../echo.pl"; |
|
559 | ||
560 |
# Test echo web service |
|
561 |
my $t = Test::Mojo->new; |
|
562 |
$t->websocket_ok('/echo') |
|
563 |
->send_ok('Hello Mojo!') |
|
564 |
->message_ok |
|
565 |
->message_is('echo: Hello Mojo!') |
|
566 |
->finish_ok; |
|
567 | ||
568 |
# Test JSON web service |
|
569 |
$t->websocket_ok('/echo.json') |
|
570 |
->send_ok({json => {test => [1, 2, 3]}}) |
|
571 |
->message_ok |
|
572 |
->json_message_is('/test', [1, 2, 3]) |
|
573 |
->finish_ok; |
|
574 | ||
575 |
done_testing(); |
|
576 | ||
577 |
=head2 EventSource web service |
|
578 | ||
579 |
EventSource is a special form of long-polling where you can use |
|
580 |
L<Mojolicious::Controller/"write"> to directly send DOM events from servers to |
|
581 |
clients. It is uni-directional, that means you will have to use Ajax requests |
|
582 |
for sending data from clients to servers, the advantage however is low |
|
583 |
infrastructure requirements, since it reuses the HTTP protocol for transport. |
|
584 | ||
585 |
use Mojolicious::Lite; |
|
586 |
use Mojo::IOLoop; |
|
587 | ||
588 |
# Template with browser-side code |
|
589 |
get '/' => 'index'; |
|
590 | ||
591 |
# EventSource for log messages |
|
592 |
get '/events' => sub { |
|
593 |
my $self = shift; |
|
594 | ||
595 |
# Increase inactivity timeout for connection a bit |
|
596 |
Mojo::IOLoop->stream($self->tx->connection)->timeout(300); |
|
597 | ||
598 |
# Change content type |
|
599 |
$self->res->headers->content_type('text/event-stream'); |
|
600 | ||
601 |
# Subscribe to "message" event and forward "log" events to browser |
|
602 |
my $cb = $self->app->log->on(message => sub { |
|
603 |
my ($log, $level, @lines) = @_; |
|
604 |
$self->write("event:log\ndata: [$level] @lines\n\n"); |
|
605 |
}); |
|
606 | ||
607 |
# Unsubscribe from "message" event again once we are done |
|
608 |
$self->on(finish => sub { |
|
609 |
my $self = shift; |
|
610 |
$self->app->log->unsubscribe(message => $cb); |
|
611 |
}); |
|
612 |
}; |
|
613 | ||
614 |
app->start; |
|
615 |
__DATA__ |
|
616 | ||
617 |
@@ index.html.ep |
|
618 |
<!DOCTYPE html> |
|
619 |
<html> |
|
620 |
<head><title>LiveLog</title></head> |
|
621 |
<body> |
|
622 |
<script> |
|
623 |
var events = new EventSource('<%= url_for 'events' %>'); |
|
624 | ||
625 |
// Subscribe to "log" event |
|
626 |
events.addEventListener('log', function(event) { |
|
627 |
document.body.innerHTML += event.data + '<br/>'; |
|
628 |
}, false); |
|
629 |
</script> |
|
630 |
</body> |
|
631 |
</html> |
|
632 | ||
633 |
The event L<Mojo::Log/"message"> will be emitted for every new log message and |
|
634 |
the event L<Mojo::Transaction/"finish"> right after the transaction has been |
|
635 |
finished. |
|
636 | ||
637 |
=head2 Streaming multipart uploads |
|
638 | ||
639 |
L<Mojolicious> contains a very sophisticated event system based on |
|
640 |
L<Mojo::EventEmitter>, with ready-to-use events on almost all layers, and |
|
641 |
which can be combined to solve some of hardest problems in web development. |
|
642 | ||
643 |
use Mojolicious::Lite; |
|
644 |
use Scalar::Util 'weaken'; |
|
645 | ||
646 |
# Emit "request" event early for requests that get upgraded to multipart |
|
647 |
hook after_build_tx => sub { |
|
648 |
my $tx = shift; |
|
649 |
weaken $tx; |
|
650 |
$tx->req->content->on(upgrade => sub { $tx->emit('request') }); |
|
651 |
}; |
|
652 | ||
653 |
# Upload form in DATA section |
|
654 |
get '/' => 'index'; |
|
655 | ||
656 |
# Streaming multipart upload (invoked twice, due to early "request" event) |
|
657 |
post '/upload' => sub { |
|
658 |
my $self = shift; |
|
659 | ||
660 |
# First invocation, subscribe to "part" event to find the right one |
|
661 |
return $self->req->content->on(part => sub { |
|
662 |
my ($multi, $single) = @_; |
|
663 | ||
664 |
# Subscribe to "body" event of part to make sure we have all headers |
|
665 |
$single->on(body => sub { |
|
666 |
my $single = shift; |
|
667 | ||
668 |
# Make sure we have the right part and replace "read" event |
|
669 |
return unless $single->headers->content_disposition =~ /example/; |
|
670 |
$single->unsubscribe('read')->on(read => sub { |
|
671 |
my ($single, $bytes) = @_; |
|
672 | ||
673 |
# Log size of every chunk we receive |
|
674 |
$self->app->log->debug(length($bytes) . ' bytes uploaded.'); |
|
675 |
}); |
|
676 |
}); |
|
677 |
}) unless $self->req->is_finished; |
|
678 | ||
679 |
# Second invocation, render response |
|
680 |
$self->render(text => 'Upload was successful.'); |
|
681 |
}; |
|
682 | ||
683 |
app->start; |
|
684 |
__DATA__ |
|
685 | ||
686 |
@@ index.html.ep |
|
687 |
<!DOCTYPE html> |
|
688 |
<html> |
|
689 |
<head><title>Streaming multipart upload</title></head> |
|
690 |
<body> |
|
691 |
%= form_for upload => (enctype => 'multipart/form-data') => begin |
|
692 |
%= file_field 'example' |
|
693 |
%= submit_button 'Upload' |
|
694 |
% end |
|
695 |
</body> |
|
696 |
</html> |
|
697 | ||
698 |
=head2 Event loops |
|
699 | ||
700 |
Internally the L<Mojo::IOLoop> event loop can use multiple reactor backends, |
|
701 |
L<EV> for example will be automatically used if installed. Which in turn |
|
702 |
allows other event loops like L<AnyEvent> to just work. |
|
703 | ||
704 |
use Mojolicious::Lite; |
|
705 |
use EV; |
|
706 |
use AnyEvent; |
|
707 | ||
708 |
# Wait 3 seconds before rendering a response |
|
709 |
get '/' => sub { |
|
710 |
my $self = shift; |
|
711 |
my $w; |
|
712 |
$w = AE::timer 3, 0, sub { |
|
713 |
$self->render(text => 'Delayed by 3 seconds!'); |
|
714 |
undef $w; |
|
715 |
}; |
|
716 |
}; |
|
717 | ||
718 |
app->start; |
|
719 | ||
720 |
Who actually controls the event loop backend is not important. |
|
721 | ||
722 |
use Mojo::UserAgent; |
|
723 |
use EV; |
|
724 |
use AnyEvent; |
|
725 | ||
726 |
# Search MetaCPAN for "mojolicious" |
|
727 |
my $cv = AE::cv; |
|
728 |
my $ua = Mojo::UserAgent->new; |
|
729 |
$ua->get('api.metacpan.org/v0/module/_search?q=mojolicious' => sub { |
|
730 |
my ($ua, $tx) = @_; |
|
731 |
$cv->send($tx->res->json('/hits/hits/0/_source/release')); |
|
732 |
}); |
|
733 |
say $cv->recv; |
|
734 | ||
735 |
You could for example just embed the built-in web server into an L<AnyEvent> |
|
736 |
application. |
|
737 | ||
738 |
use Mojolicious::Lite; |
|
739 |
use Mojo::Server::Daemon; |
|
740 |
use EV; |
|
741 |
use AnyEvent; |
|
742 | ||
743 |
# Normal action |
|
744 |
get '/' => {text => 'Hello World!'}; |
|
745 | ||
746 |
# Connect application with web server and start accepting connections |
|
747 |
my $daemon |
|
748 |
= Mojo::Server::Daemon->new(app => app, listen => ['http://*:8080']); |
|
749 |
$daemon->start; |
|
750 | ||
751 |
# Let AnyEvent take control |
|
752 |
AE::cv->recv; |
|
753 | ||
754 |
=head1 USER AGENT |
|
755 | ||
756 |
When we say L<Mojolicious> is a web framework we actually mean it. |
|
757 | ||
758 |
=head2 Web scraping |
|
759 | ||
760 |
Scraping information from web sites has never been this much fun before. The |
|
761 |
built-in HTML/XML parser L<Mojo::DOM> is accessible through |
|
762 |
L<Mojo::Message/"dom"> and supports all CSS selectors that make sense for a |
|
763 |
standalone parser, it can be a very powerful tool especially for unit testing |
|
764 |
web application. |
|
765 | ||
766 |
use Mojo::UserAgent; |
|
767 | ||
768 |
# Fetch web site |
|
769 |
my $ua = Mojo::UserAgent->new; |
|
770 |
my $tx = $ua->get('mojolicio.us/perldoc'); |
|
771 | ||
772 |
# Extract title |
|
773 |
say 'Title: ', $tx->res->dom->at('head > title')->text; |
|
774 | ||
775 |
# Extract headings |
|
776 |
$tx->res->dom('h1, h2, h3')->each(sub { say 'Heading: ', shift->all_text }); |
|
777 | ||
778 |
# Visit all elements recursively to extract more than just text |
|
779 |
for my $e ($tx->res->dom('*')->each) { |
|
780 | ||
781 |
# Text before this element |
|
782 |
print $e->text_before(0); |
|
783 | ||
784 |
# Also include alternate text for images |
|
785 |
print $e->{alt} if $e->type eq 'img'; |
|
786 | ||
787 |
# Text for elements without children |
|
788 |
print $e->text(0) unless $e->children->size; |
|
789 | ||
790 |
# Text after last element |
|
791 |
print $e->text_after(0) unless $e->next; |
|
792 |
} |
|
793 | ||
794 |
For a full list of available CSS selectors see L<Mojo::DOM::CSS/"SELECTORS">. |
|
795 | ||
796 |
=head2 JSON web services |
|
797 | ||
798 |
Most web services these days are based on the JSON data-interchange format. |
|
799 |
That's why L<Mojolicious> comes with the possibly fastest pure-Perl |
|
800 |
implementation L<Mojo::JSON> built right in, it is accessible through |
|
801 |
L<Mojo::Message/"json">. |
|
802 | ||
803 |
use Mojo::UserAgent; |
|
804 |
use Mojo::URL; |
|
805 | ||
806 |
# Fresh user agent |
|
807 |
my $ua = Mojo::UserAgent->new; |
|
808 | ||
809 |
# Search MetaCPAN for "mojolicious" and list latest releases |
|
810 |
my $url = Mojo::URL->new('http://api.metacpan.org/v0/release/_search'); |
|
811 |
$url->query({q => 'mojolicious', sort => 'date:desc'}); |
|
812 |
for my $hit (@{$ua->get($url)->res->json->{hits}{hits}}) { |
|
813 |
say "$hit->{_source}{name} ($hit->{_source}{author})"; |
|
814 |
} |
|
815 | ||
816 |
=head2 Basic authentication |
|
817 | ||
818 |
You can just add username and password to the URL. |
|
819 | ||
820 |
use Mojo::UserAgent; |
|
821 | ||
822 |
my $ua = Mojo::UserAgent->new; |
|
823 |
say $ua->get('https://sri:secret@example.com/hideout')->res->body; |
|
824 | ||
825 |
=head2 Decorating followup requests |
|
826 | ||
827 |
L<Mojo::UserAgent> can automatically follow redirects, the event |
|
828 |
L<Mojo::UserAgent/"start"> allows you direct access to each transaction right |
|
829 |
after they have been initialized and before a connection gets associated with |
|
830 |
them. |
|
831 | ||
832 |
use Mojo::UserAgent; |
|
833 | ||
834 |
# User agent following up to 10 redirects |
|
835 |
my $ua = Mojo::UserAgent->new(max_redirects => 10); |
|
836 | ||
837 |
# Add a witty header to every request |
|
838 |
$ua->on(start => sub { |
|
839 |
my ($ua, $tx) = @_; |
|
840 |
$tx->req->headers->header('X-Bender' => 'Bite my shiny metal ass!'); |
|
841 |
say 'Request: ', $tx->req->url->clone->to_abs; |
|
842 |
}); |
|
843 | ||
844 |
# Request that will most likely get redirected |
|
845 |
say 'Title: ', $ua->get('google.com')->res->dom->at('head > title')->text; |
|
846 | ||
847 |
This even works for proxy C<CONNECT> requests. |
|
848 | ||
849 |
=head2 Content generators |
|
850 | ||
851 |
Content generators can be registered with |
|
852 |
L<Mojo::UserAgent::Transactor/"add_generator"> to generate the same type of |
|
853 |
content repeatedly for multiple requests. |
|
854 | ||
855 |
use Mojo::UserAgent; |
|
856 |
use Mojo::Asset::File; |
|
857 | ||
858 |
# Add "stream" generator |
|
859 |
my $ua = Mojo::UserAgent->new; |
|
860 |
$ua->transactor->add_generator(stream => sub { |
|
861 |
my ($transactor, $tx, $path) = @_; |
|
862 |
$tx->req->content->asset(Mojo::Asset::File->new(path => $path)); |
|
863 |
}); |
|
864 | ||
865 |
# Send multiple files streaming via PUT and POST |
|
866 |
$ua->put('http://example.com/upload' => stream => '/home/sri/mojo.png'); |
|
867 |
$ua->post('http://example.com/upload' => stream => '/home/sri/mango.png'); |
|
868 | ||
869 |
The C<json> and C<form> content generators are always available. |
|
870 | ||
871 |
use Mojo::UserAgent; |
|
872 | ||
873 |
# Send "application/json" content via PATCH |
|
874 |
my $ua = Mojo::UserAgent->new; |
|
875 |
my $tx = $ua->patch('http://api.example.com' => json => {foo => 'bar'}); |
|
876 | ||
877 |
# Send query parameters via GET |
|
878 |
my $tx2 = $ua->get('http://search.example.com' => form => {q => 'test'}); |
|
879 | ||
880 |
# Send "application/x-www-form-urlencoded" content via POST |
|
881 |
my $tx3 = $ua->post('http://search.example.com' => form => {q => 'test'}); |
|
882 | ||
883 |
# Send "multipart/form-data" content via PUT |
|
884 |
my $tx4 = $ua->put('http://upload.example.com' => |
|
885 |
form => {test => {content => 'Hello World!'}}); |
|
886 | ||
887 |
For more information about available content generators see also |
|
888 |
L<Mojo::UserAgent::Transactor/"tx">. |
|
889 | ||
890 |
=head2 Large file downloads |
|
891 | ||
892 |
When downloading large files with L<Mojo::UserAgent> you don't have to worry |
|
893 |
about memory usage at all, because it will automatically stream everything |
|
894 |
above C<250KB> into a temporary file, which can then be moved into a permanent |
|
895 |
file with L<Mojo::Asset::File/"move_to">. |
|
896 | ||
897 |
use Mojo::UserAgent; |
|
898 | ||
899 |
# Lets fetch the latest Mojolicious tarball |
|
900 |
my $ua = Mojo::UserAgent->new(max_redirects => 5); |
|
901 |
my $tx = $ua->get('latest.mojolicio.us'); |
|
902 |
$tx->res->content->asset->move_to('mojo.tar.gz'); |
|
903 | ||
904 |
To protect you from excessively large files there is also a limit of C<10MB> |
|
905 |
by default, which you can tweak with the MOJO_MAX_MESSAGE_SIZE environment |
|
906 |
variable. |
|
907 | ||
908 |
# Increase limit to 1GB |
|
909 |
$ENV{MOJO_MAX_MESSAGE_SIZE} = 1073741824; |
|
910 | ||
911 |
=head2 Large file upload |
|
912 | ||
913 |
Uploading a large file is even easier. |
|
914 | ||
915 |
use Mojo::UserAgent; |
|
916 | ||
917 |
# Upload file via POST and "multipart/form-data" |
|
918 |
my $ua = Mojo::UserAgent->new; |
|
919 |
$ua->post('example.com/upload' => |
|
920 |
form => {image => {file => '/home/sri/hello.png'}}); |
|
921 | ||
922 |
And once again you don't have to worry about memory usage, all data will be |
|
923 |
streamed directly from the file. |
|
924 | ||
925 |
=head2 Streaming response |
|
926 | ||
927 |
Receiving a streaming response can be really tricky in most HTTP clients, but |
|
928 |
L<Mojo::UserAgent> makes it actually easy. |
|
929 | ||
930 |
use Mojo::UserAgent; |
|
931 | ||
932 |
# Build a normal transaction |
|
933 |
my $ua = Mojo::UserAgent->new; |
|
934 |
my $tx = $ua->build_tx(GET => 'http://example.com'); |
|
935 | ||
936 |
# Accept response of indefinite size |
|
937 |
$tx->res->max_message_size(0); |
|
938 | ||
939 |
# Replace "read" events to disable default content parser |
|
940 |
$tx->res->content->unsubscribe('read')->on(read => sub { |
|
941 |
my ($content, $bytes) = @_; |
|
942 |
say "Streaming: $bytes"; |
|
943 |
}); |
|
944 | ||
945 |
# Process transaction |
|
946 |
$ua->start($tx); |
|
947 | ||
948 |
The event L<Mojo::Content/"read"> will be emitted for every chunk of data that |
|
949 |
is received, even C<chunked> encoding will be handled transparently if |
|
950 |
necessary. |
|
951 | ||
952 |
=head2 Streaming request |
|
953 | ||
954 |
Sending a streaming request is almost just as easy. |
|
955 | ||
956 |
use Mojo::UserAgent; |
|
957 | ||
958 |
# Build a normal transaction |
|
959 |
my $ua = Mojo::UserAgent->new; |
|
960 |
my $tx = $ua->build_tx(GET => 'http://example.com'); |
|
961 | ||
962 |
# Prepare body |
|
963 |
my $body = 'Hello world!'; |
|
964 |
$tx->req->headers->content_length(length $body); |
|
965 | ||
966 |
# Start writing directly with a drain callback |
|
967 |
my $drain; |
|
968 |
$drain = sub { |
|
969 |
my $content = shift; |
|
970 |
my $chunk = substr $body, 0, 1, ''; |
|
971 |
$drain = undef unless length $body; |
|
972 |
$content->write($chunk, $drain); |
|
973 |
}; |
|
974 |
$tx->req->content->$drain; |
|
975 | ||
976 |
# Process transaction |
|
977 |
$ua->start($tx); |
|
978 | ||
979 |
The drain callback passed to L<Mojo::Content/"write"> will be invoked whenever |
|
980 |
the entire previous chunk has actually been written. |
|
981 | ||
982 |
=head2 Non-blocking |
|
983 | ||
984 |
L<Mojo::UserAgent> has been designed from the ground up to be non-blocking, |
|
985 |
the whole blocking API is just a simple convenience wrapper. Especially for |
|
986 |
high latency tasks like web crawling this can be extremely useful, because you |
|
987 |
can keep many parallel connections active at the same time. |
|
988 | ||
989 |
use Mojo::UserAgent; |
|
990 |
use Mojo::IOLoop; |
|
991 | ||
992 |
# Parallel non-blocking requests |
|
993 |
my $ua = Mojo::UserAgent->new; |
|
994 |
$ua->get('http://metacpan.org/search?q=mojo' => sub { |
|
995 |
my ($ua, $mojo) = @_; |
|
996 |
... |
|
997 |
}); |
|
998 |
$ua->get('http://metacpan.org/search?q=mango' => sub { |
|
999 |
my ($ua, $mango) = @_; |
|
1000 |
... |
|
1001 |
}); |
|
1002 | ||
1003 |
# Start event loop if necessary |
|
1004 |
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; |
|
1005 | ||
1006 |
You can take full control of the L<Mojo::IOLoop> event loop. |
|
1007 | ||
1008 |
=head2 Parallel blocking requests |
|
1009 | ||
1010 |
You can emulate blocking behavior by using L<Mojo::IOLoop/"delay"> to |
|
1011 |
synchronize multiple non-blocking requests. |
|
1012 | ||
1013 |
use Mojo::UserAgent; |
|
1014 |
use Mojo::IOLoop; |
|
1015 | ||
1016 |
# Synchronize non-blocking requests and capture results |
|
1017 |
my $ua = Mojo::UserAgent->new; |
|
1018 |
my $delay = Mojo::IOLoop->delay; |
|
1019 |
$ua->get('http://metacpan.org/search?q=mojo' => $delay->begin); |
|
1020 |
$ua->get('http://metacpan.org/search?q=mango' => $delay->begin); |
|
1021 |
my ($mojo, $mango) = $delay->wait; |
|
1022 | ||
1023 |
The event L<Mojo::IOLoop::Delay/"finish"> can be used for code that needs to |
|
1024 |
be able to work standalone as well as inside an already running event loop. |
|
1025 | ||
1026 |
use Mojo::UserAgent; |
|
1027 |
use Mojo::IOLoop; |
|
1028 | ||
1029 |
# Synchronize non-blocking requests portably |
|
1030 |
my $ua = Mojo::UserAgent->new; |
|
1031 |
my $delay = Mojo::IOLoop->delay; |
|
1032 |
$delay->on(finish => sub { |
|
1033 |
my ($delay, $mojo, $mango) = @_; |
|
1034 |
... |
|
1035 |
}); |
|
1036 |
$ua->get('http://metacpan.org/search?q=mojo' => $delay->begin); |
|
1037 |
$ua->get('http://metacpan.org/search?q=mango' => $delay->begin); |
|
1038 |
$delay->wait unless Mojo::IOLoop->is_running; |
|
1039 | ||
1040 |
=head2 Command line |
|
1041 | ||
1042 |
Don't you hate checking huge HTML files from the command line? Thanks to the |
|
1043 |
C<mojo get> command that is about to change. You can just pick the parts that |
|
1044 |
actually matter with the CSS selectors from L<Mojo::DOM> and JSON Pointers |
|
1045 |
from L<Mojo::JSON::Pointer>. |
|
1046 | ||
1047 |
$ mojo get http://mojolicio.us 'head > title' |
|
1048 | ||
1049 |
How about a list of all id attributes? |
|
1050 | ||
1051 |
$ mojo get http://mojolicio.us '*' attr id |
|
1052 | ||
1053 |
Or the text content of all heading tags? |
|
1054 | ||
1055 |
$ mojo get http://mojolicio.us 'h1, h2, h3' text |
|
1056 | ||
1057 |
Maybe just the text of the third heading? |
|
1058 | ||
1059 |
$ mojo get http://mojolicio.us 'h1, h2, h3' 3 text |
|
1060 | ||
1061 |
You can also extract all text from nested child elements. |
|
1062 | ||
1063 |
$ mojo get http://mojolicio.us '#mojobar' all |
|
1064 | ||
1065 |
The request can be customized as well. |
|
1066 | ||
1067 |
$ mojo get -M POST -c 'Hello!' http://mojolicio.us |
|
1068 |
$ mojo get -H 'X-Bender: Bite my shiny metal ass!' http://google.com |
|
1069 | ||
1070 |
You can follow redirects and view the headers for all messages. |
|
1071 | ||
1072 |
$ mojo get -r -v http://google.com 'head > title' |
|
1073 | ||
1074 |
Extract just the information you really need from JSON data structures. |
|
1075 | ||
1076 |
$ mojo get https://api.metacpan.org/v0/author/SRI /name |
|
1077 | ||
1078 |
This can be an invaluable tool for testing your applications. |
|
1079 | ||
1080 |
$ ./myapp.pl get /welcome 'head > title' |
|
1081 | ||
1082 |
=head2 One-liners |
|
1083 | ||
1084 |
For quick hacks and especially testing, L<ojo> one-liners are also a great |
|
1085 |
choice. |
|
1086 | ||
1087 |
$ perl -Mojo -E 'say g("mojolicio.us")->dom->html->head->title->text' |
|
1088 | ||
1089 |
=head1 HACKS |
|
1090 | ||
1091 |
Fun hacks you might not use very often but that might come in handy some day. |
|
1092 | ||
1093 |
=head2 Adding commands to Mojolicious |
|
1094 | ||
1095 |
By now you've probably used many of the built-in commands described in |
|
1096 |
L<Mojolicious::Commands>, but did you know that you can just add new ones and |
|
1097 |
that they will be picked up automatically by the command line interface? |
|
1098 | ||
1099 |
package Mojolicious::Command::spy; |
|
1100 |
use Mojo::Base 'Mojolicious::Command'; |
|
1101 | ||
1102 |
has description => "Spy on application.\n"; |
|
1103 |
has usage => "usage: $0 spy [TARGET]\n"; |
|
1104 | ||
1105 |
sub run { |
|
1106 |
my ($self, $target) = @_; |
|
1107 | ||
1108 |
# Leak secret passphrase |
|
1109 |
if ($target eq 'secret') { |
|
1110 |
my $secret = $self->app->secret; |
|
1111 |
say qq{The secret of this application is "$secret".}; |
|
1112 |
} |
|
1113 |
} |
|
1114 | ||
1115 |
1; |
|
1116 | ||
1117 |
There are many more useful attributes and methods in L<Mojolicious::Command> |
|
1118 |
that you can use or overload. |
|
1119 | ||
1120 |
$ mojo spy secret |
|
1121 |
The secret of this application is "HelloWorld". |
|
1122 | ||
1123 |
$ ./myapp.pl spy secret |
|
1124 |
The secret of this application is "secr3t". |
|
1125 | ||
1126 |
And to make your commands application specific, just put them in a different |
|
1127 |
namespace. |
|
1128 | ||
1129 |
# Application |
|
1130 |
package MyApp; |
|
1131 |
use Mojo::Base 'Mojolicious'; |
|
1132 | ||
1133 |
sub startup { |
|
1134 |
my $self = shift; |
|
1135 | ||
1136 |
# Add another namespace to load commands from |
|
1137 |
push @{$self->commands->namespaces}, 'MyApp::Command'; |
|
1138 |
} |
|
1139 | ||
1140 |
1; |
|
1141 | ||
1142 |
=head2 Running code against your application |
|
1143 | ||
1144 |
Ever thought about running a quick one-liner against your L<Mojolicious> |
|
1145 |
application to test something? Thanks to the C<eval> command you can do just |
|
1146 |
that, the application object itself can be accessed via C<app>. |
|
1147 | ||
1148 |
$ mojo generate lite_app myapp.pl |
|
1149 |
$ ./myapp.pl eval 'say for @{app->static->paths}' |
|
1150 | ||
1151 |
The C<verbose> options will automatically print the return value or returned |
|
1152 |
data structure to C<STDOUT>. |
|
1153 | ||
1154 |
$ ./myapp.pl eval -v 'app->static->paths->[0]' |
|
1155 |
$ ./myapp.pl eval -V 'app->static->paths' |
|
1156 | ||
1157 |
=head2 Making your application installable |
|
1158 | ||
1159 |
Ever thought about releasing your L<Mojolicious> application to CPAN? It's |
|
1160 |
actually much easier than you might think. |
|
1161 | ||
1162 |
$ mojo generate app MyApp |
|
1163 |
$ cd my_app |
|
1164 |
$ mv public lib/MyApp/ |
|
1165 |
$ mv templates lib/MyApp/ |
|
1166 | ||
1167 |
The trick is to move the C<public> and C<templates> directories so they can |
|
1168 |
get automatically installed with the modules. |
|
1169 | ||
1170 |
# Application |
|
1171 |
package MyApp; |
|
1172 |
use Mojo::Base 'Mojolicious'; |
|
1173 | ||
1174 |
use File::Basename 'dirname'; |
|
1175 |
use File::Spec::Functions 'catdir'; |
|
1176 | ||
1177 |
# Every CPAN module needs a version |
|
1178 |
our $VERSION = '1.0'; |
|
1179 | ||
1180 |
sub startup { |
|
1181 |
my $self = shift; |
|
1182 | ||
1183 |
# Switch to installable home directory |
|
1184 |
$self->home->parse(catdir(dirname(__FILE__), 'MyApp')); |
|
1185 | ||
1186 |
# Switch to installable "public" directory |
|
1187 |
$self->static->paths->[0] = $self->home->rel_dir('public'); |
|
1188 | ||
1189 |
# Switch to installable "templates" directory |
|
1190 |
$self->renderer->paths->[0] = $self->home->rel_dir('templates'); |
|
1191 | ||
1192 |
$self->plugin('PODRenderer'); |
|
1193 | ||
1194 |
my $r = $self->routes; |
|
1195 |
$r->get('/welcome')->to('example#welcome'); |
|
1196 |
} |
|
1197 | ||
1198 |
1; |
|
1199 | ||
1200 |
That's really everything, now you can package your application like any other |
|
1201 |
CPAN module. |
|
1202 | ||
1203 |
$ ./script/my_app generate makefile |
|
1204 |
$ perl Makefile.PL |
|
1205 |
$ make test |
|
1206 |
$ make manifest |
|
1207 |
$ make dist |
|
1208 | ||
1209 |
And if you have a C<PAUSE> account (which can be requested at |
|
1210 |
L<http://pause.perl.org>) even upload it. |
|
1211 | ||
1212 |
$ mojo cpanify -u USER -p PASS MyApp-0.01.tar.gz |
|
1213 | ||
1214 |
=head2 Hello World |
|
1215 | ||
1216 |
If every byte matters this is the smallest C<Hello World> application you can |
|
1217 |
write with L<Mojolicious::Lite>. |
|
1218 | ||
1219 |
use Mojolicious::Lite; |
|
1220 |
any {text => 'Hello World!'}; |
|
1221 |
app->start; |
|
1222 | ||
1223 |
It works because all routes without a pattern default to C</> and automatic |
|
1224 |
rendering kicks in even if no actual code gets executed by the router. The |
|
1225 |
renderer just picks up the C<text> value from the stash and generates a |
|
1226 |
response. |
|
1227 | ||
1228 |
=head2 Hello World one-liners |
|
1229 | ||
1230 |
The C<Hello World> example above can get even a little bit shorter in an |
|
1231 |
L<ojo> one-liner. |
|
1232 | ||
1233 |
$ perl -Mojo -E 'a({text => "Hello World!"})->start' daemon |
|
1234 | ||
1235 |
And you can use all the commands from L<Mojolicious::Commands>. |
|
1236 | ||
1237 |
$ perl -Mojo -E 'a({text => "Hello World!"})->start' get -v / |
|
1238 | ||
1239 |
=head1 MORE |
|
1240 | ||
1241 |
You can continue with L<Mojolicious::Guides> now or take a look at the |
|
1242 |
L<Mojolicious wiki|http://github.com/kraih/mojo/wiki>, which contains a lot |
|
1243 |
more documentation and examples by many different authors. |
|
1244 | ||
1245 |
=head1 SUPPORT |
|
1246 | ||
1247 |
If you have any questions the documentation might not yet answer, don't |
|
1248 |
hesitate to ask on the |
|
1249 |
L<mailing-list|http://groups.google.com/group/mojolicious> or the official IRC |
|
1250 |
channel C<#mojo> on C<irc.perl.org>. |
|
1251 | ||
1252 |
=cut |