Search the Asterisk Blog

ARICPP: an ARI library for modern C++

By Daniele Pallastrelli

As is well known, there are several ways to extend Asterisk features, but if you want to exploit the full power of its raw primitive objects — channels, bridges, endpoints, media, etc. — you really need to use ARI. On the other hand, if the performances of your application are important, chances are you’re using C or C++. Unfortunately, until now there was no C/C++ library available to interface with ARI, and even finding a good HTTP/WebSocket library was certainly not child’s play.

Some months ago, I started working on a C++ project that required a strict interaction with Asterisk internal objects and, not finding a library, I decided to develop one myself. The result is aricpp: a C++14 library for Asterisk ARI interface, released under the Boost Software License.

This is a short summary of its main characteristics :

  • it’s multi-platform. It uses standard ISO C++ so you will be able to use it on every platform having a decent C++ compiler.
  • It’s header only. No need to compile the library to use it: just include aricpp header files in your project and you’re done.
  • It’s asynchronous (more on this later).
  • It provides a high-level interface for the manipulation of asterisk concepts (channels, bridges, …) developed on top of a low-level interface with primitives to send and receive ARI commands and events from asterisk. The two interfaces coexist and can be mixed in the same client application.

Last but not least, aricpp is production-ready. Though I’d like to add more features, the library is already used in several projects, running 24/7 under high call loads.

Aricpp design

When you develop an application subject to a high rate of incoming requests, going asynchronous is a good way to improve performances, compared to spanning a new thread per request. Furthermore, with an asynchronous event-driven model, a single thread can handle multiple requests, so that the developer doesn’t have to worry about synchronization, thus reducing latencies and avoiding potential races and deadlocks.

Aricpp design is asynchronous at the core, and its features are exposed to the client through non-blocking APIs taking continuation callbacks as parameters.

Please note that having non-blocking APIs doesn’t necessarily mean the client application must be single threaded. On the contrary, the async APIs give the user of the library the freedom to choose which thread model adopt:

  • single thread
  • one thread per request
  • a thread pool

though I strongly suggest using a single thread, since (in this case) I can’t see any real benefit in the other two options.

Aricpp takes advantage of C++14 standard, that provides lots of useful abstractions ready to use. Nevertheless, it has dependencies from two other libraries: boost and beast. Beast is the library that provides HTTP and WebSockets, that in turns requires boost libraries. It’s planned that beast will be integrated into boost 1.66 (the next release, at the time I’m writing) so that in a few weeks aricpp will depend only on boost libraries.

While I don’t like very much beast interface design (I hope it will be improved during the process of integration in boost 1.66 ), I chose to use it anyway because it follows boost ASIO idioms (and also because it’s the only HTTP/WebSocket asynchronous library available :-).

With the help of beast library, aricpp provides its low-level interface: the class  aricpp::Client  having the two public methods  RawCmd and OnEvent  (respectively to send HTTP commands to asterisk and to receive JSON formatted events from asterisk).

The high-level interface aricpp::AriModel provides classes that reflect the asterisk objects ( aricpp::Channel , aricpp::Bridge  and aricpp::Recording ) and exposes methods to register callbacks for the main events:

Ok, show me some code!

Since aricpp is based on boost asio async library, you must have an instance of  boost::asio::io_service running. Then, aricpp features are available through an instance of  aricpp::Client :

The client can establish a connection with asterisk using the method Client::Connect :

Once connected, you can register for raw events by using the method Client::OnEvent :

As you can see from the snippet, aricpp provides a couple of handy functions to manipulate JSON data for when you’re using the low-level interface.

Finally, you can send requests by using the method  Client::RawCmd :

As already mentioned, aricpp also provides a higher level interface, with which you can manipulate asterisk telephonic objects (e.g., channels).

To use this interface, you need to create an instance of the class  aricpp::AriModel, on which you can register for channel events (  AriModel::OnStasisStarted ,  AriModel::OnStasisDestroyed ,   AriModel::OnChannelStateChanged  ) and allows you to create channels ( AriModel::CreateChannel() ).

All these methods give you references to  aricpp::Channel  objects, that provide the methods for the usual actions on asterisk channels (e.g., ring, answer, hangup, dial, …).

The following code connects to asterisk, calls the endpoint pjsip/Bob  and waits for a channel to enter in the stasis application, differentiating between channels created by the library and external ones.

As you can see, the high-level interface offers a fluent syntax to specify asynchronous callbacks:

This code basically means:

  1. require asterisk to perform an originate using the channel ch ,
  2. if aricpp receives a positive response from asterisk, the code specified with  After(...) is executed,
  3. if aricpp receives an error response, instead, the code specified with  OnError(...) is executed.

Please note that these are only convenience methods to specify callbacks, that improve a bit the syntax with respect to the usual way to pass them as a parameter, but by no means they’re “futures”. Even if you can chain multiple After methods, they would be all executed in sequence when asterisk will have issued its response. So, these methods don’t save you from the so-called “callback pyramid” in those (rare) cases when you need to chain a sequence of ARI requests.

For this reason, I’d like to provide in the next release a new interface using futures, so that a client could write something like:

instead of the current:

As you can see, an interface with futures would permit code more compact.

Please note that the high and low-level interfaces can coexist. Since the high-level interface does not have yet classes for all the asterisk objects (i.e., devices, endpoints, mailboxes,… ), in the meantime you can use the low-level interface for the missing commands.

Performances

From the very first release of aricpp, my goal was to provide a library as much as reliable as possible and optimized to work with a heavy load of telephonic traffic. For that reason, after writing the core of the library, I developed a simple dial application based on aricpp in order to take some measurements with a SIP traffic generator and try to optimize the library.

As a plan on paper, it was a good one. Except that this process led me to discover an asterisk performance issue, instead. During my tests, I discovered a uniform distribution latency in the range [0, 200ms] every time asterisk receives an ARI command. I discussed the topic in the community and then I filed an issue that (at the time I’m writing) has been aknowledged but not closed yet.

To cut it short: I used the traffic generator anyway, but only to prove that the library is correct and robust. I hope asterisk developers will remove the latency soon, so that we can test aricpp performances and optimize it, and — above all — create ARI clients able to managing high traffic loads.

Miles to go…

Aricpp is already production ready, it is used in mission-critical projects, and runs twenty-four hours a day under heavy load. However, some improvements can be made:

  • add the missing asterisk ARI objects to the high-level interface: devices, endpoints, mailboxes…
  • provide a better syntax to simplify the code needed for an asynchronous sequence of operation (i.e., custom futures)
  • improve the error handling mechanism
  • provide new examples and documentation.

In the meantime, any comment about the library is welcome.

No Comments Yet

Get the conversation started!

Add to the Discussion

Your email address will not be published. Required fields are marked *

About the Author

Daniele Pallastrelli

Passionate about software design. Speaker. Author. Runner.

See All of Daniele's Articles

More From
The Digium Blog

  • No items