[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