[Freeswitch-users] Asynchronous communication with FreeSWITCH's mod_event_socket
Christian Jensen
christian at jensenbox.com
Fri Sep 19 11:22:34 EDT 2008
Oh yes.... User data...
So one self assinged I'd and one user supplied.
I suggest a string as the data type with an arbitrary limit of 2k
On Sep 19, 2008, at 6:35 AM, "Richard Open Source" <oss.richard at gmail.com
> wrote:
> I agree.
>
> I have started working on a library like asterisk-java (now onhold
> but hopefully can continue working on it in a couple of weeks) using
> groovy (will name it fs-groovy :))
>
> The way I check if the command was successful is to look for the
> Event with CHANNEL_EXECUTE_COMPLETE and Application and
> ApplicationData.
>
> Here is how I implemented it. Still needs more work though.
> --
> 2
> 3 import org.apache.mina.common.IoSession
> 4 import java.util.concurrent.BlockingQueue
> 5 import java.util.concurrent.Executors
> 6 import java.util.concurrent.ExecutorService
> 7 import java.util.concurrent.Future
> 8 import java.util.concurrent.Callable
> 9 import java.util.concurrent.ExecutionException
> 10 import java.util.concurrent.TimeoutException
> 11 import java.util.concurrent.TimeUnit
> 12
> 13
> 14
> 15 class Session {
> 16 private IoSession session
> 17 private FSEventHandler handler
> 18 private BlockingQueue<String> msgQ
> 19 private final ExecutorService executor =
> Executors.newSingleThreadExecutor()
> 20 private final static long DEFAULT_TIMEOUT = 5000
> 21
> 22 def data
> 23
> 24 def Session(IoSession s, BlockingQueue q) {
> 25 session = s
> 26 msgQ = q
> 27 }
> 28
> 29 private def executeAndWait(Closure task, long timeout=0) {
> 30 Future <CommandResult> f = executor.submit(task as
> Callable)
> 31 def result
> 32 def boolean success = false
> 33 try {
> 34 if (timeout != 0) {
> 35 result = f.get(timeout,
> TimeUnit.MILLISECONDS)
> 36 } else {
> 37 result = f.get()
> 38 }
> 39 if (result.code == CommandResult.OK) data
> = result.data
> 40 } catch (ExecutionException e) {
> 41 // Should log here
> 42 } catch (TimeoutException e) {
> 43 f.cancel(true)
> 44 }
> 45
> 46 return result
> 47 }
> 48
> 49 def answer() {
> 50 def task = {
> 51 def done = false
> 52 def r = new CommandResult()
> 53 sendMessage("answer")
> 54 while (! done) {
> 55 def m = msgQ.take()
> 56 if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE") && (m?.Application == "answer")) {
> 57 done = true
> 58 r.code = CommandResult.OK
> 59 r.data = m
> 60 }
> 61 }
> 62 return r
> 63 }
> 64 executeAndWait(task, DEFAULT_TIMEOUT)
> 65 }
> 66
> 67 def unset(var) {
> 68 def task = {
> 69 def done = false
> 70 def r = new CommandResult()
> 71 sendMessage("unset", var)
> 72 while (! done) {
> 73 def m = msgQ.take()
> 74 if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> 75 &&
> (m?.Application == "unset")
> 76 &&
> (m?.ApplicationData == var)) {
> 77 done = true
> 78 r.code =
> CommandResult.OK
> 79 r.data = m
> 80 }
> 81 }
> 82 return r
> 83 }
> 84 executeAndWait(task, DEFAULT_TIMEOUT)
> 85 }
> 86
> 87 def queueDtmf(dtmfs) {
> 88 def task = {
> 89 def done = false
> 90 def r = new CommandResult()
> 91 sendMessage("queue_dtmf", dtmfs)
> 92 while (! done) {
> 93 def m = msgQ.take()
> 94 if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> 95 &&
> (m?.Application == "queue_dtmf")
> 96 &&
> (m?.ApplicationData == dtmfs)) {
> 97 done = true
> 98 r.code =
> CommandResult.OK
> 99 r.data = m
> 100 }
> 101 }
> 102 return r
> 103 }
> 104 executeAndWait(task, DEFAULT_TIMEOUT)
> 105 }
> 106
> 107 /*
> 108 def hangup() {
> 109 def task = {
> 110 def done = false
> 111 def r = new CommandResult()
> 112 sendMessage("hangup")
> 113 while (! done) {
> 114 def m = msgQ.take()
> 115 if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> 116 &&
> (m?.Application == "queue_dtmf")
> 117 &&
> (m?.ApplicationData == dtmfs)) {
> 118 done = true
> 119 r.code =
> CommandResult.OK
> 120 r.data = m
> 121 }
> 122 }
> 123 return r
> 124 }
> 125 executeAndWait(task, DEFAULT_TIMEOUT)
> 126 }
> 127 */
> 128
> 129 def setVariable(String var, String value) {
> 130 def task = {
> 131 def done = false
> 132 def r = new CommandResult()
> 133 sendMessage("set", "${var}=$
> {value}")
> 134 while (! done) {
> 135 def m = msgQ.take()
> 136 if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> 137 &&
> (m?.Application == "set")
> 138 &&
> (m?.ApplicationData == "${var}=${value}")) {
> 139 done = true
> 140 r.code =
> CommandResult.OK
> 141 r.data = m
> 142 }
> 143 }
> 144 return r
> 145 }
> 146 executeAndWait(task, DEFAULT_TIMEOUT)
> 147
> 148 }
> 149
> 150 def export(String var) {
> 151 set("export_vars", var)
> 152 }
> 153
> 154 def bridge(String number) {
> 155 def task = {
> 156 def done = false
> 157 def r = new CommandResult()
> 158 sendMessage("bridge", number)
> 159 while (! done) {
> 160 def m = msgQ.take()
> 161 // println m
> 162 if (((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> 163 &&
> (m?.Application == "bridge")
> 164 &&
> (m?.ApplicationData == number)) ||
> 165
> ((m?.event?.Name == "CHANNEL_UNBRIDGE")
> 166 &&
> (m?.variable.bridge_channel == number)) || (m?.SESSIONCLOSED ==
> "true")) {
> 167 done = true
> 168 r.code =
> CommandResult.OK
> 169 r.data = m
> 170 }
> 171 }
> 172 return r
> 173 }
> 174 executeAndWait(task)
> 175 }
> 176
> 177 def promptForDigits(int min, int max, String soundFile,
> String variableName, long timeout, String terminator) {
> 178 def task = {
> 179 def done = false
> 180 def r = new CommandResult()
> 181 def appData = "${min} ${max} $
> {soundFile} ${variableName} ${timeout} ${terminator}"
> 182 sendMessage("read", appData, true)
> 183 while (! done) {
> 184 def m = msgQ.take()
> 185 if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> 186 &&
> (m?.Application == "read")
> 187 &&
> (m?.ApplicationData == appData)) {
> 188 done = true
> 189 r.code =
> CommandResult.OK
> 190 r.data = m
> 191 }
> 192 }
> 193 return r
> 194 }
> 195 executeAndWait(task, timeout)
> 196 }
> 197
> 198 def originate(String url) {
> 199 sendMessage("originate", url, true)
> 200 }
> 201
> 202 def sleep(int sec) {
> 203 sendMessage("sleep", new
> Integer(sec*1000).toString())
> 204 }
> 205
> 206 def say(String phrase) {
> 207 sendMessage("phrase", "spell,$phrase")
> 208 }
> 209
> 210 def script() {
> 211 return "jeprox"
> 212 }
> 213
> 214 private def sendMessage(String app, String arg=null,
> boolean event_lock=false) {
> 215 String msg = "sendmsg\ncall-command: execute
> \nexecute-app-name: ${app}"
> 216 if (arg) {
> 217 msg += "\nexecute-app-arg: ${arg}"
> 218 }
> 219 if (event_lock) {
> 220 msg += "\nevent-lock: ${event_lock}"
> 221 }
> 222 System.out.println(msg)
> 223 session.write("$msg\n\n")
> 224 }
> 225 }
>
> On Thu, Sep 18, 2008 at 5:49 PM, Luke Graybill <killarny at gmail.com>
> wrote:
> I've been doing a lot of work recently with FreeSWITCH's
> mod_event_socket, and I wanted to comment a bit about the syntax
> used for commands through the socket while using asynchronous mode.
> I haven't tried the synchronous mode yet, as I always want to be
> free to be able to execute commands without waiting for other
> commands to finish. For instance, I need to be able to collect DTMF
> events while I'm playing a sound file, so that the user can do
> things like select menu items without listening to the entire menu
> first.
>
> Asterisk's AMI protocol allows you to specify an ActionID along with
> every command that you send. Asterisk then includes this ActionID
> with every event that is related to that command, making it cake to
> coordinate an asynchronous client.
>
> However, even in async mode, FreeSWITCH's mod_event_socket doesn't
> communicate any identifying information for command responses, or
> for events triggered by a previous command, unless one uses the
> bgapi command set. This command set is not applicable to every
> situation, though. It only applies to commands which manipulate the
> call; if one needs to manipulate the channel, then messages must be
> used through the sendmsg command set, which doesn't provide any
> specific identifying information.
>
> Now, to complicate things, with all commands (even bgapi) the
> protocol works something like this: you send a command, and wait for
> a response from mod_event_socket. This response is assumed to be
> immediate before anything else the client might receive from
> mod_event_socket, and in the case of bgapi, this response will
> contain a job-id to use for comparing job-related events later.
>
> For example, for the following command:
>
> sendmsg
> call-command: execute
> execute-app-name: answer\n\n
>
> The response is this:
>
> Content-Type: command/reply
> Reply-Text: +OK
>
> That response is generic to nearly every single command sent, and is
> only really saying "The last transmission was a valid command, and
> didn't immediately fail". The command may actually fail later, and
> command specific feedback is generally contained in later events
> (which have no unique identifying information).
>
> My issue here is that this seems to be forcing an asynchronous
> client to rely upon a synchronous ordering of response directly
> following command, thus violating the very concepts of an
> asynchronous protocol in which there should be no assumed order. Not
> only that, but this method increases the complexity of a client,
> which must be aware of limitations that wouldn't ordinarily be
> required by a true asynchronous protocol. An asynchronous client
> should be unconcerned with listening for a synchronous response to
> every command.
>
> My suggested solution is to apply the job-id concept from bgapi to
> messages as well, and to go a step further; borrow the Asterisk idea
> of transmitting an identifier along with each command. Every
> response and event related to that command should then contain the
> very same identifier in the header.
>
> _______________________________________________
> Freeswitch-users mailing list
> Freeswitch-users at lists.freeswitch.org
> http://lists.freeswitch.org/mailman/listinfo/freeswitch-users
> UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users
> http://www.freeswitch.org
>
>
> uld then contain the very same identifier in the header.
> _______________________________________________
> Freeswitch-users mailing list
> Freeswitch-users at lists.freeswitch.org
> http://lists.freeswitch.org/mailman/listinfo/freeswitch-users
> UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users
> http://www.freeswitch.org
>
>
> v>
> _______________________________________________
> Freeswitch-users mailing list
> Freeswitch-users at lists.freeswitch.org
> http://lists.freeswitch.org/mailman/listinfo/freeswitch-users
> UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users
> http://www.freeswitch.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.freeswitch.org/pipermail/freeswitch-users/attachments/20080919/7ab5029a/attachment-0001.html
More information about the Freeswitch-users
mailing list