Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

113 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

eslgo

PkgGoDev Go Report Card GitHub license

eslgo is a FreeSWITCH™ ESL (Event Socket Library) client/server library for GoLang, written from the ground up in idiomatic Go. It supports both inbound and outbound ESL connections and is designed to handle production traffic at thousands of calls per second.

This is a fork of percipia/eslgo adding bounded outbound linger/close-delay and — for inbound clients — TCP keepalive, an optional heartbeat, and transparent auto-reconnect (see What's different in this fork). All credit for the original library goes to Percipia and Andrew Querol — see Credits.

What's different in this fork

Inbound heartbeat & auto-reconnect

Long-lived inbound clients need to survive network blips, FreeSWITCH restarts and NAT drops. Enable it via InboundOptions:

opts := eslgo.DefaultInboundOptions
opts.Password = "ClueCon"
opts.Heartbeat = 20 * time.Second // active `api status` probe; 0 = off
opts.AutoReconnect = true         // re-dial + re-auth after any drop
opts.OnReconnect = func() { /* optional extra per-connection setup */ }

conn, err := opts.Dial("127.0.0.1:8021")
// The conn you hold stays valid across reconnects — keep using it.

What you get:

  • TCP keepalive — on by default (30s; TCPKeepAlive to tune, negative to disable). Detects half-open connections (peer crash / NAT reclaim with no FIN/RST) so a dead socket surfaces as an error instead of hanging forever.
  • Heartbeat (optional, configurable) — when Heartbeat > 0, a lightweight api status is sent every interval and must reply within HeartbeatTimeout; a miss drops the connection (and reconnects if enabled). Catches application-level hangs that TCP keepalive can't see.
  • Transparent auto-reconnect — the same *Conn is revived (swap socket, re-auth) with exponential backoff (ReconnectMinBackoff/ReconnectMaxBackoff/ReconnectMaxAttempts). OnReconnect fires after each successful reconnect.
  • No re-subscription in your code — on reconnect eslgo automatically re-issues your event subscriptions to FreeSWITCH, and your registered event handlers stay attached to the same *Conn. The business layer keeps receiving events as before without subscribing again. (Events FreeSWITCH emits during the brief downtime aren't replayed — by design, since replaying stale call events is meaningless.)

Connection pool & multi-server cluster

A single inbound connection is a serial request/reply channel — replies aren't multiplexed, so concurrent SendCommand calls queue behind each other (event delivery is unaffected). For command-heavy workloads, Pool keeps several command connections to one FreeSWITCH and dispatches each command to the least-busy one, plus one dedicated connection for events (received once, not once per connection):

opts := eslgo.PoolOptions{CommandConns: 4, WithEventConn: true}
opts.Password = "ClueCon"
opts.AutoReconnect = true

pool, _ := eslgo.NewPool("127.0.0.1:8021", opts)
defer pool.Close()

pool.OnEvent(func(e *eslgo.Event) { /* events, delivered once */ })
pool.EnableEvents(ctx)

pool.SendCommand(ctx, command.API{Command: "status"}) // runs on the least-busy connection
conn, _ := pool.Dedicated()                            // or grab an isolated connection for a long command

Cluster manages one resilient Pool per FreeSWITCH across many servers, aggregating every server's events into one host-tagged callback, with broadcast and per-host routing:

cl := eslgo.NewCluster()
defer cl.Close()
cl.OnEvent(func(host string, e *eslgo.Event) { /* events from every server, tagged */ })
cl.Add("10.0.0.1:8021", opts)
cl.Add("10.0.0.2:8021", opts)
cl.EnableEvents(ctx)

cl.Broadcast(ctx, command.API{Command: "status"}) // to every server
cl.On("10.0.0.1:8021").SendCommand(ctx, cmd)       // to one server

// Originate a new call: the cluster load-balances (default round-robin; override
// with SetBalancer) and returns the chosen host to route the call's later commands.
host, resp, _ := cl.OriginateCall(ctx, true, aLeg, bLeg, vars)

Where does host come from? You never guess it. For calls you originate, OriginateCall returns it. For inbound calls, it arrives with every event via OnEvent(host, e). Store it alongside your own per-call state and route later commands with On(host) — no global uuid -> host map to maintain (the library keeps no per-call state, and routing by UUID would require exactly such a map).

Outbound linger & close-delay

FreeSWITCH's linger lets a socket stay open after a channel hangs up so you can still receive trailing events (CHANNEL_HANGUP_COMPLETE, CDR events, etc.). This fork adds two related pieces on top of upstream:

1. linger <seconds> command — ask FreeSWITCH to linger for a bounded number of seconds instead of the all-or-nothing linger / nolinger:

// linger for 30 seconds; pass a time.Duration
_, _ = conn.SendCommand(ctx, command.Linger{Enabled: true, Seconds: 30 * time.Second})
command.Linger ESL sent
{Enabled: true, Seconds: 30 * time.Second} linger 30
{Enabled: true} (zero duration) linger
{Enabled: false} nolinger

2. Conn.SetCloseDelay for outbound connections — control how long eslgo waits after FreeSWITCH disconnects before it closes the socket, giving your handler a grace window to finish processing:

conn.SetCloseDelay(30 * time.Second) // close 30s after disconnect
// SetCloseDelay(0)  -> close immediately (default)
// SetCloseDelay(<0) -> never auto-close on disconnect (caller-managed)

Note: both Linger.Seconds and SetCloseDelay take a time.Duration and are honoured at second granularity, so pass durations like 30 * time.Second.

Install

go get github.com/luoyumin/eslgo
import "github.com/luoyumin/eslgo"

Overview

  • Inbound ESL connection (client)
  • Outbound ESL server
  • Event listeners by UUID or for all events
    • Unique-Id
    • Application-UUID
    • Job-UUID
  • Plain, JSON or XML event formats — EnableEvents(ctx, "json") (or "xml"); all are parsed into the same Event (use GetName() / GetHeader(...) regardless of format)
  • context support for cancelling requests
  • All command types abstracted out
    • Send custom data by implementing the Command interface — BuildMessage() string
  • Bounded linger and configurable close-delay for outbound connections (this fork)
  • Inbound TCP keepalive, optional heartbeat, and transparent auto-reconnect with subscription replay (this fork)
  • Connection Pool (concurrent commands to one server) and Cluster (many servers, event aggregation, broadcast) (this fork)
  • Basic helpers for common tasks
    • DTMF
    • Call origination
    • Call answer / hangup
    • Audio playback

Examples

Buildable examples live under the example directory — including resilient (heartbeat + auto-reconnect), pool (concurrent commands to one server), and cluster (many servers). They are illustrative only — they expect a running FreeSWITCH and are not run or verified by the test suite; see example/README.md.

Outbound ESL Server

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/luoyumin/eslgo"
)

func main() {
	// Start listening. This is a blocking call.
	log.Fatalln(eslgo.ListenAndServe(":8084", handleConnection))
}

func handleConnection(ctx context.Context, conn *eslgo.Conn, response *eslgo.RawResponse) {
	fmt.Printf("Got connection! %#v\n", response)

	// Place a foreground (api) call to user/100 and play an audio file as the bLeg, no exported variables.
	response, err := conn.OriginateCall(
		ctx,
		false,
		eslgo.Leg{CallURL: "user/100"},
		eslgo.Leg{CallURL: "&playback(misc/ivr-to_hear_screaming_monkeys.wav)"},
		map[string]string{},
	)
	fmt.Println("Call Originated: ", response, err)
}

Inbound ESL Client

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/luoyumin/eslgo"
)

func main() {
	// Connect to FreeSWITCH.
	conn, err := eslgo.Dial("127.0.0.1:8021", "ClueCon", func() {
		fmt.Println("Inbound Connection Disconnected")
	})
	if err != nil {
		fmt.Println("Error connecting", err)
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()

	// Place a background (bgapi) call to user/100 and play an audio file as the bLeg, no exported variables.
	response, err := conn.OriginateCall(
		ctx,
		true,
		eslgo.Leg{CallURL: "user/100"},
		eslgo.Leg{CallURL: "&playback(misc/ivr-to_hear_screaming_monkeys.wav)"},
		map[string]string{},
	)
	fmt.Println("Call Originated: ", response, err)

	// Close the connection after a short sleep.
	time.Sleep(60 * time.Second)
	conn.ExitAndClose()
}

Credits

This project is a fork of percipia/eslgo, originally written by Andrew Querol and Percipia. The original license and copyright notices are preserved in every source file and in LICENSE. Huge thanks to the upstream authors — this fork only adds the linger / close-delay features described above on top of their work.

License

eslgo is licensed under the Mozilla Public License 2.0 (MPL-2.0), the same license as the upstream project. See LICENSE for the full text.

About

FreeSWITCH ESL library for Go — fork of percipia/eslgo with bounded linger, close-delay, heartbeat & auto-reconnect

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages