request.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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. from twisted.python.failure import Failure
  26. import time, re
  27. from time import strftime
  28. from Queue import Queue
  29. from freepy import models
  30. import freepy.globals
  31. from freepy.globals import debug
  32. """
  33. These are response handlers for different types of requests.
  34. It reads the response from freeswitch, and calls back
  35. self.deferred with the result.
  36. The naming could be improved, but here is the translation:
  37. LoginRequest - Response handler for a login request
  38. """
  39. class FreepyRequest(object):
  40. def __init__(self):
  41. self.deferred = defer.Deferred()
  42. self.response_content = ""
  43. self.finished = False
  44. def isRequestFinished(self):
  45. return self.finished
  46. def setRequestFinished(self):
  47. debug("setRequestFinished called. response_content: %s " %
  48. self.response_content)
  49. self.finished = True
  50. def getDeferred(self):
  51. return self.deferred
  52. def callbackDeferred(self, cbval):
  53. self.deferred.callback(cbval)
  54. def errbackDeferred(self, result):
  55. self.deferred.errback(Exception(str(result)))
  56. def process(self, line):
  57. """
  58. processs a line from the fs response. if the fs response has been
  59. detected to be finished, then:
  60. * create an appropriate response based on request type
  61. * callback deferred with response
  62. * rturn True to indicate we are finished
  63. otherwise, if the fs response is incomplete, just buffer the data
  64. """
  65. if not line.strip() or len(line.strip()) == 0:
  66. self._fsm.BlankLine()
  67. return self.isRequestFinished()
  68. matchstr = re.compile("auth/request", re.I)
  69. result = matchstr.search(line)
  70. if (result != None):
  71. self._fsm.AuthRequest()
  72. return self.isRequestFinished()
  73. matchstr = re.compile("command/reply", re.I)
  74. result = matchstr.search(line)
  75. if (result != None):
  76. self._fsm.CommandReply()
  77. return self.isRequestFinished()
  78. matchstr = re.compile("Reply-Text", re.I)
  79. result = matchstr.search(line)
  80. if (result != None):
  81. debug("FREEPY: got Reply-Text")
  82. fields = line.split(":") # eg, ['Reply-Text','+OK Job-UUID', '882']
  83. endfields = fields[1:]
  84. self.response_content = "".join(endfields)
  85. self._fsm.ReplyText()
  86. return self.isRequestFinished()
  87. matchstr = re.compile("Job-UUID", re.I)
  88. result = matchstr.search(line)
  89. if (result != None):
  90. fields = line.split(":") # eg, ['Job-UUID','c9eee07e-508-..']
  91. endfields = fields[1:]
  92. # ignore job uuid given on this line, take the one sent
  93. # in Reply-Text response line
  94. # self.response_content = "".join(endfields)
  95. self._fsm.JobUuid()
  96. return self.isRequestFinished()
  97. matchstr = re.compile("api/response", re.I)
  98. result = matchstr.search(line)
  99. if (result != None):
  100. self._fsm.ApiResponse()
  101. return self.isRequestFinished()
  102. matchstr = re.compile("Content-Length", re.I)
  103. result = matchstr.search(line)
  104. if (result != None):
  105. # line: Content-Length: 34
  106. self.content_length = int(line.split(":")[1].strip())
  107. self._fsm.ContentLength()
  108. return self.isRequestFinished()
  109. self._fsm.ProcessLine(line)
  110. return self.isRequestFinished()
  111. def callOrErrback(self):
  112. matchstr = re.compile("OK", re.I)
  113. result = matchstr.search(self.response_content)
  114. if (result != None):
  115. self.callbackDeferred(self.response_content)
  116. return
  117. self.errbackDeferred(Failure(Exception(self.response_content)))
  118. def doNothing(self):
  119. # weird smc issue workaround attempt
  120. pass
  121. class LoginRequest(FreepyRequest):
  122. """
  123. Example success response
  124. ========================
  125. lineReceived: Content-Type: auth/request
  126. lineReceived:
  127. lineReceived: Content-Type: command/reply
  128. lineReceived: Reply-Text: +OK accepted
  129. lineReceived:
  130. Example failure response
  131. ========================
  132. lineReceived: Content-Type: auth/request
  133. lineReceived:
  134. lineReceived: Content-Type: command/reply
  135. lineReceived: Reply-Text: -ERR invalid
  136. lineReceived:
  137. """
  138. def __init__(self):
  139. super(LoginRequest, self).__init__()
  140. import loginrequest_sm
  141. self._fsm = loginrequest_sm.LoginRequest_sm(self)
  142. def callOrErrback(self):
  143. matchstr = re.compile("OK", re.I)
  144. result = matchstr.search(self.response_content)
  145. if (result != None):
  146. self.callbackDeferred(self.response_content)
  147. return
  148. msg = "Login failed, most likely a bad password"
  149. self.errbackDeferred(Failure(Exception(msg)))
  150. def getReplyText(self):
  151. self.response_content
  152. class BgApiRequest(FreepyRequest):
  153. """
  154. Here is one of the 'bgapi requests' this class
  155. supports:
  156. linereceived: Content-Type: command/reply
  157. linereceived: Reply-Text: +OK Job-UUID: 788da080-24e0-11dc-85f6-3d7b12..
  158. linereceived:
  159. """
  160. def __init__(self):
  161. super(BgApiRequest, self).__init__()
  162. import bgapirequest_sm
  163. self._fsm = bgapirequest_sm.BgApiRequest_sm(self)
  164. def getResponse(self):
  165. # subclasses may want to parse this into a meaningful
  166. # object or set of objects (eg, see ListConfRequest)
  167. # By default, just return accumulated string
  168. return self.response_content
  169. class ApiRequest(FreepyRequest):
  170. """
  171. Here is one of the 'api requests' this class
  172. supports:
  173. lineReceived: Content-Type: api/response
  174. lineReceived: Content-Length: 34
  175. lineReceived:
  176. lineReceived: Call Requested: result: [SUCCESS]
  177. """
  178. def __init__(self):
  179. super(ApiRequest, self).__init__()
  180. import apirequest_sm
  181. self._fsm = apirequest_sm.ApiRequest_sm(self)
  182. self.response_content = ""
  183. def doNothing(self):
  184. # weird smc issue workaround attempt
  185. pass
  186. def add_content(self, line):
  187. """
  188. Add content to local buffer
  189. return - True if finished adding content, False otherwise
  190. """
  191. # since the twisted LineReceiver strips off the newline,
  192. # we need to add it back .. otherwise the Content-length
  193. # will be off by one
  194. line += "\n"
  195. self.response_content += line
  196. if len(self.response_content) == self.content_length:
  197. return True
  198. elif len(self.response_content) > self.content_length:
  199. return True
  200. else:
  201. return False
  202. def getResponse(self):
  203. # subclasses may want to parse this into a meaningful
  204. # object or set of objects (eg, see ListConfRequest)
  205. # By default, just return accumulated string
  206. return self.response_content
  207. class DialoutRequest(ApiRequest):
  208. """
  209. Example raw dialout response
  210. ============================
  211. lineReceived: Content-Type: api/response
  212. lineReceived: Content-Length: 34
  213. lineReceived:
  214. lineReceived: Call Requested: result: [SUCCESS]
  215. """
  216. def __init__(self):
  217. super(DialoutRequest, self).__init__()
  218. class BgDialoutRequest(BgApiRequest):
  219. def __init__(self):
  220. super(BgDialoutRequest, self).__init__()
  221. class ConfKickRequest(ApiRequest):
  222. """
  223. Example response
  224. ================
  225. """
  226. def __init__(self):
  227. super(ConfKickRequest, self).__init__()
  228. class BgConfKickRequest(BgApiRequest):
  229. """
  230. Example response
  231. ================
  232. """
  233. def __init__(self):
  234. super(BgConfKickRequest, self).__init__()
  235. class ListConfRequest(ApiRequest):
  236. """
  237. Response to request to list conferences:
  238. ========================================
  239. lineReceived: Content-Type: api/response
  240. lineReceived: Content-Length: 233
  241. lineReceived:
  242. lineReceived: 2;sofia/mydomain.com/foo@bar.com;e9be6e72-2410-11dc-8daf-7bcec6dda2ae;FreeSWITCH;0000000000;hear|speak;0;0;300
  243. lineReceived: 1;sofia/mydomain.com/foo2@bar.com;e9be5fcc-2410-11dc-8daf-7bcec6dda2ae;FreeSWITCH;0000000000;hear|speak;0;0;300
  244. """
  245. def __init__(self):
  246. super(ListConfRequest, self).__init__()
  247. self.conf_members = []
  248. def add_content(self, line):
  249. """
  250. conf not empty example
  251. ======================
  252. 1;sofia/mydomain.com/888@conference.freeswitch.org;898e6552-24ab-11dc-9df7-9fccd4095451;FreeSWITCH;0000000000;hear|speak;0;0;300
  253. conf empty example
  254. ==================
  255. Conference foo not found
  256. """
  257. matchstr = re.compile("not found", re.I)
  258. result = matchstr.search(line)
  259. if (result != None):
  260. # no conf found..
  261. pass
  262. else:
  263. confmember = models.ConfMember(line)
  264. self.conf_members.append(confmember)
  265. return super(ListConfRequest, self).add_content(line)
  266. def getResponse(self):
  267. # TODO: parse this content into a meaningful
  268. # 'object' .. though, not sure this is really
  269. # necessary. wait till there's a need
  270. return self.conf_members