Ruby for the Asterisk AJAM manager HTTP API

Posted on December 09, 2007

I'm not a big fan of line-oriented interfaces, so when I found the new (well, new-ish) Asterisk AJAX Manager Interface, AKA "AJAM," I figured it was a better fit for Ruby than the original.

My needs right now are very simple - set variables and redirect to a new extension. The variables that are set send Asterisk off to a new RESTPhone menu, where all the heavy lifting is done.

At some point this should become a gem, but here's the current, very simple, incarnation of how to talk to Asterisk via AJAM.

require 'rubygems'
require 'net/http'
require 'uri'
require 'xmlsimple'
require 'cgi'
require File.dirname(__FILE__) + '/rest_phone_utilities/uri_mixin'
require 'pp'
require 'pathname'

class AsteriskAjaxManager
  # The authorization cookie from the manager.
  attr_accessor :auth_cookie

  # The URI to send commands to.
  attr_accessor :uri

  # The Asterisk username
  attr_accessor :username
  # The Asterisk secret
  attr_accessor :secret

  # Takes:
  # 
  #   :uri => The URI for the Asterisk http server, not including /asterisk
  #   :path => The AJAM path (default: '/asterisk')
  #   :username => The Asterisk manager username (default: mark)
  #   :secret => The Asterisk manager password (default: mysecret)
  #   
  # (mark/mysecret is the default user in the Asterisk configuration file
  # samples.)
  def initialize(args = {})
    final_uri = args[:uri]
    final_uri ||= 'http://localhost:8088'
    final_uri = URI.parse(final_uri) unless URI === final_uri
    final_uri.path = '/asterisk' if final_uri.path.empty?
    final_uri.path = '/' if final_uri.path.empty?
    self.uri = final_uri
    self.username = args[:username] || 'mark'
    self.secret = args[:secret] || 'mysecret'
  end  

  def login
    login_uri = uri
    login_uri.path = (Pathname.new(uri.path) + 'manager').to_s
    login_uri.query = "action=login&username=#{username}&secret=#{secret}"

    response = Net::HTTP.get_response(login_uri)

    # The response should include an authorization cookie that we use 
    # on future requests.
    self.auth_cookie = response.response['set-cookie']
  end

  def execute
    login
    yield self
    logout
  end

  def logout

  end

  # ActionQuery is the http query sent off to AJAM.
  class ActionQuery
    attr_accessor :query
    # Takes:
    # 
    # [<tt>:uri</tt>]
    #   The uri (usually 'http://localhost:8088')
    # [<tt>:action</tt>]
    #   The action.  (Case-insensitive string or symbol)
    # [<tt>:params</tt>]
    #   Parameters for the action
    #   
    # Actions are:
    # 
    # status::
    # sippeers::
    def initialize(args = {})
      u = args[:uri]
      u = UriWithQueryCreation.parse(u.to_s)
      u.path = '/asterisk/mxml'
      u.append_hash_to_query 'action' => args[:action]
      u.append_hash_to_query args[:params]
      self.query = u
    end

    def to_s
      query.to_s
    end

    def net_http_obj
      Net::HTTP::Get.new("#{query.path}?#{query.query}")
    end
  end

  def sip_peers
    args = append_standard_arguments(:action => :sippeers)
    result = do_ajam_cmd_return_xml(args)
    result['response'][1..-2].map {|m| m['generic'].first}
  end

  def extension_state args = {}
    args = append_standard_arguments(:action => :extensionstate, :params => args)
    result = do_ajam_cmd_return_xml(args)
    result['response'].first['generic'].first
  end

  def redirect args = {}
    args = append_standard_arguments(
      :action => :redirect,
      :params => args
    )
    result = do_ajam_cmd_return_xml(args)
    result = result['response'].first['generic'].first
    if result['response'] =~ /Error/
      raise AsteriskAjaxManagerError, result['message']
    end
  end

  def setvar args = {}
    args = append_standard_arguments(
      :action => :setvar,
      :params => args
    )
    result = do_ajam_cmd_return_xml(args)
    result = result['response'].first['generic'].first
    if result['response'] =~ /Error/
      raise AsteriskAjaxManagerError, result['message']
    end
  end

  def append_standard_arguments(args = {})
    args.merge(:uri => uri, :cookie => auth_cookie)
  end

  # Takes:
  #   :uri => the uri (usually http://localhost) as a String or URI object
  #   :action => the action (status, SIPpeers, etc) as a symbol
  #   :cookie => the authorization cookie
  def do_ajam_cmd(args = {})
    u = args[:uri]
    u = UriWithQueryCreation.parse(u.to_s)
    result = Net::HTTP.start(u.host, u.port) do |h|
      q = ActionQuery.new :uri => 'http://localhost:8088/asterisk/mxml', :action => args[:action].to_s,
        :params => args[:params]
      req = q.net_http_obj
      req.add_field('Cookie', args[:cookie])
      h.request(req)
    end
    result.body
  end

  def do_ajam_cmd_return_xml(args = {})
    result = do_ajam_cmd(args)
    XmlSimple.xml_in(result)
  end

  class AsteriskAjaxManagerError < RuntimeError
    class LoginError < AsteriskAjaxManagerError
    end
  end
end