Share
Embed
Writing simple webapps with Dancer
This talk provides a walkthrough of developing a simple web application with the Perl webapp framework Dancer. I gave this talk at the Dalhousie Faculty of Computer Science FreeSchool in March 2013
Mike Doherty
March 07, 2013
Transcript
25 slides
-
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 -
Perl? Perl is: – Old – Stable – Optimized
– Designed for text manipulation – Not dead http://mikedoherty.ca/t/dalfcs-dancer.tar.gz -
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 -
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 -
tar Open it with tar xzvf perldancer.tar.gz It
is a git repository, so feel free to track your progress -
Front page package Pastebin; use 5.14.0; use strict; use warnings;
use Dancer ':syntax'; get '/' => sub { return template 'index', { version => $^V }; }; -
Templates <pre> 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. <a href="http://www.perl.org/">perl <% version %></a> WWW::Hashbang::Pastebin(3) </pre> -
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 ... -
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 -
Schema package WWW::Hashbang::Pastebin::Schema::Result::Paste; use strict; use warnings; use base qw/DBIx::Class::Core/;
__PACKAGE__->table('paste'); __PACKAGE__->add_columns( ... ); -
Schema __PACKAGE__->add_columns( 'paste_id' => { data_type => 'bigint', is_auto_increment =>
1, accessor => 'id', }, 'paste_content' => { data_type => 'text', accessor => 'content', size => 'mediumtext', }, ); -
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 }; -
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 ); -
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"; -
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; -
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 -
Test-driven development Write failing tests that should pass first
Then make them pass -
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 -
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"; -
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 -
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 ); -
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; -
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 -
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?