Tuesday, December 1, 2020

Asterisk ARI Introduction

 To enable:

Asterisk HTTP server is used to access ARI, WS, AMI. If you need WebSocket: The built-in Asterisk HTTP server is used to provide the WebSocket support. The res_http_websocket must also be built and loaded by Asterisk. For most individuals this is done by default.
You can’t view WebSockets in a standard browser. That's how the spec is written. If you view it in a standard browser the server will tell the protocol it needs to upgrade. This is to keep it in line with the HTTP spec. It doesn’t mean there is an upgrade. So don't try to access ws://yourAsteriskServerIP:8088/ws from a standard browser.

OpenAPI - specification standardizing describing of the REST API-services. This describtions can be in JSON or YAML.
Swagger – framework allowing you to describe the structure of your APIs using OpenAPI specification so that machines can read them. Also structure become more readable by humans via Swagger UI.
Don’t access ARI directly from a web page It’s very convenient to use ARI directly from a web page for development, such as using Swagger-UI, or even abusing the WebSocket echo demo to get at the ARI WebSocket. But, please, do not do this in your production applications. This would be akin to accessing your database directly from a web page. You need to hide Asterisk behind your own application server, where you can handle security, logging, multi-tenancy and other concerns that really don’t belong in a communications engine.

http.conf
[general]
enabled=yes
enablestatic=no
bindaddr=yourAsteriskServerIP
bindport=8088
servername=Asterisk 

ari.conf
[general]
enabled = yes
pretty = yes
allowed_origins = yourDeveloperHost
[testARI]
type = user
read_only = no
password = Sup3rM3ga$tr0ng
password_format = plain

asterisk -rx "module reload res_ari.so"
asterisk -rx "reload http"

It's highly recommended to use TLS and secured web socket (WSS)  server in the production environment:
openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem
mv /tmp/foo.pem  /var/lib/asterisk/

http.conf
tlsenable=yes
tlsbindaddr=yourAsteriskServerIP:8089
tlscertfile = /var/lib/asterisk/foo.pem
tlsprivatekey = /var/lib/asterisk/foo.pem

asterisk -rx "reload http"
asterisk -rx "http show status"

To check:

asterisk -rx "http show status"
asterisk -rx "ari show status"

From browser:
yourServerIP:8088/ari/api-docs/resources.json

To read in more user-friendly way:
  1. in ari.conf add http://ari.asterisk.org to the allowed_origins (using coma as origin separator).
  2. asterisk -rx "module reload res_ari.so" 
  3. access http://ari.asterisk.org enter yourAsteriskServerIP:8088/ari/api-docs/resources.json into the first input-placeholder, then username:password of the ARI to the second input-placeholder
  4. press "Explore"

Stasis Message Bus:

In Asterisk 12, a new core component was added to Asterisk: the Stasis Message Bus (SMB). As the name suggests, Stasis is an internal publish/subscribe message bus that lets the real-time core of Asterisk inform other modules or components – who subscribe for specific information topic – about events that occurred that they were interested in (for example: Stasis Message: Channel Hangup). SMB is used by AMI, CDR, CEL, ARI etc. Key concepts of the SMB:
  1. Message - group name of the event (ax: Channel Hangup)
  2. Publisher - source of the event (ex: Channel Core - publishes message)
  3. Subscriber - subscribes to a topic and receives messages of this topic (ex: AMI subscribed to channel event topic)
  4. Cache - Some Messages - particularly those that affect core communications primitives in Asterisk (such as channels or bridges) are stored in a special cache in Stasis. Subscribers have the option to query the cache for the last known state of those primitives.

Using Stasis diaplan application:

extensions.conf
[ARI-test]
exten => 1000,1,NoOp() 
same => n,Answer() 
same => n,Stasis(hello) 
same => n,Hangup()

 asterisk -rx "dialplan reload"

Some notes:
  1. You can't manage channel which is outside of the Stasis app
  2. You can't retrieve data from channels which are outside of the Stasis app
  3. When dialplan is in Stasis app - dialplan stays in until you exit Stasis
  4. Communication between app inside Stasis and client is done over WebSocket in asynchronous manner
  5. If nobody is connected to the app inside Stasis ("hello" app in the dialplan above) over WebSocket  - app will stop working and handover execution to the dialplan
If we have an endpoint in ARI-test context and make call to the 1000, we'll see the following in Asterisk CLI:
ERROR[39437][C-00000003]: res_stasis.c:1325 stasis_app_exec: Stasis app 'hello' not registered

The Stasis application is how a channel goes from the dialplan to a Stasis application. When a channel enters the Stasis application in the dialplan, a StasisStart event is sent to the application's associated WebSocket. So as written above - we must to have our own application which will connect to the WS serer and interact with ARI.

ARI Resources:

Resources below must be controlled by ARI in order to be reachable:
  1. asterisk - global variables,server info, configuration files etc.
  2. endpoints - list endpoint, send message etc.
  3. channels - list, create, redirect, answer, mute, ring, hold, moh, get variable, dial etc.
  4. bridges - construct sharing media among channels: list, create, add channel, remove channel etc.
  5. recordings - list, copy, stop, pause etc.
  6. sounds - list, get details etc.
  7. playbacks - playback control
  8. deviceStates - list, change etc.
  9. mailboxes - list, destroy etc.
  10. events - WbSocket events: generate etc.
  11. applications - list, subscribe etc.

Python aioari / ari:

With Python3:
pip install aioari # this is the latest version of ari.py using async swagger.py

Wiht Python2:
pip install ari

import aioari
import asyncio
client = await aioari.connect('http://yourServerIP:8088', 'username', 'password')
def stasis_start(obj, event):
    print('start')
def stasis_end(obj, event):
    print('stop')
client.on_channel_event('StasisStart', stasis_start)
client.on_channel_event('StasisEnd', stasis_end)
loop = asyncio.get_event_loop() 
loop.run_until_complete(client.run(apps="hello"))

To close loop:
loop.close()

After entering above in python shell:
 Creating Stasis app 'hello'
  == WebSocket connection from 'hostAccessingARI:65292' for protocol '' accepted using version '13'

Then we call 1000 to enter Stasis hello app:
    -- Executing [1000@ARI-test:1] NoOp("PJSIP/2222-00000017", "") in new stack
    -- Executing [1000@ARI-test:2] Answer("PJSIP/2222-00000017", "") in new stack
    -- Executing [1000@ARI-test:3] Stasis("PJSIP/2222-00000017", "hello") in new stack

In ipython shell we'll see: start

Hangup call:
stop # in ipython shell

asterisk -rx "stasis show topic ari:application/hello"
Name: ari:application/hello
Detail:
Subscribers count: 1
Forwarding topic count: 0
Duration time: 00:03:58

Ctrl+C in ipython shell (don't forget to close loop):
 Deactivating Stasis app 'hello'
 Shutting down application 'hello'
  == WebSocket connection from 'hostAccessingARI:65292' forcefully closed due to fatal write error
 Destroying Stasis app hello
    -- Remove stasis-hello/h/1, registrar=res_stasis; con=stasis-hello(0x7f0dac014d70); con->root=0x7f0dac015160
    -- Remove stasis-hello/_./1, registrar=res_stasis; con=stasis-hello(0x7f0dac014d70); con->root=0x7f0dac015160

Hangup() will only be executed if we call 1000 without previously connecting to the WebSocket. Also below will be in Asterisk CLI:
ERROR[24668][C-00000019]: res_stasis.c:1325 stasis_app_exec: Stasis app 'hello' not registered

To extend functionality of ARI app, you can use this link: