# README
Description
goesl
is a go library to connect to FreeSWITCH via event socket, only inbound now.
Installation
Installation can be done as usual:
$ go get github.com/genmzy/goesl
How it works
goesl.NewConnection
create a new esl connection and take a ConnHandler
interface
which defines the callbacks to handle the event-socket events and freeswitch logs.
Example of use
- A correct way to close connection immediately should like this:
conn, err := conn.Dial(addr, passwd, handler, opts...)
if err != nil {
// do some error log
return;
}
defer conn.Close()
ctx, cancel := context.WithCancel(context.Background())
// ...
go conn.HandleEvents(ctx, /* ... other params ... */)
// when time to quit:
if timeToQuit { // triggered should quit
log.Println("exiting...")
cancel()
// close twice is allowed
conn.Close()
}
- Here is a fs_cli-like program based on goesl:
package main
import (
"bufio"
"bytes"
"context"
"errors"
"flag"
"fmt"
"fs_monitor/goesl"
"fs_monitor/goesl/ev_header"
"fs_monitor/goesl/ev_name"
"net"
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"
)
type Handler struct {
// just test
CallId string
BgJobId string
}
var (
host = flag.String("H", "127.0.0.1", "Specify the host of freeswitch")
port = flag.Int("P", 8021, "Specify the port of freeswitch")
passwd = flag.String("p", "ClueCon", "Specify the default password of freeswitch")
level = flag.String("l", "debug", "Specify the log level of freeswitch")
logPath = flag.String("log_file", "", "Specify the log path of fs_cli self")
)
func main() {
flag.Parse()
go signalCatch()
opts := make([]goesl.Option, 0)
opts = append(opts,
goesl.WithDefaultAutoRedial(),
goesl.WithHeartBeat(20*time.Second),
goesl.WithNetDelay(2*time.Second),
goesl.WithMaxRetries(-1),
goesl.WithLogLevel(goesl.LevelDebug),
goesl.WithLogPrefix("<fs_cli>"),
)
if *logPath != "" {
logFile, err := os.OpenFile(*logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil {
goesl.Warnf("error when open log file %s: %v, use stdout now ...", *logPath, err)
} else {
opts = append(opts, goesl.WithLogOutput(logFile))
defer logFile.Close()
}
}
handler := &Handler{}
conn, err := goesl.Dial(fmt.Sprintf("%s:%d", *host, *port), *passwd, handler, opts...)
if err != nil {
goesl.Fatalf("connecting to freeswitch: %v", err)
}
// close twice is allowed
defer conn.Close()
ctx, cancel := context.WithCancel(context.Background())
go messageSend(ctx, conn, cancel)
err = conn.HandleEvents(ctx)
if errors.Is(err, net.ErrClosed) || errors.Is(err, context.Canceled) {
goesl.Noticef("process exiting...")
} else {
goesl.Errorf("exiting with error: %v", err)
}
}
// just a test, it doesn't matter with fs_cli
func (h *Handler) testApi(ctx context.Context, conn *goesl.Connection) {
const (
Caller = "1002"
Callee = "1001"
)
result := ""
check := func(res string, err error) {
if err != nil {
goesl.Errorf("api: %v", err)
return
}
result = res
fmt.Println(res)
}
check(conn.Api(ctx, "stat"))
check(conn.Api(ctx, "create_uuid"))
check(conn.BgApi(ctx, "originate", "{origination_uuid="+h.CallId+",origination_caller_id_number="+
Caller+"}user/"+Callee, "&echo()"))
h.BgJobId = result
goesl.Debugf("originate bg job id: %s", h.BgJobId)
}
func rawStatus2Profiles(raw string) [][]string {
s := bufio.NewScanner(bytes.NewBuffer([]byte(raw)))
start := false
res := make([][]string, 0)
for s.Scan() {
t := s.Text()
if strings.HasPrefix(t, "====") {
if start {
break
} else {
start = true
continue
}
}
if !start {
continue
}
t = strings.TrimSpace(t)
reg := regexp.MustCompile(`\s+`)
sli := reg.Split(t, -1)
res = append(res, sli)
}
return res
}
type SofiaEntry struct {
Name string
Type string // gateway or profile
Url string
Status string
Extra string
}
func (se SofiaEntry) String() string {
return fmt.Sprintf(`{"name":"%s","type":"%s","url":"%s","status":"%s","extra":"%s"}`,
se.Name, se.Type, se.Url, se.Status, se.Extra)
}
// return sofia entry groups
func SofiaStatus(raw []byte) (entries []*SofiaEntry) {
s := bufio.NewScanner(bytes.NewBuffer(raw))
start := false
for s.Scan() {
t := s.Text()
if t[0] == '=' { // ====================
if start { // end line
break
} else { // start line
start = true
continue
}
}
if !start {
continue
}
// content line now
t = strings.TrimSpace(t)
reg := regexp.MustCompile(`\s+`)
sli := reg.Split(t, -1)
entry := &SofiaEntry{Name: sli[0], Type: sli[1], Url: sli[2], Status: sli[3]}
if len(sli) > 4 {
entry.Extra = sli[4]
}
entries = append(entries, entry)
}
return
}
func (h *Handler) OnConnect(conn *goesl.Connection) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
conn.Plain(ctx, []ev_name.EventName{ev_name.BACKGROUND_JOB, ev_name.API}, nil)
conn.Fslog(ctx, goesl.FslogString2Level(*level))
die := func(s string, err error) string {
if err != nil {
conn.Close()
goesl.Fatalf("api: %v", err)
}
return s
}
core := die(conn.Api(ctx, "global_getvar", "core_uuid"))
raw := die(conn.Api(ctx, "sofia", "status"))
entries := SofiaStatus([]byte(raw))
goesl.Noticef("connect and get freeswitch (core: %s with sofia entries: %v)", core, entries)
}
func (h *Handler) OnDisconnect(conn *goesl.Connection, ev goesl.Event) {
goesl.Noticef("esl disconnected: %v", ev)
}
func (h *Handler) OnClose(con *goesl.Connection) {
goesl.Noticef("esl connection closed")
}
func (h *Handler) OnEvent(ctx context.Context, conn *goesl.Connection, e goesl.Event) {
fmt.Println(e.EventContent())
goesl.Debugf("fire time: %s", e.FireTime().StdTime().Format("2006-01-02 15:04:05"))
en := e.Name()
app, appData := e.App()
goesl.Debugf("%s - event %s %s %s", e.Uuid(), en, app, appData)
switch en {
case ev_name.BACKGROUND_JOB:
goesl.Noticef("background job %s result: %s", e.BgJob(), e.GetTextBody())
// HEARTBEAT be subscribed when WithDefaultAutoRedial called
case ev_name.HEARTBEAT:
hostname, err := conn.Send(ctx, "api global_getvar", "hostname")
if err != nil {
goesl.Errorf("send global_getvar error: %v", err)
return
}
goesl.Noticef("receive %s of %s heartbeat", conn.Address, hostname)
case ev_name.API:
goesl.Noticef("receive api event: %s(%s)", e.Get(ev_header.API_Command), e.Get(ev_header.API_Command_Argument))
}
}
var colorFormats = map[goesl.FslogLevel]string{
goesl.FslogLevel_DEBUG: "\033[0;33m%s\033[0m\n",
goesl.FslogLevel_INFO: "\033[0;32m%s\033[0m\n",
goesl.FslogLevel_NOTICE: "\033[0;36m%s\033[0m\n",
goesl.FslogLevel_WARNING: "\033[0;35m%s\033[0m\n",
goesl.FslogLevel_ERROR: "\033[0;31m%s\033[0m\n",
goesl.FslogLevel_CRIT: "\033[0;31m%s\033[0m\n",
goesl.FslogLevel_ALERT: "\033[0;31m%s\033[0m\n",
}
func getColorFormat(lv goesl.FslogLevel) string {
if format, ok := colorFormats[lv]; ok {
return format
} else {
return "%s\n\n"
}
}
func (h *Handler) OnFslog(ctx context.Context, conn *goesl.Connection, fslog goesl.Fslog) {
var content string
if fslog.UserData != "" {
content = fmt.Sprintf("%s %s", fslog.UserData, fslog.Content)
} else {
content = fslog.Content
}
fmt.Printf(getColorFormat(fslog.Level), content)
}
func send(ctx context.Context, conn *goesl.Connection, cmd string) {
check := func(res string, err error) {
if err != nil {
goesl.Errorf("api %s error: %v", cmd, err)
return
}
fmt.Println(res)
}
if cmd[0] == '/' {
check(conn.Send(ctx, cmd[1:]))
return
}
if strings.Split(cmd, " ")[0] == "originate" {
goesl.Noticef("now use bapi originate instead of originate")
check(conn.BgApi(ctx, cmd))
return
}
goesl.Debugf("send commands: %s", cmd)
check(conn.Api(ctx, cmd))
}
func messageSend(ctx context.Context, conn *goesl.Connection, cancel func()) {
s := bufio.NewScanner(os.Stdin)
host, err := conn.Api(ctx, "global_getvar", "hostname")
if err != nil {
goesl.Errorf("api global_getvar hostname send: %v", err)
return
}
cliPrefix := fmt.Sprintf("freeswitch/%s> ", host)
fmt.Print(cliPrefix)
for s.Scan() {
fmt.Print(cliPrefix)
switch cmd := s.Text(); cmd {
case "":
case "...", "/exit", "/bye", "/quit":
cancel()
// close twice is allowed
conn.Close()
return
default:
send(ctx, conn, cmd)
}
}
}
func signalCatch() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigs)
<-sigs
panic("show all goroutines")
}
TODO
- add documentation
- add tests
- more usage examples
# Functions
No description provided by the author
Create a new event socket connection and take a ConnectionHandler interface.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
need to pay attention that the `strategy` should handle that time.Duration is empty.
No description provided by the author
No description provided by the author
Set heartbeat interval time duration only take effect when set with `WithAutoRedial` event receiver regards connection lost when `heartbeat_interval + net_delay > time_wait`.
No description provided by the author
Set logger level, ONLY set level of internal logger if a user defined logger, do nothing.
Set logger output file, only set output file of set internal logger if a user defined logger, do nothing.
No description provided by the author
if n is -1, always retry.
Set max network delay time duration used as connection write timeout, event callback ticker timeout suggest range: 1*time.Second <= t <= 5*time.Second valid range: 100*time.Milliseconds <= t <= 10*time.Second.
Sender channel capacity.
# Constants
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
The levels of logs.
The levels of logs.
The levels of logs.
The levels of logs.
The levels of logs.
The levels of logs.
# Variables
No description provided by the author
SWITCH_LOG_ALERT.
SWITCH_LOG_CONSOLE.
SWITCH_LOG_CRIT.
SWITCH_LOG_DEBUG.
SWITCH_LOG_DEBUG1.
SWITCH_LOG_DEBUG10.
SWITCH_LOG_DEBUG2.
SWITCH_LOG_DEBUG3.
SWITCH_LOG_DEBUG4.
SWITCH_LOG_DEBUG5.
SWITCH_LOG_DEBUG6.
SWITCH_LOG_DEBUG7.
SWITCH_LOG_DEBUG8.
SWITCH_LOG_DEBUG9.
SWITCH_LOG_DISABLE.
SWITCH_LOG_ERROR.
SWITCH_LOG_INFO.
SWITCH_LOG_INVALID.
SWITCH_LOG_NOTICE.
SWITCH_LOG_UNINIT.
SWITCH_LOG_WARNING.
# Interfaces
No description provided by the author
Logger is a logger interface that output logs with a format.
No description provided by the author
# Type aliases
No description provided by the author
No description provided by the author
No description provided by the author
Level defines the priority of a log message.