|
| 1 | +--- |
| 2 | +sidebar_position: 3 |
| 3 | +id: esl-inbound |
| 4 | +slug: /programming/esl-inbound |
| 5 | +title: "The Inbound Event Socket" |
| 6 | +description: "Connect to FreeSWITCH's event_socket listener from your own program: the wire protocol, the ESL client library, and a worked event-consuming loop." |
| 7 | +sidebar_label: "ESL: Inbound" |
| 8 | +--- |
| 9 | + |
| 10 | +# The Inbound Event Socket |
| 11 | + |
| 12 | +In **inbound** mode your program is the client: it opens a TCP connection *to* |
| 13 | +FreeSWITCH's `mod_event_socket` listener, authenticates, and then issues |
| 14 | +commands and consumes events for as long as it likes. This is the mode you use |
| 15 | +to build dashboards, click-to-call backends, dialers, billing collectors, and |
| 16 | +any out-of-band controller that talks to a running FreeSWITCH. |
| 17 | + |
| 18 | +Contrast this with **outbound** mode, covered in the sibling chapter |
| 19 | +[The Outbound Event Socket](/programming/esl-outbound), where the roles are |
| 20 | +reversed: the dialplan's `socket` application makes FreeSWITCH connect *out* to |
| 21 | +*your* listening server, handing you one call to control. Same wire protocol, |
| 22 | +opposite direction. Inbound is server-wide and persistent; outbound is per-call. |
| 23 | + |
| 24 | +The listener itself, its configuration parameters, and ACL restrictions are |
| 25 | +documented in the integration chapter |
| 26 | +[The Event Socket](/integration/event-socket). The events you will subscribe to |
| 27 | +are described in [The Event Model](./event-model.mdx) and the |
| 28 | +[events catalog](./events-catalog.mdx). This chapter is the programmer's |
| 29 | +companion: how to *write client code* against the socket. |
| 30 | + |
| 31 | +## The Connection Model {#the-connection-model} |
| 32 | + |
| 33 | +By default `mod_event_socket` listens on port `8021` with the password |
| 34 | +`ClueCon`. The shipped `event_socket.conf.xml` binds `listen-ip` to `::` |
| 35 | +(all interfaces, IPv6/IPv4), but the conventional and safe target for a local |
| 36 | +controller is `127.0.0.1:8021`. |
| 37 | + |
| 38 | +``` |
| 39 | +listen-port = 8021 |
| 40 | +password = ClueCon |
| 41 | +``` |
| 42 | + |
| 43 | +Source: `conf/vanilla/autoload_configs/event_socket.conf.xml`. The password is |
| 44 | +checked verbatim against the `auth` command in `mod_event_socket.c` |
| 45 | +(`parse_command`, `if (!strcmp(prefs.password, pass))`). Change it before |
| 46 | +exposing the socket to anything other than localhost. |
| 47 | + |
| 48 | +When a connection is accepted, FreeSWITCH speaks first. It sends an |
| 49 | +authentication challenge: |
| 50 | + |
| 51 | +``` |
| 52 | +Content-Type: auth/request |
| 53 | +
|
| 54 | +``` |
| 55 | + |
| 56 | +(`switch_snprintf(buf, ..., "Content-Type: auth/request\n\n")` in |
| 57 | +`mod_event_socket.c`.) Your program must respond with `auth` before any other |
| 58 | +command is accepted. |
| 59 | + |
| 60 | +## The Wire Protocol {#the-wire-protocol} |
| 61 | + |
| 62 | +Every message on the socket is a block of `Name: Value` headers terminated by a |
| 63 | +blank line, optionally followed by a body whose length is given by a |
| 64 | +`Content-Length` header. This is the same framing FreeSWITCH events use, so the |
| 65 | +protocol is symmetric: commands you send and events you receive look alike. |
| 66 | + |
| 67 | +### Commands {#commands} |
| 68 | + |
| 69 | +The commands below are the ones `parse_command` recognizes in |
| 70 | +`mod_event_socket.c`. Send each as a single line terminated by a blank line. The |
| 71 | +argument to each comparison in the source is shown so you can trust the spelling. |
| 72 | + |
| 73 | +| Command | Purpose | |
| 74 | +| --- | --- | |
| 75 | +| `auth <password>` | Authenticate. Required first. Replies `+OK accepted` or `-ERR invalid`. | |
| 76 | +| `api <cmd> <args>` | Run an API command and **block** until it returns; the result comes back as `Content-Type: api/response`. | |
| 77 | +| `bgapi <cmd> <args>` | Run an API command in the **background**; returns a `Job-UUID` immediately, the result arrives later as a `BACKGROUND_JOB` event. | |
| 78 | +| `event <format> <types>` | Subscribe to events. Format is `plain`, `json`, or `xml`. Use `event plain ALL` for everything. | |
| 79 | +| `nixevent <types>` | Unsubscribe from specific event types while keeping the rest. | |
| 80 | +| `noevents` | Unsubscribe from all events. | |
| 81 | +| `filter <header> <value>` | Restrict delivered events to those whose header matches; `filter delete ...` removes a filter. | |
| 82 | +| `sendevent <event-name>` | Inject an event into FreeSWITCH's event system. | |
| 83 | +| `sendmsg <uuid>` | Send a message/command to a specific channel (used to control calls). | |
| 84 | +| `exit` | Close the connection cleanly (replies `+OK bye`). | |
| 85 | + |
| 86 | +These names are verified against the `strncasecmp(cmd, "...")` checks in |
| 87 | +`mod_event_socket.c`: `auth ` (line ~1739), `api ` (~2277), `bgapi ` (~2322), |
| 88 | +`event` (~2454), `nixevent` (~2536), `noevents` (~2590), `filter ` (~1950), |
| 89 | +`sendevent` (~2230), `sendmsg` (~2169), and `exit` (~1732). Additional commands |
| 90 | +exist for log streaming (`log`, `nolog`) and the linger/divert mechanics used by |
| 91 | +outbound mode. |
| 92 | + |
| 93 | +### Command and Reply {#command-and-reply} |
| 94 | + |
| 95 | +Most commands get a synchronous **Command/Reply**. FreeSWITCH answers with: |
| 96 | + |
| 97 | +``` |
| 98 | +Content-Type: command/reply |
| 99 | +Reply-Text: +OK accepted |
| 100 | +
|
| 101 | +``` |
| 102 | + |
| 103 | +(`"Content-Type: command/reply\nReply-Text: %s\n\n"` in `mod_event_socket.c`.) |
| 104 | +A reply that starts with `+OK` succeeded; one that starts with `-ERR` failed and |
| 105 | +carries a reason, for example `-ERR invalid` or `-ERR command not found`. |
| 106 | + |
| 107 | +`api` is the exception: its result is not a `command/reply` but a separate |
| 108 | +framed block: |
| 109 | + |
| 110 | +``` |
| 111 | +Content-Type: api/response |
| 112 | +Content-Length: 5 |
| 113 | +
|
| 114 | ++OK 1 |
| 115 | +``` |
| 116 | + |
| 117 | +(`"Content-Type: api/response\nContent-Length: %ld\n\n"` in `api_exec`, |
| 118 | +`mod_event_socket.c`.) You must read exactly `Content-Length` bytes of body. |
| 119 | + |
| 120 | +### Blocking vs Background {#blocking-vs-background} |
| 121 | + |
| 122 | +`api uptime` blocks the socket until the command completes and returns the |
| 123 | +output inline. `bgapi originate ...` returns immediately: |
| 124 | + |
| 125 | +``` |
| 126 | +Content-Type: command/reply |
| 127 | +Reply-Text: +OK Job-UUID: 1b9be1e2-... |
| 128 | +Job-UUID: 1b9be1e2-... |
| 129 | +
|
| 130 | +``` |
| 131 | + |
| 132 | +(`"~Reply-Text: +OK Job-UUID: %s\nJob-UUID: %s\n\n"` in the `bgapi` branch of |
| 133 | +`mod_event_socket.c`.) The actual result is delivered later, only if you are |
| 134 | +subscribed to events, as a `BACKGROUND_JOB` event carrying the matching |
| 135 | +`Job-UUID` header and the command output in its body. The event is built in |
| 136 | +`api_exec`: |
| 137 | + |
| 138 | +```c |
| 139 | +switch_event_create(&event, SWITCH_EVENT_BACKGROUND_JOB); |
| 140 | +switch_event_add_header_string(event, ..., "Job-UUID", acs->uuid_str); |
| 141 | +switch_event_add_header_string(event, ..., "Job-Command", acs->api_cmd); |
| 142 | +switch_event_add_body(event, "%s", reply); |
| 143 | +``` |
| 144 | +
|
| 145 | +Use `bgapi` for anything slow (notably `originate`) so a single long call does |
| 146 | +not stall your control connection. |
| 147 | +
|
| 148 | +## The ESL Client Library {#the-esl-client-library} |
| 149 | +
|
| 150 | +You rarely hand-roll this framing. FreeSWITCH ships **ESL** (Event Socket |
| 151 | +Library) under `libs/esl/`, a small C library wrapped by SWIG into Lua, Python, |
| 152 | +Perl, PHP, Ruby, Java, and .NET bindings. All bindings expose the same two |
| 153 | +objects, `ESLconnection` and `ESLevent`, declared in |
| 154 | +`libs/esl/src/include/esl_oop.h`. |
| 155 | +
|
| 156 | +### ESLconnection {#eslconnection} |
| 157 | +
|
| 158 | +For inbound use you construct an `ESLconnection` with a host, port, and |
| 159 | +password. The constructor connects and performs the `auth` handshake for you. |
| 160 | +The methods you will use most, all from `esl_oop.h`: |
| 161 | +
|
| 162 | +| Method | What it does | |
| 163 | +| --- | --- | |
| 164 | +| `ESLconnection(host, port, password)` | Connect and authenticate (inbound). | |
| 165 | +| `connected()` | Returns nonzero while the socket is up. | |
| 166 | +| `api(cmd, arg)` | Send `api`, block, return an `ESLevent` whose body holds the result. | |
| 167 | +| `bgapi(cmd, arg, job_uuid)` | Send `bgapi`, return immediately; result arrives as a `BACKGROUND_JOB` event. | |
| 168 | +| `events(etype, value)` | Subscribe. `etype` is `plain`, `json`, or `xml`; `value` is the event list. | |
| 169 | +| `filter(header, value)` | Add a server-side event filter. | |
| 170 | +| `recvEvent()` | Block until the next event arrives; return it as an `ESLevent`. | |
| 171 | +| `recvEventTimed(ms)` | Like `recvEvent` but give up after `ms` milliseconds. | |
| 172 | +| `sendEvent(event)` | Send an `ESLevent` you built (the `sendevent` command). | |
| 173 | +| `sendMSG(event, uuid)` | Send a message to a channel (the `sendmsg` command). | |
| 174 | +| `disconnect()` | Close the connection. | |
| 175 | +
|
| 176 | +Under the hood these build the wire commands shown above. For example |
| 177 | +`api(cmd, arg)` emits `api <cmd> <arg>` and `bgapi` emits |
| 178 | +`bgapi <cmd> <arg>` with a `Job-UUID:` header (see `esl.c`, `esl_oop.cpp`). |
| 179 | +`events` emits `event <type> <value>` and `filter` emits |
| 180 | +`filter <header> <value>`. |
| 181 | +
|
| 182 | +### ESLevent {#eslevent} |
| 183 | +
|
| 184 | +Every event and every API result comes back as an `ESLevent`. Its accessors, |
| 185 | +from `esl_oop.h`: |
| 186 | +
|
| 187 | +| Method | What it returns | |
| 188 | +| --- | --- | |
| 189 | +| `getHeader(name)` | The value of a header, or null if absent. | |
| 190 | +| `getBody()` | The event body (the API output, or a message payload). | |
| 191 | +| `getType()` | The event name, e.g. `CHANNEL_ANSWER`. | |
| 192 | +| `serialize(format)` | The whole event as text (or `json`/`xml`). | |
| 193 | +| `addHeader(name, value)` | Set a header (when building an event to send). | |
| 194 | +| `addBody(value)` | Set the body (when building an event to send). | |
| 195 | +
|
| 196 | +## A Worked Example {#a-worked-example} |
| 197 | +
|
| 198 | +The example below uses the Lua binding (`libs/esl/lua`). The constructor, |
| 199 | +`con:api(...)`, and `e:getBody()` are exactly the pattern in the shipped |
| 200 | +`libs/esl/lua/single_command.lua`. The Python, Perl, and other bindings are |
| 201 | +identical method-for-method; only the dot-versus-colon syntax differs. |
| 202 | +
|
| 203 | +```lua |
| 204 | +-- inbound ESL: connect, run an API command, then watch channel events |
| 205 | +require("ESL") |
| 206 | +
|
| 207 | +-- ESLconnection(host, port, password) connects AND authenticates |
| 208 | +local con = ESL.ESLconnection("127.0.0.1", "8021", "ClueCon") |
| 209 | +
|
| 210 | +if con:connected() == 0 then |
| 211 | + print("could not connect to FreeSWITCH") |
| 212 | + return |
| 213 | +end |
| 214 | +
|
| 215 | +-- a blocking api call: result is the ESLevent body |
| 216 | +local info = con:api("status") |
| 217 | +print(info:getBody()) |
| 218 | +
|
| 219 | +-- kick off a call in the background; we get a Job-UUID back immediately |
| 220 | +con:bgapi("originate", "user/1000 &echo") |
| 221 | +
|
| 222 | +-- subscribe to all events in plain format so we receive BACKGROUND_JOB |
| 223 | +-- and the CHANNEL_* lifecycle |
| 224 | +con:events("plain", "ALL") |
| 225 | +
|
| 226 | +-- consume events forever |
| 227 | +while con:connected() == 1 do |
| 228 | + -- recvEvent() blocks for the next event; recvEventTimed(ms) is the |
| 229 | + -- timed variant |
| 230 | + local e = con:recvEvent() |
| 231 | + if e then |
| 232 | + local name = e:getHeader("Event-Name") |
| 233 | + if name == "CHANNEL_CREATE" |
| 234 | + or name == "CHANNEL_ANSWER" |
| 235 | + or name == "CHANNEL_HANGUP" then |
| 236 | + print(string.format("%s uuid=%s number=%s", |
| 237 | + name, |
| 238 | + e:getHeader("Unique-ID"), |
| 239 | + e:getHeader("Caller-Destination-Number"))) |
| 240 | + elseif name == "BACKGROUND_JOB" then |
| 241 | + -- the deferred result of our bgapi originate |
| 242 | + print("job " .. tostring(e:getHeader("Job-UUID")) |
| 243 | + .. " -> " .. tostring(e:getBody())) |
| 244 | + end |
| 245 | + end |
| 246 | +end |
| 247 | +``` |
| 248 | + |
| 249 | +The same program in Python is line-for-line equivalent (and matches the shipped |
| 250 | +`libs/esl/python3/events.py`): |
| 251 | + |
| 252 | +```python |
| 253 | +from ESL import ESLconnection |
| 254 | + |
| 255 | +con = ESLconnection("127.0.0.1", "8021", "ClueCon") |
| 256 | +if con.connected(): |
| 257 | + print(con.api("status").getBody()) |
| 258 | + con.events("plain", "ALL") |
| 259 | + while con.connected(): |
| 260 | + e = con.recvEvent() |
| 261 | + if e: |
| 262 | + print(e.getType(), e.getHeader("Unique-ID")) |
| 263 | +``` |
| 264 | + |
| 265 | +## Common Uses {#common-uses} |
| 266 | + |
| 267 | +**Monitoring channel lifecycle.** Subscribe with |
| 268 | +`con:events("plain", "CHANNEL_CREATE CHANNEL_ANSWER CHANNEL_HANGUP_COMPLETE")` |
| 269 | +and loop on `recvEvent()`. Each event's headers (`Unique-ID`, |
| 270 | +`Caller-Caller-ID-Number`, `Caller-Destination-Number`, `Hangup-Cause`) give you |
| 271 | +a complete picture of who is talking to whom. To reduce noise, subscribe only to |
| 272 | +the event types you need rather than `ALL`, or add a `filter` to narrow by |
| 273 | +header. |
| 274 | + |
| 275 | +**Placing and controlling calls.** Use `api`/`bgapi` to run any API command. The |
| 276 | +two you will reach for most: |
| 277 | + |
| 278 | +- `con:bgapi("originate", "user/1000 &park")` to start a call without blocking. |
| 279 | +- `con:api("uuid_kill", uuid)` or `con:api("uuid_transfer", uuid .. " 9999")` |
| 280 | + to act on a live channel by its `Unique-ID`. |
| 281 | + |
| 282 | +Because `bgapi` returns a `Job-UUID` and the real answer arrives later as a |
| 283 | +`BACKGROUND_JOB` event, a robust dialer keeps an events loop running, records |
| 284 | +the `Job-UUID` it got back, and matches it against the `Job-UUID` header on each |
| 285 | +`BACKGROUND_JOB` to learn the outcome of each `originate`. |
| 286 | + |
| 287 | +**Injecting events.** Build an `ESLevent`, populate it with `addHeader`, and send |
| 288 | +it with `con:sendEvent(...)` (the `sendevent` command) to fire custom events or |
| 289 | +trigger behavior in other modules. |
| 290 | + |
| 291 | +For the listener-side configuration, ACLs, the `event_socket.conf.xml` |
| 292 | +parameters, and the raw protocol walkthrough, see |
| 293 | +[The Event Socket](/integration/event-socket). For the per-call, |
| 294 | +FreeSWITCH-connects-to-you variant, see |
| 295 | +[The Outbound Event Socket](/programming/esl-outbound). |
0 commit comments