103
Perl web frameworks Catalyst & Mojolicious Curs avançat de Perl 2012 10/03/2012

Perl web frameworks

  • Upload
    diegok

  • View
    546

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Perl web frameworks

Perl web frameworksCatalyst & Mojolicious

Curs avançat de Perl 201210/03/2012

Page 2: Perl web frameworks

Perl web frameworksHola!

Diego Kupermandiegok | @freekey

Page 3: Perl web frameworks

Web frameworks~

MVC

Page 4: Perl web frameworks
Page 5: Perl web frameworks

Router~

Dispatcher

Page 6: Perl web frameworks

Rutas//prueba-curso/event/23/event/23/where/event/23-una-prueba/picture/3/event/23/picture/3/event/una-prueba/pic/3

Page 7: Perl web frameworks

Rutas//prueba-curso/event/23/event/23/where/event/23-una-prueba/picture/3/event/23/picture/3/event/una-prueba/pic/3

Page 8: Perl web frameworks

Controller

Page 9: Perl web frameworks

Invocado por el dispatcher

Manipulación de capturas del router

Validaciones

Pegamento entre otros componentes:modelos y vistas

Idealmente poco código: thin controller,fat models.

Page 10: Perl web frameworks

Model~

Storage

Page 11: Perl web frameworks

ModelHabitualmente base de datos

Lógica de negocio

Uso fuera de la app

Tests independientes de la app

Otros modelos: git, api-rest, ...

Page 12: Perl web frameworks

View~

Templates / Serializers

Page 13: Perl web frameworks

ViewNormalmente un motor de templates

MUCHAS opciones en CPAN

Template toolkit en Catalyst

EP en Mojolicious

Serialización: JSON, XML, YAML, ...

Page 14: Perl web frameworks

Install~

CPAN

Page 15: Perl web frameworks

Catalyst$ cpanm -n Catalyst::Runtime Catalyst::Devel $ cpanm -n Catalyst::View::TT Catalyst::View::JSON $ cpanm -n Catalyst::Plugin::Unicode::Encoding $ cpanm -n Catalyst::Plugin::Session $ cpanm -n Catalyst::Plugin::Session::Store::File $ cpanm -n Catalyst::Plugin::Session::State::Cookie $ cpanm -n Catalyst::Plugin::Authentication $ cpanm -n Catalyst::Plugin::Authorization::Roles $ cpanm -n Catalyst::Authentication::Store::DBIx::Class$ cpanm -n HTML::FormHandler HTML::FormHandler::Model::DBIC

Page 16: Perl web frameworks

Mojolicioushttp://mojolicio.usThe web in a box

$ cpanm -n Mojolicious

Page 17: Perl web frameworks

Catalyst vs Mojolicious

Page 18: Perl web frameworks

Catalyst

Page 19: Perl web frameworks

$ git clone git://github.com/diegok/dbic.curs.barcelona.pm.git$ cd dbic.curs.barcelona.pm$ dzil build; cpanm -n *.tar.gz; dzil clean$ git clone git://github.com/diegok/app.curs.barcelona.pm.git$ cd app.curs.barcelona.pm$ cpanm -n --installdeps .

Page 20: Perl web frameworks

CatalystThe elegant MVC framework

Page 21: Perl web frameworks

CatalystCrear nueva App

$ catalyst.pl MyCatAppcreated "MyCatApp"created "MyCatApp/script"created "MyCatApp/lib"created "MyCatApp/root"created "MyCatApp/root/static"...created "MyCatApp/script/mycatapp_server.pl"created "MyCatApp/script/mycatapp_test.pl"created "MyCatApp/script/mycatapp_create.pl"

Page 22: Perl web frameworks

├── Changes├── Makefile.PL├── README├── lib│ └── Curs| ├── App│ │ ├── Controller│ │ │ └── Root.pm│ │ ├── Model│ | └── View│ └── App.pm├── curs_app.conf├── curs_app.psgi

Page 23: Perl web frameworks

├── root│ ├── favicon.ico│ └── static│ └── images│ ├── ...│ └── catalyst_logo.png├── script│ ├── ...│ ├── curs_app_create.pl│ └── curs_app_server.pl└── t ├── 01app.t ├── 02pod.t └── 03podcoverage.t

Page 24: Perl web frameworks

package Curs::App;use Moose;use namespace::autoclean;use Catalyst::Runtime 5.80;use Catalyst qw/ -Debug ConfigLoader Static::Simple/;extends 'Catalyst';our $VERSION = '0.01';__PACKAGE__->config( name => 'Curs::App', disable_component_resolution_regex_fallback => 1, enable_catalyst_header => 1, # Send X-Catalyst header);__PACKAGE__->setup();

Page 25: Perl web frameworks

package Curs::App;use Moose;use namespace::autoclean;use Catalyst::Runtime 5.80;use Catalyst qw/ ConfigLoader Static::Simple/;extends 'Catalyst';our $VERSION = '0.01';__PACKAGE__->config( name => 'Curs::App', disable_component_resolution_regex_fallback => 1, enable_catalyst_header => 1, # Send X-Catalyst header);__PACKAGE__->setup();

Page 26: Perl web frameworks

$ ./script/curs_app_server.pl -r -d[debug] Debug messages enabled[debug] Statistics enabled[debug] Loaded plugins:.-------------------------------------------------------.| Catalyst::Plugin::ConfigLoader 0.30 |'-------------------------------------------------------'[debug] Loaded dispatcher "Catalyst::Dispatcher"[debug] Loaded engine "Catalyst::Engine"[debug] Found home "/.../Curs-App"[debug] Loaded Config "/.../Curs-App/curs_app.conf"[debug] Loaded components:.--------------------------------------------+----------.| Class | Type |+--------------------------------------------+----------+| Curs::App::Controller::Root | instance |'--------------------------------------------+----------'[debug] Loaded Private actions:.-------------+-----------------------------+------------.| Private | Class | Method |+-------------+-----------------------------+------------+| /default | Curs::App::Controller::Root | default || /end | Curs::App::Controller::Root | end || /index | Curs::App::Controller::Root | index |'-------------+-----------------------------+------------'

Page 27: Perl web frameworks

$ ./script/curs_app_server.pl -r -d[debug] Loaded Path actions:.--------------------------------+-----------------------.| Path | Private |+--------------------------------+-----------------------+| / | /index || /... | /default |'--------------------------------+-----------------------'[info] Curs::App powered by Catalyst 5.90010HTTP::Server::PSGI: Accepting connections at http://0:3000/

Page 28: Perl web frameworks
Page 29: Perl web frameworks

package Curs::App::Controller::Root;use Moose; use namespace::autoclean;BEGIN { extends 'Catalyst::Controller' }__PACKAGE__->config(namespace => '');sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->response->body($c->welcome_message);}sub default :Path { my ( $self, $c ) = @_; $c->response->body('Page not found'); $c->response->status(404);}sub end : ActionClass('RenderView') {}

Page 30: Perl web frameworks

TieneRouter + Dispatcher

Static::Simple

Controller Root

Acción por defecto

Page 31: Perl web frameworks

aún no tiene...Vista/s

Modelo/s

+Controllers

Ninguna gracia!

Page 32: Perl web frameworks

Contexto~$c

Page 33: Perl web frameworks

Catalyst::Request$c->request$c->req # alias$c->req->params->{foo};$c->req->cookies->{name};$c->req->headers->content_type;$c->req->base;$c->req->uri_with( { page => 3 } );

Page 34: Perl web frameworks

Catalyst::Response$c->response$c->res # alias$c->res->body('Hello World');$c->res->status(404);$c->res->redirect('http://barcelona.pm');# CGI::Simple::Cookie$c->res->cookies->{foo} = { value => '123' };

Page 35: Perl web frameworks

Catalyst::Log$c->log$c->log->debug('Something happened');$c->log->info('Something you should know');

Page 36: Perl web frameworks

Stash$c->stash( key => 'value' );$c->stash( 'key' ); # 'value'$c->stash->{key} = [1..10];$c->stash->{key}; # [1..10]

Dura un request-response completoPaso de datos entre componentes

Page 37: Perl web frameworks

Routes~

Controller actions

Page 38: Perl web frameworks

Nuevo Controller$ ./script/curs_app_create.pl controller Example exists ".../Curs-App/script/lib/Curs/App/Controller" exists ".../Curs-App/script/t"created ".../Curs-App/lib/Curs/App/Controller/Example.pm"created ".../Curs-App/t/controller_Example.t"

Page 39: Perl web frameworks

lib/Curs/App/Controller/Example.pmpackage Curs::App::Controller::Example;use Moose; use namespace::autoclean;BEGIN {extends 'Catalyst::Controller'; }# /examplesub index :Path :Args(0) { my ( $self, $c ) = @_; $c->res->body('Example index match!');}

Page 40: Perl web frameworks

Controller ActionsLiteral match (:Path)

Root-level (:Global) = Path('/...')

Namespace-prefixed (:Local) = Path('.../')

Restricción de argumentos (:Args)

Page 41: Perl web frameworks

/example/cero/...sub cero :Local { my ( $self, $c, @args ) = @_; $c->res->body('Args: ' . join ', ', @args);}

/example/unosub uno :Local :Args(0) { my ( $self, $c ) = @_; $c->res->body(':Local :Args(0)');}

Page 42: Perl web frameworks

/example/dossub dos :Path('dos') :Args(0) { my ( $self, $c ) = @_; $c->res->body(":Path('dos') :Args(0)");}

/example/tressub tres :Path('/example/tres') :Args(0) { my ( $self, $c ) = @_; $c->res->body(":Path('/example/tres') :Args(0)");}

Page 43: Perl web frameworks

/hola/mundosub cuatro :Path('/hola') :Args(1) { my ( $self, $c, $arg1 ) = @_; $c->res->body("Hola $arg1!");}

Page 44: Perl web frameworks

Controller ActionsPattern-match

:Regex() & :LocalRegex()

Page 45: Perl web frameworks

/item23/order32sub cinco :Regex('^item(\d+)/order(\d+)$') { my ( $self, $c ) = @_; my $item = $c->req->captures->[0]; my $order = $c->req->captures->[1]; $c->res->body( "(cinco) Item: $item | Order: $order" );}

Page 46: Perl web frameworks

/example/item23/order32sub seis :LocalRegex('^item(\d+)/order(\d+)$') { my ( $self, $c ) = @_; my $item = $c->req->captures->[0]; my $order = $c->req->captures->[1]; $c->res->body( "(seis) Item: $item | Order: $order" );}

Page 47: Perl web frameworks

Controller ActionsPrivadas & control flow

:Privateforward() & detach()

Page 48: Perl web frameworks

sub now :Local :Args(0) { my ( $self, $c ) = @_; $c->forward('stash_now'); $c->detach('say_now'); $c->log->debug('ouch!');} sub stash_now :Private { my ( $self, $c ) = @_; $c->stash( now => DateTime->now );} sub say_now :Private { my ( $self, $c ) = @_; $c->res->body($c->stash->{now});}

Page 49: Perl web frameworks

Built-in special actions

Page 50: Perl web frameworks

Default controller actionsub default : Path {}

Como default, con mas precedenciasub index :Path Args(0) {}

Page 51: Perl web frameworks

Antes de la acción, solo una vezsub begin :Private {}

Despues de la acción, solo una vezsub end :Private {}

Despues de begin, de menos especifico a masespecifico

sub auto :Private {}

Si retorna false se salta hasta end()

Page 52: Perl web frameworks

Chained actions:Chained

Page 53: Perl web frameworks

sub with_now : PathPart('example/now') Chained( '/' ) CaptureArgs( 0 ) { my ( $self, $c ) = @_; $c->forward('stash_now');}sub show_now : PathPart('show') Chained( 'with_now' ) Args( 0 ) { my ( $self, $c ) = @_; $c->detach('say_now');}

Page 54: Perl web frameworks

Chained es MUY potente,pero antes tenemos que

añadir algunas cosas mas...

Page 55: Perl web frameworks

VistasTemplate toolkit

+JSON

Page 56: Perl web frameworks

$ script/curs_app_create.pl view Web TTexists ".../Curs-App/script/../lib/Curs/App/View"exists ".../Curs-App/script/../t"created ".../Curs-App/script/../lib/Curs/App/View/Web.pm"created ".../Curs-App/script/../t/view_Web.t"

Page 57: Perl web frameworks

lib/Curs/App/View/Web.pmCurs::App::View::Web;use Moose;extends 'Catalyst::View::TT';__PACKAGE__->config( TEMPLATE_EXTENSION => '.tt', CATALYST_VAR => 'c', TIMER => 0, ENCODING => 'utf-8' WRAPPER => 'layout', render_die => 1,);1;

Page 58: Perl web frameworks

lib/Curs/App.pm__PACKAGE__->config( # ... 'View::Web' => { INCLUDE_PATH => [ __PACKAGE__->path_to('root', 'src'), __PACKAGE__->path_to('root', 'lib'), ], },);

Page 59: Perl web frameworks

root/lib/layout

<!DOCTYPE HTML><html lang="en-us"> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Curs avançat de Perl 2012</title> <link rel="stylesheet" href="/css/style.css" type="text/css"> </head> <body> [% content %] </body></html>

Page 60: Perl web frameworks

TT y layout en su sitio,hora de cambiar la home

Page 61: Perl web frameworks

root/src/index.tt<h1>[% message %]</h1>

lib/Curs/App/Controller/Root.pmsub index :Path :Args(0) { my ( $self, $c ) = @_; $c->stash( message => 'Hola mundo!', template => 'index.tt' );}

Page 62: Perl web frameworks
Page 63: Perl web frameworks

$ ./script/curs_app_create.pl view JSON JSON exists "lib/Curs/App/View" exists "t/"created "lib/Curs/App/View/JSON.pm"created "t/view_JSON.t"

Page 64: Perl web frameworks

lib/Curs/App.pm__PACKAGE__->config({ ... 'View::JSON' => { expose_stash => 'json', # defaults to everything }, default_view => 'Web',});

Page 65: Perl web frameworks

Uso de View::JSONsub status :Path('/status') :Args(0) { my ( $self, $c ) = @_; $c->stash( json => { value => 'testing' } ); $c->forward('View::JSON');}

Page 66: Perl web frameworks

ModeloDBIC

Page 67: Perl web frameworks

Curs::Schema$ script/curs_app_create.pl model DB DBIC::Schema Curs::Schemaexists ".../Curs-App/script/../lib/Curs/App/Model"exists ".../Curs-App/script/../t"created ".../Curs-App/script/../lib/Curs/App/Model/DB.pm"created ".../Curs-App/script/../t/model_DB.t"

Page 68: Perl web frameworks

Config por defectocurs_app.conf

name Curs::App<Model::DB> connect_info dbi:SQLite:dbname=curs_schema.db connect_info connect_info <connect_info> sqlite_unicode 1 RaiseError 1 </connect_info></Model::DB>

Page 69: Perl web frameworks

Deploy!$ ./script/schema_deploy.pl Creating sql/Curs-Schema-1-SQLite.sql => done.Making initial deploy (ddbb has no version) => done.

Page 70: Perl web frameworks

Nuestro schema es uncomponente más ahora!

sub action :Local { my ( $self, $c ) = @_; $c->res->body( $c->model('DB::User')->first->email );}

Page 71: Perl web frameworks

Authentication&

Authorization

Page 72: Perl web frameworks

Catalyst::Plugin::Authentication&

Catalyst::Plugin:Authorization::Roles+ Catalyst::Plugin::Session

Page 73: Perl web frameworks

lib/Curs/App.pmuse Catalyst qw/ ... Session Session::State::Cookie Session::Store::File Authentication Authorization::Roles/;

Page 74: Perl web frameworks

__PACKAGE__->config( ... 'Plugin::Authentication' => { default_realm => 'users', realms => { users => { credential => { class => 'Password', password_field => 'password', password_type => 'self_check', }, store => { class => 'DBIx::Class', user_model => 'DB::User', role_relation => 'roles', role_field => 'name', id_field => 'email' } } }

Page 75: Perl web frameworks

} } },);

Nuevos metodos en la app$c->authenticate( email => $email, password => $pwd );$c->user_exists;$c->user;

Page 76: Perl web frameworks

Todo listoNecesitamos un form para login

:-(

Page 77: Perl web frameworks

HTML::FormHandleral rescate! :-)

Page 78: Perl web frameworks

lib/Curs/App/Form/Login.pmpackage Curs::App::Form::Login;use HTML::FormHandler::Moose;extends 'HTML::FormHandler';use Email::Valid;has_field 'email' => ( type => 'Text', required => 1, apply => [{ check => sub { Email::Valid->address( $_[0] ) }, message => 'Must be a valid email address' }]);

Page 79: Perl web frameworks

lib/Curs/App/Form/Login.pmhas_field 'password' => ( type => 'Password', required => 1 );has_field 'submit' => ( type => 'Submit', value => 'Login' );

Page 80: Perl web frameworks

Ahora sí!

Page 81: Perl web frameworks

Un controller nuevo paraauth

$ ./script/curs_app_create.pl controller Auth...

Page 82: Perl web frameworks

lib/Curs/App/Controller/Auth.pmpackage Curs::App::Controller::Auth;use Moose; use namespace::autoclean;BEGIN {extends 'Catalyst::Controller'; }use Curs::App::Form::Login;

Page 83: Perl web frameworks

sub login :Path(/login) Args(0) { my ( $self, $c ) = @_; my $form = Curs::App::Form::Login->new(); my $creds = { email => $form->value->{email}, password => $form->value->{password} }; if ( $form->process( params => $c->req->params ) ) { if ( $c->authenticate( $creds ) ) { $c->detach('after_login_redirect'); } else { $form->field('password')->add_error( 'Invalid password' ); } } $c->stash( template => 'auth/login.tt', form => $form );}

Page 84: Perl web frameworks

root/src/auth/login.tt<div id="login"> [% form.render %]</div>

Page 85: Perl web frameworks

=head2 need_login Ensure user exists on the chain.=cutsub need_login :PathPart( '' ) Chained( '/' ) CaptureArgs( 0 ) { my ( $self, $c ) = @_; unless ( $c->user_exists ) { $c->session->{after_login_path} = '/' . $c->req->path; $c->res->redirect( $c->uri_for_action( $c->controller('Auth') ->action_for('login') ) ); $c->detach; }}

Page 86: Perl web frameworks

=head2 need_role_admin Ensure user with the admin role.=cutsub need_role_admin :PathPart('admin') Chained('need_login') CaptureArgs(0) { my ( $self, $c ) = @_; unless ( $c->check_user_roles( 'admin' ) ) { $c->res->body('You need admin role for this action!'); $c->detach(); }}

Page 87: Perl web frameworks

En otro controller... perdido en otra galaxia ...

Page 88: Perl web frameworks

=head2 element_chainBase chain for actions related to one user=cutsub element_chain :PathPart('user') Chained('/auth/need_login') CaptureArgs(1) { my ( $self, $c, $user_id ) = @_; $c->stash( user => $c->model('DB::User') ->find( $user_id ) ); unless ( $c->stash->{user} ) { $c->detach( '/error/element_not_found', [ 'user' ] ); }}

Page 89: Perl web frameworks

sub view :PathPart() Chained('element_chain') Args(0) { my ( $self, $c ) = @_; $c->stash( template => 'user/view.tt' );}sub delete :PathPart() Chained('element_chain') Args(0) { my ( $self, $c ) = @_; $c->stash->{user}->delete; # ...}

Page 90: Perl web frameworks

Pluginvs

TraitFor

Page 91: Perl web frameworks

Plugin globalvs

Plugin for component

Page 92: Perl web frameworks

TraitFor ControllerRole para el controller

Page 93: Perl web frameworks

package Catalyst::TraitFor::Controller::WithDateTime;use MooseX::MethodAttributes::Role;use namespace::autoclean;use DateTime;has 'stash_key' => ( is => 'ro', default => 'datetime' );after 'auto' => sub { my ( $self, $c ) = @_; $c->stash( $self->stash_key => DateTime->now );};sub auto : Private { 1 }

Page 94: Perl web frameworks

Trait's locales

Page 95: Perl web frameworks

package Curs::App::TraitFor::Controller::WithDBIC;use MooseX::MethodAttributes::Role;use namespace::autoclean;require 'model_name';require 'base_chain';has stash_key => ( is => 'ro', default => sub { lc @{[split /::/, shift->model_name ]}[-1] } );

Page 96: Perl web frameworks

...sub item :PathPart('') Chained('base_chain') CaptureArgs(1) { my ( $self, $c, $id ) = @_; $c->stash->{ $self->stash_key } = $c->model( $self->model_name )->find($id) || $c->detach('missing');}sub missing { my ( $self, $c ) = @_; $c->res->code(404); $c->res->body('Not found!');}1;

Page 97: Perl web frameworks

A consumir!

Page 98: Perl web frameworks

package Curs::App::Controller::Event;use Moose; use namespace::autoclean;BEGIN {extends 'Catalyst::Controller'}has model_name => ( is => 'ro', default => 'DB::Event' );with 'Curs::App::TraitFor::Controller::WithDBIC';sub base_chain :PathPart('event') Chained('/') CaptureArgs(1) {}

sub delete :PathPart('delete') Chained('item') Args(0) { my ( $self, $c ) = @_; $c->stash->{event}->delete; }

Page 99: Perl web frameworks

https://metacpan.org/search?q=catalyst

896 results

Page 100: Perl web frameworks

Plack(ya lo estamos usando)

Page 101: Perl web frameworks

$ cpanm -n Starman...$ starman curs_app.psgi2012/03/10-11:25:36 Starman::Server (type Net::Server::PreFork) starting! pid(73661)Binding to TCP port 5000 on host *Setting gid to "20 20 20 204 100 98 81 80 79 61 12 403 402 401"

Page 102: Perl web frameworks

Más CatalystIRC

#catalyst en irc.perl.org.

#catalyst-dev en irc.perl.org (desarrollo).

Mailing lists

http://lists.scsys.co.uk/mailman/listinfo/catalyst

http://lists.scsys.co.uk/mailman/listinfo/catalyst-dev

Page 103: Perl web frameworks

Manual

https://metacpan.org/module/Catalyst::ManualEjercicios

Añadir un metodo (API) que deje verdatos de UN usuario en JSON:/user/1/json

Extra: vista json que devuelva array deusuarios (sin repetir codigo)

Añadir una vista que liste los eventos(Creados en la práctica anterior)

Crear una acción (solo para admins), unformulario y su plantilla para crear unevento y otra para editarlo.