Slide 1

Slide 1 text

Writing simple Perl webapps using Dancer Mike Doherty @mikedoherty_ca

Slide 2

Slide 2 text

Our simple webapp  A pastebin – Accept a POST request containing some text to remember – Store it, and give the ID back – Respond to a GET for the ID with the paste content  If you want to follow along: – Start installing dependencies: ● cpanm --installdeps WWW::Hashbang::Pastebin – Grab the starter kit: http://mikedoherty.ca/t/dalfcs-dancer.tar.gz

Slide 3

Slide 3 text

Perl?  Perl is: – Old – Stable – Optimized – Designed for text manipulation – Not dead http://mikedoherty.ca/t/dalfcs-dancer.tar.gz

Slide 4

Slide 4 text

Perl renaissance  New release cycle  Way better regexes  autodie  Moose  cpanm, local::lib  perlbrew  Dist::Zilla  rxrx  Perl::Critic & Perl::Tidy  DBIx::Class  Plack  Mojo/Dancer/Catalyst I'd love to tell you more about these in another session. http://mikedoherty.ca/t/dalfcs-dancer.tar.gz

Slide 5

Slide 5 text

Dancer  http://perldancer.org  Like Sinatra, but for Perl  Use Plack for deployment flexibility  Does everyone have the starter kit? http://mikedoherty.ca/t/dalfcs-dancer.tar.gz

Slide 6

Slide 6 text

tar  Open it with tar xzvf perldancer.tar.gz  It is a git repository, so feel free to track your progress

Slide 7

Slide 7 text

Front page package Pastebin; use 5.14.0; use strict; use warnings; use Dancer ':syntax'; get '/' => sub { return template 'index', { version => $^V }; };

Slide 8

Slide 8 text

Templates
WWW::Hashbang::Pastebin(3)
NAME
WWW::Hashbang::Pastebin - command line pastebin
SYNOPSIS
$ (hostname ; uptime) | curl -F 'p=<-' <% request.uri_base %>
<% request.uri_base %>/f4s2
$ chromium-browser <% request.uri_base %>/f4s2+#l2
DESCRIPTION
This pastebin has no user interface - use "curl" to POST paste
content. Your paste's ID is returned in the "X-Pastebin-ID" header;
the URL in the "X-Pastebin-URL", as well as the response content.
perl <% version %>
WWW::Hashbang::Pastebin(3)

Slide 9

Slide 9 text

Fire it up $ perl ./bin/app.pl [13008] core @0.000011> loading Dancer::Handler::Standalone handler in /home/mike/perl5/perlbrew/perls/demo-perl/lib/site_perl/5.16.2/ Dancer/Handler.pm l. 45 [13008] core @0.000219> loading handler 'Dancer::Handler::Standalone' in /home/mike/perl5/perlbrew/perls/demo-perl/lib/site_perl/5.16.2/ Dancer.pm l. 475 >> Dancer 1.3111 server 13008 listening on http://0.0.0.0:3000 >> Dancer::Plugin::DBIC (0.1802) == Entering the development dance floor ...

Slide 10

Slide 10 text

DBIx::Class  Describes your DB schema  Abstracts away differences between RDBMSs  Object-Relational Mapping (ORM) – Allows you to access DB rows as objects – Inflates relationships (foreign keys) into objects  Write your own schema, or generate one from your DB

Slide 11

Slide 11 text

Schema package WWW::Hashbang::Pastebin::Schema::Result::Paste; use strict; use warnings; use base qw/DBIx::Class::Core/; __PACKAGE__->table('paste'); __PACKAGE__->add_columns( ... );

Slide 12

Slide 12 text

Schema __PACKAGE__->add_columns( 'paste_id' => { data_type => 'bigint', is_auto_increment => 1, accessor => 'id', }, 'paste_content' => { data_type => 'text', accessor => 'content', size => 'mediumtext', }, );

Slide 13

Slide 13 text

Accept a POST use Dancer::Plugin::DBIC qw(schema); post '/' => sub { my $paste_content = param('p'); unless ($paste_content) { status 'bad_request'; return 'No paste content received'; } my $row = schema ->resultset('Paste') ->create({ paste_content => $paste_content, }); my $ext_id = ...; # get an ID };

Slide 14

Slide 14 text

Generate an ID  Could be the autoincrement integer  But that's not compact enough – We have a whole alphabet of acceptable URL characters  Integer::Tiny represents integers compactly my $mapper = do { my $key = join ( 'a'..'z', 0..9 ); Integer::Tiny ->new($key); }; ... my $ext_id = $mapper ->encrypt($row->id );

Slide 15

Slide 15 text

Webapp my $ext_id = $mapper->encrypt($row->id); my $ext_url = uri_for("/$ext_id"); headers 'X-Pastebin-ID' => $ext_id, 'X-Pastebin-URL' => $ext_url; return "$ext_url\n";

Slide 16

Slide 16 text

Test use Test::More tests => 2; use WWW::Hashbang::Pastebin; use Dancer::Plugin::DBIC; use Dancer::Test; my $rand = rand(); # paste content route_exists [POST => '/'], 'a route handler is defined for POST /'; my $response = dancer_response( 'POST', '/', { params => {p => $rand} } ); like $response->content => qr{^http://.+/.+} or diag explain $response;

Slide 17

Slide 17 text

Run the tests $ prove t/create_paste.t t/create_paste.t .. ok All tests successful. Files=1, Tests=5, 1 wallclock secs ( 0.02 usr 0.02 sys + 0.64 cusr 0.17 csys = 0.85 CPU) Result: PASS

Slide 18

Slide 18 text

Test-driven development  Write failing tests that should pass first  Then make them pass 

Slide 19

Slide 19 text

Let's test the retrieval of a paste 1. So, we need to send a POST with content • Safe to do this; we already tested that this works 2.Then, request the URL it gave us 3.Verify that the content matches what we sent in #1

Slide 20

Slide 20 text

Retrieving content my $rand = rand(); # paste content my $response = dancer_response( 'POST', '/', { params => {p => $rand} } ); my $id = $response->header('X-Pastebin-ID'); route_exists [GET => "/$paste_id"], "route /$paste_id exists"; response_status_is [GET => "/$paste_id"], 200; response_content_like [GET => "/$paste_id"], qr{\Q$rand\E}, "$rand appears in the content";

Slide 21

Slide 21 text

ACHIEVEMENT UNLOCKED: FAILURE $ prove t/create_paste.t t/create_paste.t .. 2/5 # Failed test 'route /b exists' # at t/create_paste.t line 16. # Failed test '200 for /b' # at t/create_paste.t line 17. # got: '404' # expected: '200' # Failed test '0\.814022216803046 appears in the content' # at t/create_paste.t line 20. # 'GLOB(0x39d78d8)' # doesn't match '(?^:0\.814022216803046)' # Looks like you failed 3 tests of 5. t/create_paste.t .. Dubious, test returned 3 (wstat 768, 0x300) Failed 3/5 subtests Test Summary Report ------------------- t/create_paste.t (Wstat: 768 Tests: 5 Failed: 3) Failed tests: 3-5 Non-zero exit status: 3 Files=1, Tests=5, 1 wallclock secs ( 0.03 usr 0.00 sys + 0.57 cusr 0.06 csys = 0.66 CPU) Result: FAIL

Slide 22

Slide 22 text

Retrieval use Try::Tiny; get '/:id' => sub { my $ext_id = param('id'); my $int_id = try { $mapper->decrypt( $ext_id ) } catch { status 'bad_request'; return "'$ext_id' is an ivalid paste ID"; }; my $paste = schema ->resultset('Paste') ->find( $int_id );

Slide 23

Slide 23 text

Retrieval my $paste = schema->resultset('Paste') ->find( $int_id ); unless ($paste) { content_type('text/plain'); status 'not_found'; return "No such paste as '$ext_id'"; } headers 'X-Pastebin-ID' => $ext_id; content_type('text/plain'); return $paste->content;

Slide 24

Slide 24 text

Command-line usage mike@charron:~ $ (hostname;uptime)|curl -F 'p=<-' http://p.hashbang.ca http://p.hashbang.ca/7Q mike@charron:~ $ curl http://p.hashbang.ca/7Q charron 21:25:45 up 2 days, 27 min, 3 users, load average: 0.35, 0.49, 0.44

Slide 25

Slide 25 text

SUCCESS  The completed code is available at https://github.com/doherty/WWW-Hashbang-Pastebin  The slides are available at http://mikedoherty.ca/t/dalfcs-perldancer  Thanks for joining me  I'm happy to stick around for questions  More Perl sessions?