Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing simple webapps with Dancer

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
Tweet

More Decks by Mike Doherty

Other Decks in Technology

Transcript

  1. 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
  2. Perl?  Perl is: – Old – Stable – Optimized

    – Designed for text manipulation – Not dead http://mikedoherty.ca/t/dalfcs-dancer.tar.gz
  3. 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
  4. 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
  5. tar  Open it with tar xzvf perldancer.tar.gz  It

    is a git repository, so feel free to track your progress
  6. Front page package Pastebin; use 5.14.0; use strict; use warnings;

    use Dancer ':syntax'; get '/' => sub { return template 'index', { version => $^V }; };
  7. Templates <pre> WWW::Hashbang::Pastebin(3) NAME WWW::Hashbang::Pastebin - command line pastebin SYNOPSIS

    $ (hostname ; uptime) | curl -F 'p=&lt;-' <% 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>
  8. 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 ...
  9. 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
  10. Schema __PACKAGE__->add_columns( 'paste_id' => { data_type => 'bigint', is_auto_increment =>

    1, accessor => 'id', }, 'paste_content' => { data_type => 'text', accessor => 'content', size => 'mediumtext', }, );
  11. 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 };
  12. 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 );
  13. 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";
  14. 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;
  15. 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
  16. 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
  17. 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";
  18. 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
  19. 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 );
  20. 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;
  21. 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
  22. 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?