[Freeswitch-users] Asynchronous communication with FreeSWITCH's mod_event_socket
Christian Jensen
christian at jensenbox.com
Fri Sep 19 11:24:30 EDT 2008
Use the pastebin please.
On Sep 19, 2008, at 7:00 AM, "Richard Open Source" <oss.richard at gmail.com
> wrote:
> oops...sorry for the mess...
>
>
> import org.apache.mina.common.IoSession
> import java.util.concurrent.BlockingQueue
> import java.util.concurrent.Executors
> import java.util.concurrent.ExecutorService
> import java.util.concurrent.Future
> import java.util.concurrent.Callable
> import java.util.concurrent.ExecutionException
> import java.util.concurrent.TimeoutException
> import java.util.concurrent.TimeUnit
>
>
>
> class Session {
> private IoSession session
> private FSEventHandler handler
> private BlockingQueue<String> msgQ
> private final ExecutorService executor =
> Executors.newSingleThreadExecutor()
> private final static long DEFAULT_TIMEOUT = 5000
>
> def data
>
> def Session(IoSession s, BlockingQueue q) {
> session = s
> msgQ = q
> }
>
> private def executeAndWait(Closure task, long timeout=0) {
> Future <CommandResult> f = executor.submit(task as Callable)
> def result
> def boolean success = false
> try {
> if (timeout != 0) {
> result = f.get(timeout,
> TimeUnit.MILLISECONDS)
> } else {
> result = f.get()
> }
> if (result.code == CommandResult.OK) data =
> result.data
> } catch (ExecutionException e) {
> // Should log here
> } catch (TimeoutException e) {
> f.cancel(true)
> }
>
> return result
> }
>
> def answer() {
> def task = {
> def done = false
> def r = new CommandResult()
> sendMessage("answer")
> while (! done) {
> def m = msgQ.take()
> if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE") && (m?.Application == "answer")) {
> done = true
> r.code = CommandResult.OK
> r.data = m
> }
> }
> return r
> }
> executeAndWait(task, DEFAULT_TIMEOUT)
> }
>
> def unset(var) {
> def task = {
> def done = false
> def r = new CommandResult()
> sendMessage("unset", var)
> while (! done) {
> def m = msgQ.take()
> if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> &&
> (m?.Application == "unset")
> &&
> (m?.ApplicationData == var)) {
> done = true
> r.code =
> CommandResult.OK
> r.data = m
> }
> }
> return r
> }
> executeAndWait(task, DEFAULT_TIMEOUT)
> }
>
> def queueDtmf(dtmfs) {
> def task = {
> def done = false
> def r = new CommandResult()
> sendMessage("queue_dtmf", dtmfs)
> while (! done) {
> def m = msgQ.take()
> if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> &&
> (m?.Application == "queue_dtmf")
> &&
> (m?.ApplicationData == dtmfs)) {
> done = true
> r.code =
> CommandResult.OK
> r.data = m
> }
> }
> return r
> }
> executeAndWait(task, DEFAULT_TIMEOUT)
> }
>
> /*
> def hangup() {
> def task = {
> def done = false
> def r = new CommandResult()
> sendMessage("hangup")
> while (! done) {
> def m = msgQ.take()
> if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> &&
> (m?.Application == "queue_dtmf")
> &&
> (m?.ApplicationData == dtmfs)) {
> done = true
> r.code =
> CommandResult.OK
> r.data = m
> }
> }
> return r
> }
> executeAndWait(task, DEFAULT_TIMEOUT)
> }
> */
>
> def setVariable(String var, String value) {
> def task = {
> def done = false
> def r = new CommandResult()
> sendMessage("set", "${var}=${value}")
> while (! done) {
> def m = msgQ.take()
> if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> &&
> (m?.Application == "set")
> &&
> (m?.ApplicationData == "${var}=${value}")) {
> done = true
> r.code =
> CommandResult.OK
> r.data = m
> }
> }
> return r
> }
> executeAndWait(task, DEFAULT_TIMEOUT)
>
> }
>
> def export(String var) {
> set("export_vars", var)
> }
>
> def bridge(String number) {
> def task = {
> def done = false
> def r = new CommandResult()
> sendMessage("bridge", number)
> while (! done) {
> def m = msgQ.take()
> // println m
> if (((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> &&
> (m?.Application == "bridge")
> &&
> (m?.ApplicationData == number)) ||
>
> ((m?.event?.Name == "CHANNEL_UNBRIDGE")
> &&
> (m?.variable.bridge_channel == number)) || (m?.SESSIONCLOSED ==
> "true")) {
> done = true
> r.code =
> CommandResult.OK
> r.data = m
> }
> }
> return r
> }
> executeAndWait(task)
> }
>
> def promptForDigits(int min, int max, String soundFile, String
> variableName, long timeout, String terminator) {
> def task = {
> def done = false
> def r = new CommandResult()
> def appData = "${min} ${max}
> ${soundFile} ${variableName} ${timeout} ${terminator}"
> sendMessage("read", appData, true)
> while (! done) {
> def m = msgQ.take()
> if ((m?.event?.Name ==
> "CHANNEL_EXECUTE_COMPLETE")
> &&
> (m?.Application == "read")
> &&
> (m?.ApplicationData == appData)) {
> done = true
> r.code =
> CommandResult.OK r.data
> = m
> }
> }
> return r
> }
> executeAndWait(task, timeout)
> }
>
> def originate(String url) {
> sendMessage("originate", url, true)
> }
>
> def sleep(int sec) {
> sendMessage("sleep", new Integer(sec*1000).toString())
> }
>
> def say(String phrase) {
> sendMessage("phrase", "spell,$phrase")
> }
>
> def script() {
> return "jeprox"
> }
>
> private def sendMessage(String app, String arg=null, boolean
> event_lock=false) {
> String msg = "sendmsg\ncall-command:
> execute\nexecute-app-name: ${app}"
> if (arg) {
> msg += "\nexecute-app-arg: ${arg}"
> }
> if (event_lock) {
> msg += "\nevent-lock: ${event_lock}"
> }
> System.out.println(msg)
> session.write("$msg\n\n")
> }
> }
>
> On Fri, Sep 19, 2008 at 9: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.
>> --
>> 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
>>>
>>
>
> _______________________________________________
> 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
More information about the Freeswitch-users
mailing list