fseventlistener.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. """
  2. FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
  3. Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
  4. Version: MPL 1.1
  5. The contents of this file are subject to the Mozilla Public License Version
  6. 1.1 (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.mozilla.org/MPL/
  9. Software distributed under the License is distributed on an "AS IS" basis,
  10. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. for the specific language governing rights and limitations under the
  12. License.
  13. The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
  14. The Initial Developer of the Original Code is
  15. Anthony Minessale II <anthm@freeswitch.org>
  16. Portions created by the Initial Developer are Copyright (C)
  17. the Initial Developer. All Rights Reserved.
  18. Contributor(s): Traun Leyden <tleyden@branchcut.com>
  19. """
  20. import sys
  21. from twisted.internet import reactor, defer
  22. from twisted.protocols.basic import LineReceiver
  23. from twisted.internet.protocol import Protocol, ClientFactory
  24. from twisted.python import failure
  25. import time, re
  26. from time import strftime
  27. from Queue import Queue
  28. from freepy import request
  29. """
  30. This class connects to freeswitch and listens for
  31. events and calls callback with the events.
  32. Example messages
  33. =================
  34. Content-Length: 675
  35. Content-Type: text/event-xml
  36. <event>
  37. <header name="force-contact" value="nat-connectile-dysfunction"></header>
  38. etc..
  39. </event>
  40. Content-Length: 875
  41. Content-Type: text/event-xml
  42. <event>
  43. ...
  44. </event>
  45. """
  46. class FreeswitchEventListener(LineReceiver):
  47. def __init__(self, conncb, discocb=None):
  48. self.delimiter='\n' # parent class uses this
  49. self.conncb=conncb
  50. self.discocb=discocb
  51. self.bufferlines = []
  52. self.receiving_event = False # state to track if in <event>..</event>
  53. self.requestq = Queue() # queue of pending requests
  54. self.active_request = None # the current active (de-queued) request
  55. def connectionMade(self):
  56. self.conncb(self)
  57. def connectionLost(self, reason):
  58. if self.discocb:
  59. self.discocb(reason)
  60. print "connectionLost: %s" % reason
  61. def login(self, passwd):
  62. """
  63. send login request
  64. """
  65. msg = "auth %s" % passwd
  66. req = request.LoginRequest()
  67. self.active_request = req
  68. self.transport.write("%s\n\n" % str(msg))
  69. return req.getDeferred()
  70. def sniff_events(self, output_type, events):
  71. """
  72. @param output_type - eg, xml or plain
  73. @param events - list of events, eg ['all']
  74. """
  75. event_list = " ".join(events)
  76. msg = "event %s %s" % (output_type, event_list)
  77. self.transport.write("%s\n\n" % str(msg))
  78. def sniff_custom_events(self, output_type, events):
  79. """
  80. when sniffing custom events, the CUSTOM keyword
  81. must be present in message
  82. http://wiki.freeswitch.org/wiki/Event_Socket#event
  83. @param output_type - eg, xml or plain
  84. @param events - list of events, eg ['all']
  85. """
  86. event_list = " ".join(events)
  87. msg = "event %s CUSTOM %s" % (output_type, event_list)
  88. self.transport.write("%s\n\n" % msg)
  89. def sniff_all_events(self, output_type):
  90. """
  91. @param output_type - eg, xml or plain
  92. """
  93. msg = "event %s all" % output_type
  94. self.transport.write("%s\n\n" % str(msg))
  95. def lineReceived(self, line):
  96. if not self.active_request:
  97. if line.find("<event>") != -1:
  98. self.receiving_event = True
  99. if self.receiving_event:
  100. self.bufferlines.append(line)
  101. if line.find("</event>") != -1:
  102. event_xml_str = "\n".join(self.bufferlines)
  103. self.eventReceived(event_xml_str)
  104. self.bufferlines = []
  105. self.receiving_event = False
  106. else:
  107. # we have an active request (seperate state machine)
  108. # tell the request to process the line, and tell us
  109. # if its finished or not. if its finished, we remove it
  110. # as the active request so that a new active request will
  111. # be de-queued.
  112. finished = self.active_request.process(line)
  113. if finished == True:
  114. self.active_request = None
  115. def eventReceived(self, event_xml_str):
  116. """
  117. should be overridden by subclasses.
  118. """
  119. raise Exception("This is an abstract class, should be overridden "
  120. "in a subclass")
  121. class FreeswitchEventListenerFactory(ClientFactory):
  122. def __init__(self, protoclass, host=None, passwd=None, port=None):
  123. """
  124. @param protoclass - a class (not instance) of the protocol
  125. should be a subclass of a FreeswitchEventListener
  126. """
  127. # dictionary of observers. key: event name, value: list of observers
  128. self.event2observer = {}
  129. self.protoclass=protoclass
  130. if host:
  131. self.host = host
  132. if passwd:
  133. self.passwd = passwd
  134. if port:
  135. self.port = port
  136. self.protocol = None
  137. self.connection_deferred = None
  138. self.num_attempts = 0
  139. def addobserver(self, event_name, observer):
  140. """
  141. @param event_name, eg "CHANNEL_ANSWER"
  142. @param observer (instance of object that has an eventReceived() method
  143. """
  144. observers = self.event2observer.get(event_name, [])
  145. observers.append(observer)
  146. self.event2observer[event_name] = observers
  147. def dispatch2observers(self, event_name, event_xml_str, event_dom):
  148. """
  149. called back by the underlying protocol upon receiving an
  150. event from freeswitch. Currently subclasses must explicitly
  151. call this method from their eventReceived method for observers
  152. to get the message. TODO: move this call to FreeswitchEventListener
  153. and use observer pattern instead of any subclassing.
  154. """
  155. observers = self.event2observer.get(event_name, [])
  156. for observer in observers:
  157. observer.eventReceived(event_name, event_xml_str, event_dom)
  158. def reset(self):
  159. self.protocol = None
  160. self.connection_deferred = None
  161. def connect(self):
  162. if self.protocol:
  163. # if we have a protocol object, we are connected (since we always
  164. # null it upon any disconnection)
  165. return defer.succeed(self.protocol)
  166. #if self.connection_deferred:
  167. # we are already connecting, return existing dfrd
  168. # return self.connection_deferred
  169. # connect and automatically login after connection
  170. if not self.connection_deferred:
  171. self.connection_deferred = defer.Deferred()
  172. self.connection_deferred.addCallback(self.dologin)
  173. self.connection_deferred.addErrback(self.generalError)
  174. reactor.connectTCP(self.host, self.port, self)
  175. return self.connection_deferred
  176. def conncb(self, protocol):
  177. self.protocol = protocol
  178. self.protocol.__dict__["factory"] = self
  179. deferred2callback = self.connection_deferred
  180. self.connection_deferred = None
  181. deferred2callback.callback(self.protocol)
  182. def generalError(self, failure):
  183. print "General error: %s" % failure
  184. return failure
  185. def startedConnecting(self, connector):
  186. pass
  187. def buildProtocol(self, addr):
  188. return self.protoclass(self.conncb, self.discocb)
  189. def clientConnectionLost(self, connector, reason):
  190. print "clientConnectionLost! conn=%s, reason=%s" % (connector,
  191. reason)
  192. self.connection_deferred = None
  193. self.protocol = None
  194. def clientConnectionFailed(self, connector, reason):
  195. if self.num_attempts < 10000:
  196. self.num_attempts += 1
  197. print "Connection to %s:%s refused, retrying attempt #%s in " \
  198. "5 seconds" % (self.host, self.port, self.num_attempts)
  199. return reactor.callLater(5, self.connect)
  200. else:
  201. print "clientConnectionFailed! conn=%s, reason=%s" % (connector,
  202. reason)
  203. print ("Retry attempts exhausted, total attempts: %s" %
  204. self.num_attempts)
  205. deferred2callback = self.connection_deferred
  206. deferred2callback.errback(reason)
  207. def discocb(self, reason):
  208. print "disconnected. reason: %s" % reason
  209. self.protocol = None
  210. def dologin(self, connectmsg):
  211. return self.protocol.login(self.passwd)
  212. def test1():
  213. fel = FreeswitchEventListener
  214. factory = FreeswitchEventListenerFactory(protoclass=fel,
  215. host="127.0.0.1",
  216. port=8021,
  217. passwd="ClueCon")
  218. def connected(result):
  219. print "We connected, result: %s" % result
  220. events=['sofia::register','sofia::expire']
  221. factory.protocol.sniff_custom_events(output_type="xml", events=events)
  222. #factory.protocol.sniff_all_events(output_type="xml")
  223. def failure(failure):
  224. print "Failed to connect: %s" % failure
  225. d = factory.connect()
  226. d.addCallbacks(connected, failure)
  227. d.addErrback(failure)
  228. reactor.run()
  229. if __name__=="__main__":
  230. test1()