PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/python/osrf/net.py

https://gitlab.com/evergreen-bjwebb/opensrf-debian
Python | 258 lines | 221 code | 15 blank | 22 comment | 4 complexity | 60bb6dc480362f4537a7580d7a70ef3e MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. # -----------------------------------------------------------------------
  2. # Copyright (C) 2007 Georgia Public Library Service
  3. # Bill Erickson <billserickson@gmail.com>
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. # -----------------------------------------------------------------------
  15. import os, time, threading
  16. from pyxmpp.jabber.client import JabberClient
  17. from pyxmpp.message import Message
  18. from pyxmpp.jid import JID
  19. from socket import gethostname
  20. import libxml2
  21. import osrf.log, osrf.ex
  22. THREAD_SESSIONS = {}
  23. # - log jabber activity (for future reference)
  24. #import logging
  25. #logger=logging.getLogger()
  26. #logger.addHandler(logging.StreamHandler())
  27. #logger.addHandler(logging.FileHandler('j.log'))
  28. #logger.setLevel(logging.DEBUG)
  29. class XMPPNoRecipient(osrf.ex.OSRFException):
  30. ''' Raised when a message was sent to a non-existent recipient
  31. The recipient is stored in the 'recipient' field on this object
  32. '''
  33. def __init__(self, recipient):
  34. osrf.ex.OSRFException.__init__(self, 'Error communicating with %s' % recipient)
  35. self.recipient = recipient
  36. class XMPPNoConnection(osrf.ex.OSRFException):
  37. pass
  38. def set_network_handle(handle):
  39. """ Sets the thread-specific network handle"""
  40. THREAD_SESSIONS[threading.currentThread().getName()] = handle
  41. def get_network_handle():
  42. """ Returns the thread-specific network connection handle."""
  43. return THREAD_SESSIONS.get(threading.currentThread().getName())
  44. def clear_network_handle():
  45. ''' Disconnects the thread-specific handle and discards it '''
  46. handle = THREAD_SESSIONS.get(threading.currentThread().getName())
  47. if handle:
  48. osrf.log.log_internal("clearing network handle %s" % handle.jid.as_utf8())
  49. del THREAD_SESSIONS[threading.currentThread().getName()]
  50. return handle
  51. class NetworkMessage(object):
  52. """Network message
  53. attributes:
  54. sender - message sender
  55. recipient - message recipient
  56. body - the body of the message
  57. thread - the message thread
  58. locale - locale of the message
  59. osrf_xid - The logging transaction ID
  60. """
  61. def __init__(self, message=None, **args):
  62. if message:
  63. self.body = message.get_body()
  64. self.thread = message.get_thread()
  65. self.recipient = message.get_to()
  66. self.router_command = None
  67. self.router_class = None
  68. if message.xmlnode.hasProp('router_from') and \
  69. message.xmlnode.prop('router_from') != '':
  70. self.sender = message.xmlnode.prop('router_from')
  71. else:
  72. self.sender = message.get_from().as_utf8()
  73. if message.xmlnode.hasProp('osrf_xid'):
  74. self.xid = message.xmlnode.prop('osrf_xid')
  75. else:
  76. self.xid = ''
  77. else:
  78. self.sender = args.get('sender')
  79. self.recipient = args.get('recipient')
  80. self.body = args.get('body')
  81. self.thread = args.get('thread')
  82. self.router_command = args.get('router_command')
  83. self.router_class = args.get('router_class')
  84. self.xid = osrf.log.get_xid()
  85. @staticmethod
  86. def from_xml(xml):
  87. doc = libxml2.parseDoc(xml)
  88. msg = Message(doc.getRootElement())
  89. return NetworkMessage(msg)
  90. def make_xmpp_msg(self):
  91. ''' Creates a pyxmpp.message.Message and adds custom attributes '''
  92. msg = Message(None, self.sender, self.recipient, None, None, None, \
  93. self.body, self.thread)
  94. if self.router_command:
  95. msg.xmlnode.newProp('router_command', self.router_command)
  96. if self.router_class:
  97. msg.xmlnode.newProp('router_class', self.router_class)
  98. if self.xid:
  99. msg.xmlnode.newProp('osrf_xid', self.xid)
  100. return msg
  101. def to_xml(self):
  102. ''' Turns this message into XML '''
  103. return self.make_xmpp_msg().serialize()
  104. class Network(JabberClient):
  105. def __init__(self, **args):
  106. self.isconnected = False
  107. # Create a unique jabber resource
  108. resource = args.get('resource') or 'python_client'
  109. resource += '_' + gethostname() + ':' + str(os.getpid()) + '_' + \
  110. threading.currentThread().getName().lower()
  111. self.jid = JID(args['username'], args['host'], resource)
  112. osrf.log.log_debug("initializing network with JID %s and host=%s, "
  113. "port=%s, username=%s" % (self.jid.as_utf8(), args['host'], \
  114. args['port'], args['username']))
  115. #initialize the superclass
  116. JabberClient.__init__(self, self.jid, args['password'], args['host'])
  117. self.queue = []
  118. self.receive_callback = None
  119. self.transport_error_msg = None
  120. def connect(self):
  121. JabberClient.connect(self)
  122. while not self.isconnected:
  123. stream = self.get_stream()
  124. act = stream.loop_iter(10)
  125. if not act:
  126. self.idle()
  127. def set_receive_callback(self, func):
  128. """The callback provided is called when a message is received.
  129. The only argument to the function is the received message. """
  130. self.receive_callback = func
  131. def session_started(self):
  132. osrf.log.log_info("Successfully connected to the opensrf network")
  133. self.authenticated()
  134. self.stream.set_message_handler("normal", self.message_received)
  135. self.stream.set_message_handler("error", self.error_received)
  136. self.isconnected = True
  137. def send(self, message):
  138. """Sends the provided network message."""
  139. osrf.log.log_internal("jabber sending to %s: %s" % (message.recipient, message.body))
  140. message.sender = self.jid.as_utf8()
  141. msg = message.make_xmpp_msg()
  142. self.stream.send(msg)
  143. def error_received(self, stanza):
  144. self.transport_error_msg = NetworkMessage(stanza)
  145. osrf.log.log_error("XMPP error message received from %s" % self.transport_error_msg.sender)
  146. def message_received(self, stanza):
  147. """Handler for received messages."""
  148. if stanza.get_type()=="headline":
  149. return True
  150. # check for errors
  151. osrf.log.log_internal("jabber received message from %s : %s"
  152. % (stanza.get_from().as_utf8(), stanza.get_body()))
  153. self.queue.append(NetworkMessage(stanza))
  154. return True
  155. def stream_closed(self, stream):
  156. osrf.log.log_debug("XMPP Stream closing...")
  157. def stream_error(self, err):
  158. osrf.log.log_error("XMPP Stream error: condition: %s %r"
  159. % (err.get_condition().name,err.serialize()))
  160. def disconnected(self):
  161. osrf.log.log_internal('XMPP Disconnected')
  162. def recv(self, timeout=120):
  163. """Attempts to receive a message from the network.
  164. timeout - max number of seconds to wait for a message.
  165. If a message is received in 'timeout' seconds, the message is passed to
  166. the receive_callback is called and True is returned. Otherwise, false is
  167. returned.
  168. """
  169. forever = False
  170. if timeout < 0:
  171. forever = True
  172. timeout = None
  173. if len(self.queue) == 0:
  174. while (forever or timeout >= 0) and len(self.queue) == 0:
  175. starttime = time.time()
  176. stream = self.get_stream()
  177. if not stream:
  178. raise XMPPNoConnection('We lost our server connection...')
  179. act = stream.loop_iter(timeout)
  180. endtime = time.time() - starttime
  181. if not forever:
  182. timeout -= endtime
  183. osrf.log.log_internal("exiting stream loop after %s seconds. "
  184. "act=%s, queue size=%d" % (str(endtime), act, len(self.queue)))
  185. if self.transport_error_msg:
  186. msg = self.transport_error_msg
  187. self.transport_error_msg = None
  188. raise XMPPNoRecipient(msg.sender)
  189. if not act:
  190. self.idle()
  191. # if we've acquired a message, handle it
  192. msg = None
  193. if len(self.queue) > 0:
  194. msg = self.queue.pop(0)
  195. if self.receive_callback:
  196. self.receive_callback(msg)
  197. return msg
  198. def flush_inbound_data(self):
  199. ''' Read all pending inbound messages from the socket and discard them '''
  200. cb = self.receive_callback
  201. self.receive_callback = None
  202. while self.recv(0): pass
  203. self.receive_callback = cb