So You Think You Can Dance, Halifax?

Who Am I?

Yanick Champoux

How I Got Involved in Dancer: A Cautionary Tale

Circa 3 years ago

How I Got Involved in Dancer: Only a Taste

How I Got Involved in Dancer: The Hooks Bite In

How I Got Involved in Dancer: Uh Oh...

How I Got Involved in Dancer: Tactical Failure

How I Got Involved in Dancer: Dancer 1 Takeover

What the Deuce is Dancer?

Web Framework?

Provide the basic frame to build websites.

Micro?

Awesome Features

What is it (Maybe) Not Good For?

Architecture

Route Dispatcher

GET /welcome          -> send welcome web page
PUT /user/(username)  -> set user info
...

Not a MVC (out of the box)

Big Brothers

Catalyst

Insanely flexible

Mojolicious

one-stop solution to everything (yes, everything)

Can I Have the First Dance?

# Can I Have the First Dance?

* Markdown: cool for slides

 ```html
    # Can I Have the First Dance?

    * Markdown: cool for slides

    ```html
        Present-ception!
    ```

    * Markdown-to-html web app!
 ```

* Markdown-to-html web app!

First Dance

#!/usr/bin/env perl

use 5.10.0;

use strict;
use warnings;


First Dance

#!/usr/bin/env perl

use 5.10.0;

use strict;
use warnings;

use Text::Markdown 'markdown';

my $webpage = markdown( join '', <> );

First Dance

#!/usr/bin/env perl

use 5.10.0;

use strict;
use warnings;

use Text::Markdown 'markdown';
use Dancer ':syntax';

my $webpage = markdown( join '', <> );

get '/' => sub { $webpage };

dance;

First Dance

$ perl micro_chorus.pl prez.mkd
>> Dancer 1.3115 server 17163 listening on http://0.0.0.0:3000
== Entering the development dance floor ...

TADAH!

Thank You!

Any question?

(just kidding)

Slow Dancing

Step 0 - Installation

$ sudo apt-get libdancer-perl

or 

$ cpan Dancer

or

$ cpanm Dancer
(using cpanminus - https://metacpan.org/release/App::cpanminus)

or

$ git clone http://github.com/perldancer/Dancer
$ cd Dancer
$ perl Makefile.PL && make test && make install

Step 1 - Let There Be an App!

$ dancer -a my_app
+ my_app
+ my_app/bin
+ my_app/bin/app.pl
+ my_app/config.yml
+ my_app/environments
+ my_app/environments/development.yml
+ my_app/environments/production.yml
+ my_app/views
+ my_app/views/index.tt
+ my_app/views/layouts
+ my_app/views/layouts/main.tt
+ my_app/MANIFEST.SKIP
+ my_app/lib
+ my_app/lib/
+ my_app/lib/mww.pm
+ my_app/public
+ my_app/public/css
+ my_app/public/css/style.css
+ my_app/public/css/error.css
+ my_app/public/images
+ my_app/public/500.html
+ my_app/public/404.html
+ my_app/public/dispatch.fcgi
+ my_app/public/dispatch.cgi
+ my_app/public/javascripts
+ my_app/public/javascripts/jquery.js
+ my_app/t
+ my_app/t/002_index_route.t
+ my_app/t/001_base.t
+ my_app/Makefile.PL

The App's Launching Script

my_app/bin/app.pl

#!/usr/bin/env perl
use Dancer;
use Foo;
dance;

Configuration Files

+ my_app/config.yml
+ my_app/environments/development.yml
+ my_app/environments/production.yml

config.yml

    appname: "Foo"

    layout: "main"

    charset: "UTF-8"

    template: "simple"

    # template: "template_toolkit"
    # engines:
    #   template_toolkit:
    #     start_tag: '[%'
    #     end_tag:   '%]'

config environments

development

logger: "console"

log: "core"

warnings: 1

show_errors: 1

auto_reload: 0

Template files

+ my_app/views
+ my_app/views/index.tt
+ my_app/views/layouts
+ my_app/views/layouts/main.tt

Typical template system: Template Toolkit

The App Core

my_app/lib/my_app.pm

package Foo;
use Dancer ':syntax';

our $VERSION = '0.1';

get '/' => sub {
    template 'index';
};

true;

Static Files

+ my_app/public/css/style.css
+ my_app/public/css/error.css
+ my_app/public/images
+ my_app/public/500.html
+ my_app/public/404.html
+ my_app/public/dispatch.fcgi
+ my_app/public/dispatch.cgi
+ my_app/public/javascripts
+ my_app/public/javascripts/jquery.js

Tests

+ my_app/t/001_base.t
+ my_app/t/002_index_route.t

Distribution Miscallenous

+ my_app/MANIFEST.SKIP
+ my_app/Makefile.PL

Let's Dance!

$ ./bin/app.pl

[3481]  core @0.000016> loading Dancer::Handler::Standalone handler
[3481]  core @0.000333> loading handler 'Dancer::Handler::Standalone'

>> Dancer 1.3071 server 3481 listening on http://0.0.0.0:3000
== Entering the development dance floor ...

basic application

Deployment

Run over the Plack middleware (inspired from Rack)

Oreo

Deployment modes

Single-threaded test server

$ ./bin/app.pl

High-performance Standalone Web Server

$ plackup --server Twiggy --port 9090 --host 127.0.0.1 ./bin/app.pl

$ plackup --server Starman --port 9090 --host 127.0.0.1 ./bin/app.pl

Fast-CGI

$ plackup -s FCGI --listen /tmp/app.fcgi ./bin/app.pl

CGI

    AddHandler cgi-script .cgi
    ScriptAlias / /path/to/app/public/dispatch.cgi/

The Heart That Provides the Beat

package MyApp;

use Dancer ':syntax';

our $VERSION = '0.1';

get '/' => sub {
    template 'index';
};

true;

Routes

http_verb route => what_to_do;


    get '/' => sub {
        return "hello there!";
    };

Basic

Verbs: get, post, del, put, post

get '/about' => sub {
    return 'this is an app';
};

post '/man' => sub {
    "I've been known to ring twice";
};

any [ qw/ get post put del / ] => '/rome' => sub {
    'You reached Rome.'
};

Named Captures

get '/login/:name' => sub { 
    'Hi ' . param( 'name' );
};

Unnamed Captures

get '/path/*/*' => sub {
    my ( $from, $to ) = splat;
    return "going from $from to $to";
}

Megasplat

get '/tags/**' => sub {
    my ( $tags ) = splat;

    return 'tags selected: ' . join ',', @$tag;
}

Good ol' Regexps

put qr#^/plate/(cake|pie)/slice/(\d+)$# => sub {
    my( $food, $slices ) = splat;

    return "yum, delicious $food " x $slices;
};

Flow control and co

prefix


prefix '/foo';

get '/bar' => sub { ... };

pass

get '/user/:name' => sub {
    # yanick is "special"
    pass if param('name') eq 'yanick';
    ...
};

get '/user/yanick' => sub {
    ...
};

halt

post '/movie/:title' => sub {
    halt("disk is full") if disk_usage() > 0.90;
    ...
};

Flow control and co

send_error

send_error "wut?", 404;

send_file


get '/puppy' => sub {
    my $file = $puppy_pic[rand @puppy_pic];
    send_file $file;
};

upload

post '/upload' => sub {
    my $up = upload('foo');
    my $content = $up->content;
    ...
};

Flow control and co

forward

get '/this' => sub {
    forward '/that';
};

redirect

get '/search/:term' => sub {
    redirect 'http://google.ca/?s=' . param('term');
};

Templates

get '/about' => sub {
    return q{ 
        <html>
            <body>
                <h1>This is my app</h1>
            </body>
        </html>
    };
};

Yuck

Templates

get '/about' => sub {
    template 'about' => {
        app_name => 'Milky Way War'
    };
};

In 'about.mason'

<%args>
$game_name
</%args>
<html>
    <body>
        <h1><% $game_name %></h1>
    </body>
</html>

'about.mason' with layouts

<%args>
$game_name
</%args>
<h1><% $game_name %></h1>

in config.yml

template: mason

engines:
    mason:
        default_escape_flags: ['h']

Template Choices

Dancer::Template::*

More Template Choices

AJAX

Serializers FTW!

set serializer => 'JSON';

get '/webpage' => sub {
    return "totally normal webpage";
};

get '/ajaxy' => sub {
    my $struct = {
        name         => 'Sammy',
        seals_broken => [ 3, 6, 66 ],
    };

    return $struct;
};

# PUT "{ name: 'Dean' }"
put '/ajaxy' => sub {
    my $name = request->body->{name};

    return "Hi $name";
};

More easy

set serializer => 'Mutable';

get '/ajaxy' => sub {
    my $struct = {
        name         => 'Sammy',
        seals_broken => [ 3, 6, 66 ],
    };

    return $struct;
};

# PUT "{ name: 'Dean' }"
put '/ajaxy' => sub {
    my $name = request->body->{name};

    return "Hi $name";
};

MOAR EASY!

use Dancer::Plugin::REST;

prepare_serializer_for_format;

resource '/ajaxy' => 
    get    => sub { ... },
    create => sub { ... },
    delete => sub { ... },
    update => sub { ... };

Defines the following routes

Helper functions too

resource '/user' => 
    get => sub {
        my $user = find_user( param('id') )
            or returns status_not_found( "all sad :-(" );

        return status_ok({
            name => $user->name,
            age  => $user->age,
        });
    };

Plugins

Dancer::Plugin::*

More Plugins

Even More Plugins

Last of the Plugins (for now)

Typical Use

config file:

plugins:
    'Cache::CHI':
        driver: Memory
        global: 1
        expires_in: 3600

in the code:

use Dancer::Plugin::Cache::CHI

get '/expensive/action' => sub {
    # lots of processing-intensive stuff

    cache_page template 'expensive';
}

Hooks

use Dancer::Plugin::MobileDevice;

get '/' => sub {
    set layout => 'mobile' if is_mobile_device;

    ...
};

Do It Once

hook before => sub {
    set layout => 'mobile' if is_mobile_device;
};

We have... a few of them

Testing

use Test::More tests => 3;
use strict;
use warnings;

use mww;
use Dancer::Test;

route_exists [GET => '/'];
response_status_is ['GET' => '/'], 200;

response_content_like [ 'GET' => '/timestamp' ], 
    qr/stardate \d{16}/;

Thank You!

Questions?

PSGI

Perl Web Server Gateway Interface Specification

Inspired by Ruby's Rack and Python's WSGI.

# syntax: perl
my $app = sub {
    my $env = shift;
    return [
        '200',
        [ 'Content-Type' => 'text/plain' ],
        [ "Hello World" ], # or IO::Handle-like object
    ];
};

Plack

PSGI toolkit and middleware.