Dancing withwebsockets
Dancer• You’ve heard about it• It’s simple, powerful,  flexible
This talk      • Not an hello world      • real life example      • unusual web app      • unusual technologies      • goa...
The background
At $company,• Many Perl products• Lot of modules + deps• Come from CPAN
But,• CPAN = moving target• need to stay stable• local CPAN mirror• but we need to catch up,  sometimes
• We have a CLI injecter• We want a web injecter• Let’s see the process
The process• mirror created with minicpan                                 • it’s «single threaded»• managed with CPAN::Min...
no user             no database                     but, no session              a forking process
The web interface• everything on a single page• input a string, verify• get corresponding CPAN module• check there is a ne...
WStatus                              ire                                       fra           input                        ...
Technologies        We need                    We choose• « instantaneous » update  of the logs                • WebSocket...
Dancer in one sentence• « Dancer allows you to easily create web applications where you  can associate http routes to code...
WebSocket• What are WebSockets ? bidirectional web communication• The server can push to the client (e.g. add more log lin...
forked injection                   AnyEvent run_cmd    process                        WebSocket            logs + status  ...
Show me the GUI
Show me the code
bin/app.pl                                            config.yml     use Dancer;                    layout: "main"     use ...
views/index.tt<b>Status</b> : <span id="status">    <span id="status_text">[% status %]</span></span><form action="/" meth...
setup the websocket, handle logs   var socket = new WebSocket(       "ws://[% server_host %]:[% server_port %]/_hippie/ws"...
handle status changevar status_regex = /__STATUS_CHANGED:/;if (status_regex.test(data.msg)) {    var new_status = data.msg...
package My::Mirror::Injector;use Dancer;use Dancer::Plugin::FlashMessage; # for status messageuse Dancer::Plugin::WebSocke...
post / => sub {    param confirm or $module_name = param module_name;    if (defined param schedule) {        $status = 1;...
my $next_status;sub launch_command {    my ($cmd) = @_;    run_cmd( $cmd ,> => sub {  my ($data) = @_;  if ( defined $data...
Show me the video
Dancing with websocket
Dancing with websocket
Dancing with websocket
Upcoming SlideShare
Loading in...5
×

Dancing with websocket

2,984

Published on

Given at YAPC::EU 2012

Dancer + WebSocket + AnyEvent + Twiggy

This in *not* a talk about doing a hello world in Dancer, as there are plenty of it. This is a real-life example of using Dancer to address a problem in an elegant and powerful way

At $job, we have cpan mirrors. We want them to stay a bit behind the real CPAN for stability, but we have a tool to update modules from the real CPAN to our mirrors. Cool.

I wanted to have a web interface to trigger it, and monitor the injection. This problem is not a typical one (blog, wiki, CRUD, etc). Here we have a long running operation that shall happen only one at a time, that generates logs to be displayed, with states that need keeping. In this regard, it's interesting to see how Dancer is versatile enough to address these situations with ease.

This talk details how I did that, the technology I used, and the full source code (which is quite short). I used Dancer + WebSocket + AnyEvent + Twiggy + some other stuff.

This talk doesn't require any particular knowledge beyond basic Perl, and very basic web server understanding.

Published in: Technology, Art & Photos
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,984
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
19
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Dancing with websocket

    1. 1. Dancing withwebsockets
    2. 2. Dancer• You’ve heard about it• It’s simple, powerful, flexible
    3. 3. This talk • Not an hello world • real life example • unusual web app • unusual technologies • goal: show easiness
    4. 4. The background
    5. 5. At $company,• Many Perl products• Lot of modules + deps• Come from CPAN
    6. 6. But,• CPAN = moving target• need to stay stable• local CPAN mirror• but we need to catch up, sometimes
    7. 7. • We have a CLI injecter• We want a web injecter• Let’s see the process
    8. 8. The process• mirror created with minicpan • it’s «single threaded»• managed with CPAN::Mini • one mirror at a time• injecting = calling • one injection at a time CPAN::Mini::Inject::inject • one command to perform• with the mirror’s config file
    9. 9. no user no database but, no session a forking process
    10. 10. The web interface• everything on a single page• input a string, verify• get corresponding CPAN module• check there is a newer version• confirm/cancel• inject the module in the mirror• display the process log• keep track of the log and the status
    11. 11. WStatus ire fra input meschedule confirm cancel logs
    12. 12. Technologies We need We choose• « instantaneous » update of the logs • WebSockets• every user see the same • Event programming thing • MetaCPAN::API• info about the module
    13. 13. Dancer in one sentence• « Dancer allows you to easily create web applications where you can associate http routes to code, which usually end up sending back data via a template engine »• dancer -a plop creates a new application• templating system: • a layout, with a [% content %] placeholder • views, template page, that are injected as content
    14. 14. WebSocket• What are WebSockets ? bidirectional web communication• The server can push to the client (e.g. add more log lines)• Dancer::Plugin::WebSocket • uses Web::Hippie, websocket/comet Plack implementation • which uses AnyEvent • so we need an AnyEvent Plack web server : Twiggy
    15. 15. forked injection AnyEvent run_cmd process WebSocket logs + status javascript statusserver process logs server side client side
    16. 16. Show me the GUI
    17. 17. Show me the code
    18. 18. bin/app.pl config.yml use Dancer; layout: "main" use My::Mirror::Injector; template: "template_toolkit" dance; engines: template_toolkit: encoding: utf8views/layouts/main.tt start_tag: [%<html> end_tag: %]<head><title>My cool Injector</title><script src="...">jquery</script></head><body>[% content %]</body></html> launch.sh plackup -s Twiggy ./bin/app.pl -p 5005
    19. 19. views/index.tt<b>Status</b> : <span id="status"> <span id="status_text">[% status %]</span></span><form action="/" method="post"> Module names (space seperated): <input type="text" name="module_name" value="[% module_name %]"> <br/> <input type="submit" name="schedule" value="Schedule"> <input type="submit" name="confirm" value="Confirm"> <input type="submit" name="cancel" value="Cancel"></form><pre> <div id="injection_log">[% log %]</div></pre> then some javascript...
    20. 20. setup the websocket, handle logs var socket = new WebSocket( "ws://[% server_host %]:[% server_port %]/_hippie/ws" ); socket.onmessage = function(e) { var data = JSON.parse(e.data); if (data.msg) { $(#injection_log).append(data.msg); } }; function send_msg(message) { socket.send(JSON.stringify({ msg: message })); }
    21. 21. handle status changevar status_regex = /__STATUS_CHANGED:/;if (status_regex.test(data.msg)) { var new_status = data.msg; new_status = new_status.replace(/__STATUS_CHANGED:(.*)__/, "$1"); $(#status_text).remove(); $(#status).append( <span id="status_text"> + new_status + </span> );}
    22. 22. package My::Mirror::Injector;use Dancer;use Dancer::Plugin::FlashMessage; # for status messageuse Dancer::Plugin::WebSocket; # for WebSocket writeuse AnyEvent::Util; # for run_cmdmy $status = 0;my $log = ; # OMG GLOBAL VARIABLES !!!111my $module_name;get / => &display_page;sub display_page { my $uri = request->uri_base; my $scheme = request->scheme; my $host = $uri =~ m|$scheme://(.*?):|; template index => { status => $status, module_name => $module_name, server_host => $host, server_port => request->port, log => $log };}
    23. 23. post / => sub { param confirm or $module_name = param module_name; if (defined param schedule) { $status = 1; flash info => "Scheduled injection"; $log = ; launch_command("mirror-inject --autoflush --dryrun --module $module_name" ); } elsif (defined param confirm) { # Same, but status = 2 # and run command with no --dryrun } elsif (defined param cancel) { # User clicked on Cancel $status = 0; flash info => "Cancelled injection"; } display_page();}
    24. 24. my $next_status;sub launch_command { my ($cmd) = @_; run_cmd( $cmd ,> => sub { my ($data) = @_; if ( defined $data ) { $data =~ s/__CONFIRM__// and $next_status = 2; $data =~ s/__FINISHED__// and $next_status = 0; $log .= $data; ws_send $data; } else { # End of execution if (defined $next_status) { $status = $next_status; $next_status = undef; ws_send __STATUS_CHANGED: . $status . "__n"; } }});}
    25. 25. Show me the video
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×