Newer Older
717 lines | 20.955kb
add files
Yuki Kimoto authored on 2014-03-26
1

            
2
=encoding utf8
3

            
4
=head1 NAME
5

            
6
Mojolicious::Guides::Growing - Growing
7

            
8
=head1 OVERVIEW
9

            
10
This document explains the process of starting a L<Mojolicious::Lite>
11
prototype from scratch and growing it into a well structured L<Mojolicious>
12
application.
13

            
14
=head1 CONCEPTS
15

            
16
Essentials every L<Mojolicious> developer should know.
17

            
18
=head2 Model View Controller
19

            
20
MVC is a software architectural pattern for graphical user interface
21
programming originating in Smalltalk-80, that separates application logic,
22
presentation and input.
23

            
24
           +------------+    +-------+    +------+
25
  Input -> | Controller | -> | Model | -> | View | -> Output
26
           +------------+    +-------+    +------+
27

            
28
A slightly modified version of the pattern moving some application logic into
29
the C<controller> is the foundation of pretty much every web framework these
30
days, including L<Mojolicious>.
31

            
32
              +----------------+     +-------+
33
  Request  -> |                | <-> | Model |
34
              |                |     +-------+
35
              |   Controller   |
36
              |                |     +-------+
37
  Response <- |                | <-> | View  |
38
              +----------------+     +-------+
39

            
40
The C<controller> receives a request from a user, passes incoming data to the
41
C<model> and retrieves data from it, which then gets turned into an actual
42
response by the C<view>. But note that this pattern is just a guideline that
43
most of the time results in cleaner more maintainable code, not a rule that
44
should be followed at all costs.
45

            
46
=head2 REpresentational State Transfer
47

            
48
REST is a software architectural style for distributed hypermedia systems such
49
as the web. While it can be applied to many protocols it is most commonly used
50
with HTTP these days. In REST terms, when you are opening a URL like
51
C<http://mojolicio.us/foo> with your browser, you are basically asking the web
52
server for the HTML C<representation> of the C<http://mojolicio.us/foo>
53
C<resource>.
54

            
55
  +--------+                                +--------+
56
  |        | -> http://mojolicio.us/foo  -> |        |
57
  | Client |                                | Server |
58
  |        | <- <html>Mojo rocks!</html> <- |        |
59
  +--------+                                +--------+
60

            
61
The fundamental idea here is that all resources are uniquely addressable with
62
URLs and every resource can have different representations such as HTML, RSS
63
or JSON. User interface concerns are separated from data storage concerns and
64
all session state is kept client-side.
65

            
66
  +---------+                        +------------+
67
  |         | ->    PUT /foo      -> |            |
68
  |         | ->    Hello world!  -> |            |
69
  |         |                        |            |
70
  |         | <-    201 CREATED   <- |            |
71
  |         |                        |            |
72
  |         | ->    GET /foo      -> |            |
73
  | Browser |                        | Web Server |
74
  |         | <-    200 OK        <- |            |
75
  |         | <-    Hello world!  <- |            |
76
  |         |                        |            |
77
  |         | ->    DELETE /foo   -> |            |
78
  |         |                        |            |
79
  |         | <-    200 OK        <- |            |
80
  +---------+                        +------------+
81

            
82
While HTTP methods such as PUT, GET and DELETE are not directly part of REST
83
they go very well with it and are commonly used to manipulate C<resources>.
84

            
85
=head2 Sessions
86

            
87
HTTP was designed as a stateless protocol, web servers don't know anything
88
about previous requests, which makes user-friendly login systems very tricky.
89
Sessions solve this problem by allowing web applications to keep stateful
90
information across several HTTP requests.
91

            
92
  GET /login?user=sri&pass=s3cret HTTP/1.1
93
  Host: mojolicio.us
94

            
95
  HTTP/1.1 200 OK
96
  Set-Cookie: sessionid=987654321
97
  Content-Length: 10
98
  Hello sri.
99

            
100
  GET /protected HTTP/1.1
101
  Host: mojolicio.us
102
  Cookie: $Version=1; sessionid=987654321
103

            
104
  HTTP/1.1 200 OK
105
  Set-Cookie: sessionid=987654321
106
  Content-Length: 16
107
  Hello again sri.
108

            
109
Traditionally all session data was stored on the server-side and only session
110
ids were exchanged between browser and web server in the form of cookies.
111

            
112
  HTTP/1.1 200 OK
113
  Set-Cookie: session=hmac-sha1(base64(json($session)))
114

            
115
In L<Mojolicious> however we are taking this concept one step further by
116
storing everything C<JSON> serialized and C<Base64> encoded in C<HMAC-SHA1>
117
signed cookies, which is more compatible with the REST philosophy and reduces
118
infrastructure requirements.
119

            
120
=head2 Test Driven Development
121

            
122
TDD is a software development process where the developer starts writing
123
failing test cases that define the desired functionality and then moves on to
124
producing code that passes these tests. There are many advantages such as
125
always having good test coverage and code being designed for testability,
126
which will in turn often prevent future changes from breaking old code. Most
127
of L<Mojolicious> was developed using TDD.
128

            
129
=head1 PROTOTYPE
130

            
131
One of the main differences between L<Mojolicious> and other web frameworks is
132
that it also includes L<Mojolicious::Lite>, a micro web framework optimized
133
for rapid prototyping.
134

            
135
=head2 Differences
136

            
137
You likely know the feeling, you've got a really cool idea and want to try it
138
as quickly as possible, that's exactly why L<Mojolicious::Lite> applications
139
don't need more than a single file.
140

            
141
  myapp.pl   # Templates and even static files can be inlined
142

            
143
Full L<Mojolicious> applications on the other hand are much closer to a well
144
organized CPAN distribution to maximize maintainability.
145

            
146
  myapp                      # Application directory
147
  |- script                  # Script directory
148
  |  +- myapp                # Application script
149
  |- lib                     # Library directory
150
  |  |- MyApp.pm             # Application class
151
  |  +- MyApp                # Application namespace
152
  |     +- Example.pm        # Controller class
153
  |- t                       # Test directory
154
  |  +- basic.t              # Random test
155
  |- log                     # Log directory
156
  |  +- development.log      # Development mode log file
157
  |- public                  # Static file directory (served automatically)
158
  |  +- index.html           # Static HTML file
159
  +- templates               # Template directory
160
     |- layouts              # Template directory for layouts
161
     |  +- default.html.ep   # Layout template
162
     +- example              # Template directory for "Example" controller
163
        +- welcome.html.ep   # Template for "welcome" action
164

            
165
Both application skeletons can be automatically generated.
166

            
167
  $ mojo generate lite_app myapp.pl
168
  $ mojo generate app MyApp
169

            
170
=head2 Foundation
171

            
172
We start our new application with a single executable Perl script.
173

            
174
  $ mkdir myapp
175
  $ cd myapp
176
  $ touch myapp.pl
177
  $ chmod 744 myapp.pl
178

            
179
This will be the foundation for our login manager example application.
180

            
181
  #!/usr/bin/env perl
182
  use Mojolicious::Lite;
183

            
184
  get '/' => sub {
185
    my $self = shift;
186
    $self->render(text => 'Hello world!');
187
  };
188

            
189
  app->start;
190

            
191
The built-in development web server makes working on your application a lot of
192
fun thanks to automatic reloading.
193

            
194
  $ morbo myapp.pl
195
  Server available at http://127.0.0.1:3000.
196

            
197
Just save your changes and they will be automatically in effect the next time
198
you refresh your browser.
199

            
200
=head2 Model
201

            
202
In L<Mojolicious> we consider web applications simple frontends for existing
203
business logic, that means L<Mojolicious> is by design entirely L<model> layer
204
agnostic and you just use whatever Perl modules you like most.
205

            
206
  $ mkdir lib
207
  $ touch lib/MyUsers.pm
208
  $ chmod 644 lib/MyUsers.pm
209

            
210
Our login manager will simply use a plain old Perl module abstracting away all
211
logic related to matching usernames and passwords.
212

            
213
  package MyUsers;
214

            
215
  use strict;
216
  use warnings;
217

            
218
  my $USERS = {
219
    sri    => 'secr3t',
220
    marcus => 'lulz',
221
    yko    => 'zeecaptain'
222
  };
223

            
224
  sub new { bless {}, shift }
225

            
226
  sub check {
227
    my ($self, $user, $pass) = @_;
228

            
229
    # Success
230
    return 1 if $USERS->{$user} && $USERS->{$user} eq $pass;
231

            
232
    # Fail
233
    return undef;
234
  }
235

            
236
  1;
237

            
238
A simple helper can be registered with the function
239
L<Mojolicious::Lite/"helper"> to make our C<model> available to all actions
240
and templates.
241

            
242
  #!/usr/bin/env perl
243
  use Mojolicious::Lite;
244

            
245
  use lib 'lib';
246
  use MyUsers;
247

            
248
  # Helper to lazy initialize and store our model object
249
  helper users => sub { state $users = MyUsers->new };
250

            
251
  # /?user=sri&pass=secr3t
252
  any '/' => sub {
253
    my $self = shift;
254

            
255
    # Query parameters
256
    my $user = $self->param('user') || '';
257
    my $pass = $self->param('pass') || '';
258

            
259
    # Check password
260
    return $self->render(text => "Welcome $user.")
261
      if $self->users->check($user, $pass);
262

            
263
    # Failed
264
    $self->render(text => 'Wrong username or password.');
265
  };
266

            
267
  app->start;
268

            
269
The method L<Mojolicious::Controller/"param"> is used to access query
270
parameters, POST parameters, file uploads and route placeholders, all at once.
271

            
272
=head2 Testing
273

            
274
In L<Mojolicious> we take test driven development very serious and try to
275
promote it wherever we can.
276

            
277
  $ mkdir t
278
  $ touch t/login.t
279
  $ chmod 644 t/login.t
280

            
281
L<Test::Mojo> is a scriptable HTTP user agent designed specifically for
282
testing, with many fun state of the art features such as CSS selectors based
283
on L<Mojo::DOM>.
284

            
285
  use Test::More;
286
  use Test::Mojo;
287

            
288
  # Include application
289
  use FindBin;
290
  require "$FindBin::Bin/../myapp.pl";
291

            
292
  # Allow 302 redirect responses
293
  my $t = Test::Mojo->new;
294
  $t->ua->max_redirects(1);
295

            
296
  # Test if the HTML login form exists
297
  $t->get_ok('/')
298
    ->status_is(200)
299
    ->element_exists('form input[name="user"]')
300
    ->element_exists('form input[name="pass"]')
301
    ->element_exists('form input[type="submit"]');
302

            
303
  # Test login with valid credentials
304
  $t->post_ok('/' => form => {user => 'sri', pass => 'secr3t'})
305
    ->status_is(200)->text_like('html body' => qr/Welcome sri/);
306

            
307
  # Test accessing a protected page
308
  $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
309

            
310
  # Test if HTML login form shows up again after logout
311
  $t->get_ok('/logout')->status_is(200)
312
    ->element_exists('form input[name="user"]')
313
    ->element_exists('form input[name="pass"]')
314
    ->element_exists('form input[type="submit"]');
315

            
316
  done_testing();
317

            
318
From now on you can always check your progress by running these unit tests
319
against your application.
320

            
321
  $ ./myapp.pl test
322
  $ ./myapp.pl test t/login.t
323
  $ ./myapp.pl test -v t/login.t
324

            
325
Or perform quick requests right from the command line.
326

            
327
  $ ./myapp.pl get /
328
  Wrong username or password.
329

            
330
  $ ./myapp.pl get -v '/?user=sri&pass=secr3t'
331
  GET /?user=sri&pass=secr3t HTTP/1.1
332
  User-Agent: Mojolicious (Perl)
333
  Connection: keep-alive
334
  Accept-Encoding: gzip
335
  Content-Length: 0
336
  Host: localhost:59472
337

            
338
  HTTP/1.1 200 OK
339
  Connection: keep-alive
340
  Date: Sun, 18 Jul 2010 13:09:58 GMT
341
  Server: Mojolicious (Perl)
342
  Content-Length: 12
343
  Content-Type: text/plain
344

            
345
  Welcome sri.
346

            
347
=head2 State keeping
348

            
349
Sessions in L<Mojolicious> pretty much just work out of the box once you start
350
using the method L<Mojolicious::Controller/"session">, there is no setup
351
required, but we suggest setting a more secure passphrase with
352
L<Mojolicious/"secret">.
353

            
354
  app->secret('Mojolicious rocks');
355

            
356
This passphrase is used by the C<HMAC-SHA1> algorithm to make signed cookies
357
secure and can be changed at any time to invalidate all existing sessions.
358

            
359
  $self->session(user => 'sri');
360
  my $user = $self->session('user');
361

            
362
By default all sessions expire after one hour, for more control you can use
363
the C<expiration> session value to set an expiration date in seconds from now.
364

            
365
  $self->session(expiration => 3600);
366

            
367
And the whole session can be deleted by using the C<expires> session value to
368
set an absolute expiration date in the past.
369

            
370
  $self->session(expires => 1);
371

            
372
For data that should only be visible on the next request, like a confirmation
373
message after a 302 redirect, you can use the flash, accessible through the
374
method L<Mojolicious::Controller/"flash">.
375

            
376
  $self->flash(message => 'Everything is fine.');
377
  $self->redirect_to('goodbye');
378

            
379
Just remember that all session data gets serialized with L<Mojo::JSON> and
380
stored in C<HMAC-SHA1> signed cookies, which usually have a 4096 byte limit,
381
depending on browser.
382

            
383
=head2 Final prototype
384

            
385
A final C<myapp.pl> prototype passing all of the unit tests above could look
386
like this.
387

            
388
  #!/usr/bin/env perl
389
  use Mojolicious::Lite;
390

            
391
  use lib 'lib';
392
  use MyUsers;
393

            
394
  # Make signed cookies secure
395
  app->secret('Mojolicious rocks');
396

            
397
  helper users => sub { state $users = MyUsers->new };
398

            
399
  # Main login action
400
  any '/' => sub {
401
    my $self = shift;
402

            
403
    # Query or POST parameters
404
    my $user = $self->param('user') || '';
405
    my $pass = $self->param('pass') || '';
406

            
407
    # Check password and render "index.html.ep" if necessary
408
    return $self->render unless $self->users->check($user, $pass);
409

            
410
    # Store username in session
411
    $self->session(user => $user);
412

            
413
    # Store a friendly message for the next page in flash
414
    $self->flash(message => 'Thanks for logging in.');
415

            
416
    # Redirect to protected page with a 302 response
417
    $self->redirect_to('protected');
418
  } => 'index';
419

            
420
  # Make sure user is logged in for actions in this group
421
  group {
422
    under sub {
423
      my $self = shift;
424

            
425
      # Redirect to main page with a 302 response if user is not logged in
426
      return $self->session('user') || !$self->redirect_to('index');
427
    };
428

            
429
    # A protected page auto rendering "protected.html.ep"
430
    get '/protected';
431
  };
432

            
433
  # Logout action
434
  get '/logout' => sub {
435
    my $self = shift;
436

            
437
    # Expire and in turn clear session automatically
438
    $self->session(expires => 1);
439

            
440
    # Redirect to main page with a 302 response
441
    $self->redirect_to('index');
442
  };
443

            
444
  app->start;
445
  __DATA__
446

            
447
  @@ index.html.ep
448
  % layout 'default';
449
  %= form_for index => begin
450
    % if (param 'user') {
451
      <b>Wrong name or password, please try again.</b><br>
452
    % }
453
    Name:<br>
454
    %= text_field 'user'
455
    <br>Password:<br>
456
    %= password_field 'pass'
457
    <br>
458
    %= submit_button 'Login'
459
  % end
460

            
461
  @@ protected.html.ep
462
  % layout 'default';
463
  % if (my $msg = flash 'message') {
464
    <b><%= $msg %></b><br>
465
  % }
466
  Welcome <%= session 'user' %>.<br>
467
  %= link_to Logout => 'logout'
468

            
469
  @@ layouts/default.html.ep
470
  <!DOCTYPE html>
471
  <html>
472
    <head><title>Login Manager</title></head>
473
    <body><%= content %></body>
474
  </html>
475

            
476
A list of all built-in helpers can be found in
477
L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>.
478

            
479
=head1 WELL STRUCTURED APPLICATION
480

            
481
Due to the flexibility of L<Mojolicious> there are many variations of the
482
actual growing process, but this should give you a good overview of the
483
possibilities.
484

            
485
=head2 Inflating templates
486

            
487
All templates and static files inlined in the C<DATA> section can be
488
automatically turned into separate files in the C<templates> and C<public>
489
directories.
490

            
491
  $ ./myapp.pl inflate
492

            
493
Those directories always get priority, so inflating can also be a great way to
494
allow your users to customize their applications.
495

            
496
=head2 Simplified application class
497

            
498
This is the heart of every full L<Mojolicious> application and always gets
499
instantiated during server startup.
500

            
501
  $ touch lib/MyApp.pm
502
  $ chmod 644 lib/MyApp.pm
503

            
504
We will start by extracting all actions from C<myapp.pl> and turn them into
505
simplified hybrid routes in the L<Mojolicious::Routes> router, none of the
506
actual action code needs to be changed.
507

            
508
  package MyApp;
509
  use Mojo::Base 'Mojolicious';
510

            
511
  use MyUsers;
512

            
513
  sub startup {
514
    my $self = shift;
515

            
516
    $self->secret('Mojolicious rocks');
517
    $self->helper(users => sub { state $users = MyUsers->new });
518

            
519
    my $r = $self->routes;
520

            
521
    $r->any('/' => sub {
522
      my $self = shift;
523

            
524
      my $user = $self->param('user') || '';
525
      my $pass = $self->param('pass') || '';
526
      return $self->render unless $self->users->check($user, $pass);
527

            
528
      $self->session(user => $user);
529
      $self->flash(message => 'Thanks for logging in.');
530
      $self->redirect_to('protected');
531
    } => 'index');
532

            
533
    my $logged_in = $r->under(sub {
534
      my $self = shift;
535
      return $self->session('user') || !$self->redirect_to('index');
536
    });
537
    $logged_in->get('/protected');
538

            
539
    $r->get('/logout' => sub {
540
      my $self = shift;
541
      $self->session(expires => 1);
542
      $self->redirect_to('index');
543
    });
544
  }
545

            
546
  1;
547

            
548
The C<startup> method gets called right after instantiation and is the place
549
where the whole application gets set up. Since full L<Mojolicious>
550
applications can use nested routes they have no need for C<group> blocks.
551

            
552
=head2 Simplified application script
553

            
554
C<myapp.pl> itself can now be turned into a simplified application script to
555
allow running unit tests again.
556

            
557
  #!/usr/bin/env perl
558

            
559
  use strict;
560
  use warnings;
561

            
562
  use lib 'lib';
563
  use Mojolicious::Commands;
564

            
565
  # Start command line interface for application
566
  Mojolicious::Commands->start_app('MyApp');
567

            
568
=head2 Controller class
569

            
570
Hybrid routes are a nice intermediate step, but to maximize maintainability it
571
makes sense to split our action code from its routing information.
572

            
573
  $ mkdir lib/MyApp
574
  $ touch lib/MyApp/Login.pm
575
  $ chmod 644 lib/MyApp/Login.pm
576

            
577
Once again the actual action code does not change at all.
578

            
579
  package MyApp::Login;
580
  use Mojo::Base 'Mojolicious::Controller';
581

            
582
  sub index {
583
    my $self = shift;
584

            
585
    my $user = $self->param('user') || '';
586
    my $pass = $self->param('pass') || '';
587
    return $self->render unless $self->users->check($user, $pass);
588

            
589
    $self->session(user => $user);
590
    $self->flash(message => 'Thanks for logging in.');
591
    $self->redirect_to('protected');
592
  }
593

            
594
  sub logged_in {
595
    my $self = shift;
596
    return $self->session('user') || !$self->redirect_to('index');
597
  }
598

            
599
  sub logout {
600
    my $self = shift;
601
    $self->session(expires => 1);
602
    $self->redirect_to('index');
603
  }
604

            
605
  1;
606

            
607
All L<Mojolicious::Controller> controllers are plain old Perl classes and get
608
instantiated on demand.
609

            
610
=head2 Application class
611

            
612
The application class C<lib/MyApp.pm> can now be reduced to model and routing
613
information.
614

            
615
  package MyApp;
616
  use Mojo::Base 'Mojolicious';
617

            
618
  use MyUsers;
619

            
620
  sub startup {
621
    my $self = shift;
622

            
623
    $self->secret('Mojolicious rocks');
624
    $self->helper(users => sub { state $users = MyUsers->new });
625

            
626
    my $r = $self->routes;
627
    $r->any('/')->to('login#index')->name('index');
628
    my $logged_in = $r->under->to('login#logged_in');
629
    $logged_in->get('/protected')->to('login#protected');
630
    $r->get('/logout')->to('login#logout');
631
  }
632

            
633
  1;
634

            
635
L<Mojolicious::Routes> allows many route variations, choose whatever you like
636
most.
637

            
638
=head2 Templates
639

            
640
Templates are usually bound to controllers, so they need to be moved into the
641
appropriate directories.
642

            
643
  $ mkdir templates/login
644
  $ mv templates/index.html.ep templates/login/index.html.ep
645
  $ mv templates/protected.html.ep templates/login/protected.html.ep
646

            
647
=head2 Script
648

            
649
Finally C<myapp.pl> can be replaced with a proper L<Mojolicious> script.
650

            
651
  $ rm myapp.pl
652
  $ mkdir script
653
  $ touch script/myapp
654
  $ chmod 744 script/myapp
655

            
656
Only a few small details change, since installable scripts can't use L<lib>
657
without breaking updated dual-life modules.
658

            
659
  #!/usr/bin/env perl
660

            
661
  use strict;
662
  use warnings;
663

            
664
  use FindBin;
665
  BEGIN { unshift @INC, "$FindBin::Bin/../lib" }
666

            
667
  # Start command line interface for application
668
  require Mojolicious::Commands;
669
  Mojolicious::Commands->start_app('MyApp');
670

            
671
=head2 Simplified tests
672

            
673
Normal L<Mojolicious> applications are a little easier to test, so
674
C<t/login.t> can be simplified.
675

            
676
  use Test::More;
677
  use Test::Mojo;
678

            
679
  # Load application class
680
  my $t = Test::Mojo->new('MyApp');
681
  $t->ua->max_redirects(1);
682

            
683
  $t->get_ok('/')
684
    ->status_is(200)
685
    ->element_exists('form input[name="user"]')
686
    ->element_exists('form input[name="pass"]')
687
    ->element_exists('form input[type="submit"]');
688

            
689
  $t->post_ok('/' => form => {user => 'sri', pass => 'secr3t'})
690
    ->status_is(200)->text_like('html body' => qr/Welcome sri/);
691

            
692
  $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
693

            
694
  $t->get_ok('/logout')->status_is(200)
695
    ->element_exists('form input[name="user"]')
696
    ->element_exists('form input[name="pass"]')
697
    ->element_exists('form input[type="submit"]');
698

            
699
  done_testing();
700

            
701
Test driven development takes a little getting used to, but is very well worth
702
it!
703

            
704
=head1 MORE
705

            
706
You can continue with L<Mojolicious::Guides> now or take a look at the
707
L<Mojolicious wiki|http://github.com/kraih/mojo/wiki>, which contains a lot
708
more documentation and examples by many different authors.
709

            
710
=head1 SUPPORT
711

            
712
If you have any questions the documentation might not yet answer, don't
713
hesitate to ask on the
714
L<mailing-list|http://groups.google.com/group/mojolicious> or the official IRC
715
channel C<#mojo> on C<irc.perl.org>.
716

            
717
=cut