[initial version jeremy@n-heptane.com**20080704002545] { addfile ./HitCounter.lhs addfile ./Makefile addfile ./hscolour.css hunk ./HitCounter.lhs 1 - + + +
+I just glued two of my favorite technologies together, Asterisk (the opensource PBX/VoIP/etc system) and HAppS (the Haskell Application Server framework).
+ +If you have heard of HAppS, but never used it, you may have the impression that HAppS is a web development platform -- but that is not quite correct. HAppS is actually a collection of several different server components which can be combined together or used separately.
+ +In this post, I will show how to build as simple FastAGI server on top of the HAppS-State component. We will not be using the web component (HAppS-Server), which is the part that provides HTTP, templating, cookies, etc. This post assumes no prior knowledge of HAppS or AGI.
+ +If you want to build this demo you will need:
+The asterisk server can be extended by using the Asterisk Gateway Interface (AGI). It provides the functionality you need to do stuff like "Please enter your 16-digit account number."
+ +An AGI script is a standalone program you write (in a language of your choice), which will be run by asterisk. Asterisk communications with your AGI script by writing to its stdin and reading from its stdout. The commands and responses are human readable text.
+ +Asterisk also has the option of communicating with your AGI script remotely via TCP instead of directly running a local program. This feature is known as FastAGI. The commands and responses are identical to normal AGI, the only differences are:
+There is one additional import difference, which is more of a side-effect. When using AGI, a new process will be spawn for each call. When using FastAGI, a new TCP connection will be opened -- typically to a single, long running server process. So, with AGI you will need to worry about how to provide communication and synchronization between multiple processes, but with FastAGI, you can just use threads.
+The HAppS State component provides in-memory state with ACID guarantees. It uses write ahead logging and checkpointing to ensure the state can be restored from disk in the event of a power outage, and also provides multimaster replication. Unlike a traditional relational database, HAppS-State works directly with native, arbitrary Haskell data types. This means you don't have to figure out how to get your beautiful data structures wedged into a relational database just to get ACID guarantees.
+The remainder of the post is a simple hit-counter AGI application. You just call the phone number, and it tells you what caller number you are. I won't go into much detail about the HAppS State portion, since this post is supposed to show how to integrate AGI, not how to use HAppS State.
+ +> {-# LANGUAGE TemplateHaskell, UndecidableInstances, TypeFamilies, +> TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable, +> MultiParamTypeClasses, TypeOperators, GeneralizedNewtypeDeriving #-} + +> module Main where +> +> import Control.Concurrent +> import Control.Monad +> import Control.Monad.Reader +> import Control.Monad.State +> import Control.Monad.Trans +> import HAppS.Data +> import HAppS.State +> import Network +> import Network.AGI +> import System.Random +> import System.Posix.Unistd + +The first thing we do is define the type we will use to store our
+ persistant state. This is our database. The deriveAll
is
+ similar to deriving (Eq, Ord, Read, Show, Num, Enum, Default,
+ Data, Typeable)
. Since there is no way to extend the
+ deriving
we have to use template Haskell to add support
+ for deriving Default
.
+
deriveSerialize
is the magic that allows HitCounter
+ to be serialized to disk or replicated between servers.
The Version
instance is used to support migrating the
+ old data if modify the HitCounter
data structure. That
+ is a subject for a different tutorial.
Next we define a function which modifies the global state
+ (HitCounter
). This function while be run
+ automatically. This means that there is no race condition between the
+ get
and the put
. The get
and
+ put
functions the ones found in
+ Control.Monad.State
.
This is the magic which converts the incHits
function
+ into an atomic function for updating the global state.
Next we define our top-level component which uses the global state. A more complex application might use a bunch of independent components similar to HitCounter. This allows us to easily build things like session support, user accounts, etc, in third party reusable libraries. I believe it also makes atomic locks finer grained and makes it possible to support shards.
+ +> +> instance Component HitCounter where +> type Dependencies HitCounter = End +> initialValue = HitCounter 0 + +> entryPoint :: Proxy HitCounter +> entryPoint = Proxy + +The main
function starts up the state engine, forks off the fastAGI
server, waits for the server to receive a shutdown signal (for example, ^C), and then cleanly shuts down the state engine. The fastAGI
function comes from the Haskell AGI library, and is in no way HAppS specific.
Here is our simple AGI application. It
+ The functions answer
, streamFile
,
+ sayNumber
, and hangUp
come from the AGI
+ library.
The, update IncHits
call is our database query. Note
+ that we don't call the incHits
function
+ directly. Instead we call update
and pass it the value
+ IncHits
. The IncHits
type was created for
+ us automatically by the call to mkMethods
we made
+ earlier.
To test the application, first we need to update the asterisk dialplan to call our AGI application. Something like this should do the trick (be sure to reload the dialplan after modifying extensions.conf
):
+[default] +exten => 31415,1,AGI(agi://127.0.0.1) ++
Then we just run the server:
++ $ runhaskell HitCounter.lhs ++
Now, if we dial 31415
the magic should happen.
The above code is a good starting template for a more interesting AGI application. Note that caller number is a bit fuzzy. The caller number is determined by who gets to the update
function first -- which could be different than who actually connected to the asterisk server first.