ari Go Library

repository·main·Indexed 42 days ago

https://github.com/cycoresystems/ari

A Go library for interacting with the Asterisk Rest Interface (ARI). It provides low-level access to Asterisk objects and high-level extensions for telephony patterns such as media playback, bridge monitoring (bridgemon), and recording. The library supports Go 1.11+ and Asterisk 14+ (v6), with v5 available for Asterisk 13. It features a cloud-ready client configurable via environment variables and a staging mechanism to prevent lost events during resource creation.

Tokens
25.1K
Snippets
82
Records
117
Agent score
53%

What's inside ari

  1. Overview of ARI Extensions (/ext)

    main

    The library includes several high-level extensions in the /ext directory to simplify complex telephony tasks:

    • AudioURI: Utilities for constructing Asterisk audio playback URIs (handling digits and dates).
    • Bridgemon: A cache and proxy for bridge data that allows you to Watch() for state changes efficiently without redundant API calls.
    • Play: A tool for executing media playback sequences and waiting for DTMF responses (includes play.Play() and play.Prompt()).
    • Record: A session wrapper for managing recordings, including beep control, termination listening (hangup, DTMF, silence, timeout), and saving/reviewing recordings.
  2. How Resource Keys work

    main

    In multi-node Asterisk environments, a simple string ID is insufficient to identify a resource. ari uses the Key struct to provide a cluster-unique identifier that includes the resource type (Kind), the specific ID, the Asterisk Node it resides on, and an optional Dialog for logical grouping across nodes.

    To create a key, use ari.NewKey. You can also derive a new key from an existing one using the .New() method, which copies the location metadata (Node and Dialog) to the new resource type.

    // Simple key
    key := ari.NewKey(ari.ChannelKey, "myID")
    
    // Key with Node
    key := ari.NewKey(ari.BridgeKey, "myID", ari.WithNode("00:01:02:30:40:50"))
    
    // Key with Node and Dialog
    key := ari.NewKey(ari.ChannelKey, "myID",
       ari.WithNode("00:01:02:30:40:50"),
       ari.WithDialog("privateConversation"))
    
    // Derive a new key from an existing one (copies Node and Dialog)
    brKey := key.New(ari.BridgeKey, "myBridgeID")
  3. How Handles work

    main

    Handles are objects that bundle a resource's Key and the ari client connection together. They allow you to perform operations directly on the entity rather than passing keys into client methods.

    Using handles results in more readable code and is recommended for long-lived entities like channels and bridges. You can obtain a handle from a key using the .Get() method on the appropriate resource interface; this is a local operation and does not make a network call to Asterisk.

    // Instead of this:
    ariClient.Channel().Hangup(channelKey, "normal")
    
    // Use a handle:
    h := ariClient.Channel().Get(channelKey)
    h.Hangup()
  4. Use Bridge Monitor to monitor and cache bridge data

    main

    bridgemon is a tool designed to monitor and cache Asterisk bridge data, providing efficient and safe access for multi-threaded routines.

    Key Features

    • Thread Safety: Safe for use across multiple goroutines.
    • Lifecycle Management: Can be closed manually or automatically when the bridge is destroyed.
    • Data Access Patterns:
      • Polling/Arbitrary Access: Use the Data() method to retrieve the current cached state of the bridge.
      • Event-Driven Access: Use the Watch() method to receive updates via a channel whenever the bridge data changes.

    Initialization

    To create a monitor, pass an existing bridge handle to the constructor. Note that the bridge must already exist for the monitor to be operational; the initial state is loaded immediately upon creation.

  5. Configure Asterisk for ARI usage

    main

    To allow an ARI application to connect to Asterisk, you must configure both http.conf and ari.conf.

    1. Enable HTTP: In http.conf, ensure the HTTP server is enabled and bound to the correct address and port.
    2. Enable ARI: In ari.conf, enable ARI and define a user with appropriate permissions. Use allowed_origins = * for initial testing, but restrict this in production.
    3. Set up Credentials: Use an encrypted password for the ARI user. You can generate an encrypted password using the Asterisk CLI command ari mkpasswd <username>.
    # Generate an encrypted password for a user named 'admin'
    # asterisk -rx "ari mkpasswd admin"
    
    # http.conf configuration
    [general]
    enabled=yes
    bindaddr=127.0.0.1
    bindport=8088
    
    # ari.conf configuration
    [general]
    enabled = yes
    allowed_origins = *
    
    [admin]
    type = user
    read_only = no
    password_format = crypt
    password = <ENCRYPTED_PASSWORD>
  6. Staging resources to prevent lost events

    main

    To avoid the 'chicken-and-egg' problem where events occur before a subscription is active, ari provides StageXXXX methods. These methods return a handle for a resource without actually creating it in Asterisk.

    Workflow:

    1. Call the Stage variant (e.g., StagePlay) to get a handle.
    2. Set up all necessary subscriptions on that handle.
    3. Call .Exec() on the handle to actually create/start the resource in Asterisk.
    // 1. Stage the resource
    h := NewChannelHandle(key, c, nil)
    pb, err := h.StagePlay("myPlaybackID", "sound:tt-monkeys")
    if err != nil {
       return err
    }
    
    // 2. Add subscriptions
    startSub := pb.Subscribe(EventTypes.PlaybackStarted)
    defer startSub.Cancel()
    
    // 3. Execute the staged resource
    pb.Exec()
    
    // 4. Wait for events
    select {
    case <-startSub.Events():
       fmt.Println("playback started")
    }
  7. Install the ari library

    main

    To use the ari library in your Go application, install it using go get. This library requires Go version 1.11 or later and Asterisk version 14 or later. For Asterisk version 13, use v5 of the library instead.

    go get github.com/CyCoreSystems/ari/v6
  8. Mock Client methods using the EXPECT() pattern

    main

    The arimocks.Client provides an EXPECT() method that returns a Client_Expecter. This allows you to use a type-safe, fluent API to define mock behaviors instead of using the standard On(...).Return(...) syntax.

    For each method, you can:

    1. Call the method on the expecter (e.g., .Application()).
    2. Use .Return(value) to specify a static return value.
    3. Use .Run(func()) to execute custom logic when the method is called.
    4. Use .RunAndReturn(func() T) to provide a dynamic return value via a function.
    // Example: Mocking Application() to return a specific ari.Application
    client.EXPECT().Application().Return(myMockApplication)
    
    // Example: Mocking ApplicationName() to return a string
    client.EXPECT().ApplicationName().Return("my-app")
    
    // Example: Mocking a method with custom logic
    client.EXPECT().Connected().RunAndReturn(func() bool {
    	return true
    })
  9. Staging operations with StoredRecordingHandle.Exec()

    main

    The StoredRecordingHandle supports a staging pattern via the Exec() method. When a handle is created using NewStoredRecordingHandle, an execution function (exec) can be provided. This function is not called immediately; instead, it is triggered when Exec() is called.

    Exec() ensures idempotency by marking the handle as executed so that subsequent calls to Exec() do nothing.

    // Example of creating a handle with a staged operation
    handle := ari.NewStoredRecordingHandle(key, storedRecording, func(a *ari.StoredRecordingHandle) error {
        // This logic runs only when Exec() is called
        return a.Delete()
    })
    
    // Trigger the staged operation
    err := handle.Exec()
  10. How staged operations work with LiveRecordingHandle.Exec()

    main

    The LiveRecordingHandle supports a pattern where operations can be staged and then executed atomically or in a controlled sequence using the Exec() method.

    When a handle is created via NewLiveRecordingHandle, an exec function is provided. This function is called when h.Exec() is invoked. The Exec() method ensures that the execution logic is only run once per handle instance, using an internal mutex to prevent concurrent execution of the staged logic.

  11. Configure the ari client via environment variables

    main

    The client is cloud-ready and can be configured entirely through environment variables, which is ideal for Kubernetes deployments. If no configuration is provided, it defaults to http://localhost:8088/ari.

    ARI_APPLICATION      (*randomly-generated ID*)
    ARI_URL              http://localhost:8088/ari
    ARI_WSURL            ws://localhost:8088/ari/events
    ARI_WSORIGIN         http://localhost/
    ARI_USERNAME         (*none*)
    ARI_PASSWORD         (*none*)
  12. Use ChannelHandle for staged operations

    main

    A ChannelHandle is a wrapper around a Channel that allows you to stage multiple operations and execute them atomically or at a specific point in time using the .Exec() method.

    When you call a Stage... method (like StagePlay, StageRecord, or StageOriginate), the operation is not immediately sent to Asterisk. Instead, it is attached to the ChannelHandle. The operation is only performed when you call ch.Exec() on that handle.

    This is useful for ensuring that a sequence of commands is sent to the server in a single batch or at a controlled moment in your application logic.

    // Example of staging and executing an operation
    handle, err := ch.StagePlay("playback-id", "media-uri")
    if err != nil {
    	return err
    }
    
    // The playback hasn't happened yet...
    
    // Execute the staged operation
    err = handle.Exec()
    if err != nil {
    	return err
    }