Dancing with websocket
Upcoming SlideShare
Loading in...5

Dancing with websocket



Given at YAPC::EU 2012 ...

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.



Total Views
Views on SlideShare
Embed Views



3 Embeds 38

http://www.techgig.com 32 5
https://twitter.com 1


Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
Post Comment
Edit your comment
  • \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 Dancing with websocket Presentation Transcript

  • 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 • goal: show easiness
  • 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::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
  • 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 newer version• confirm/cancel• inject the module in the mirror• display the process log• keep track of the log and the status
  • WStatus ire fra input meschedule confirm cancel logs
  • 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
  • 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
  • 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
  • forked injection AnyEvent run_cmd process WebSocket logs + status javascript statusserver process logs server side client side
  • Show me the GUI
  • Show me the code
  • 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
  • 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...
  • 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 })); }
  • 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> );}
  • 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 };}
  • 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();}
  • 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"; } }});}
  • Show me the video