Using AnyEvent and Dancer
In our Hackerspace RaumZeitLabor in Mannheim, Germany, we have a "Network Power Manager 2000" which is a device that has 8 power sockets and is able to turn these sockets on/off when told so via network. We use it to save some power when nobody is at our space.
That sounds quite nice, but we had to reverse-engineer the protocol. Afterwards, we noticed that only one (!) connection to that device is possible. So our first approach of using a little script to communicate with it was not optimal: It had timing problems because the NPM2000 took a little bit longer to close the TCP connection.
Therefore, I decided to write a little webapp using the excellent Dancer framework which talks to the NPM2000 and provides a nice API to the user(s). It also should keep one TCP connection open all the time to save the overhead of creating a new connection and waiting for old connections to close properly on both sides.
For creating and using said TCP connection I decided to use AnyEvent::Socket. The main issue with this approach is that Dancer doesn’t use the AnyEvent mainloop and thus AnyEvent won’t work at all.
The nice people in #dancer suggested taking a look at Twiggy, an AnyEvent HTTP server for PSGI. It can be easily combined with Dancer and uses AnyEvent. So, to run a Dancer application with Twiggy, use:
cpanm Dancer Twiggy dancer -a npmd cd npmd plackup -s Twiggy bin/app.pl
Now that’s great, we have an AnyEvent-based server running our Dancer application. So, let’s use an AnyEvent timer in lib/npmd.pm:
package npmd; use Dancer ':syntax'; use AnyEvent;our $VERSION = '0.1';
my $w;
get '/' => sub {
$w = AnyEvent->timer(after => 1, interval => 1, cb => sub {
debug 'timer msg!';
});
template 'index';
};true;
After the first request to the application in your browser you should see the message 'timer msg' on your terminal every second.
Let’s instead create the timer outside a route, so that it runs all the time:
package npmd; use Dancer ':syntax'; use AnyEvent;our $VERSION = '0.1';
my $w = AnyEvent->timer(after => 1, interval => 1, cb => sub {
debug 'timer msg!';
});get '/' => sub {
template 'index';
};true;
Oops. That won’t work. The code is run, but $w falls out of scope immediately because we are not using it in any handler. This causes AnyEvent to delete the timer. There are two ways to fix this: use 'our $w' instead of 'my $w' or use $w in any of your routes.
So, a working version could look like this:
package npmd; use Dancer ':syntax'; use AnyEvent;our $VERSION = '0.1';
our $w;
$w = AnyEvent->timer(after => 1, interval => 1, cb => sub {
debug 'timer msg';
});get '/' => sub {
template 'index';
};true;
Very interesting!
I've been using Dancer to write a web application that displays (and actively) monitors Perlbal reverse proxies that we have.
I'm currently using fork in order to run the status probing. It would be an interesting experiment to use AnyEvent so I could run interactive commands (and not interact using routes). This can save quite a bit over forking overhead and on route maintenance.
Thanks for the idea, and I'm happy you're enjoying Dancer! :)