/lib/rapidsms/backends/smpp.py

http://github.com/rapidsms/rapidsms · Python · 205 lines · 184 code · 18 blank · 3 comment · 23 complexity · 890e9e602046be0a79d22844bd230500 MD5 · raw file

  1. #!/usr/bin/env python
  2. # vim: et ai sts=4 sw=4 ts=4
  3. import time
  4. import socket
  5. from ..models import Connection
  6. from .base import BackendBase
  7. from ..utils.modules import try_import
  8. smpplib = try_import('smpplib')
  9. class Backend(BackendBase):
  10. def __init__(self, *args, **kwargs):
  11. BackendBase.__init__(*args, **kwargs)
  12. if smpplib is None:
  13. raise ImportError(
  14. "The rapidsms.backends.smpp engine is not available, " +
  15. "because 'smpplib' is not installed.")
  16. def configure(self, **args):
  17. self.host = args['host']
  18. self.port = args['port']
  19. self.user = args['username']
  20. self.pwd = args['password']
  21. self.sender = args['sender']
  22. self.system_type = args['system_type'] if args.has_key('system_type') else None
  23. self.address_range = args['address_range'] if args.has_key('address_range') else None
  24. # compulsory SMPP parameters
  25. self.source_addr_ton = args['source_addr_ton'] if args.has_key('source_addr_ton') else 0
  26. self.dest_addr_ton = args['dest_addr_ton'] if args.has_key('dest_addr_ton') else 0
  27. self.smppClient = Client(self.host, self.port)
  28. self.smppClient.set_message_received_handler(self.recv_handler)
  29. self.justConnected = False
  30. def run (self):
  31. while self.running and self.smppClient.connectAndBind(
  32. system_id=self.user, password=self.pwd,
  33. address_range=self.address_range, system_type=self.system_type):
  34. if not self.smppClient.justConnected and self.smppClient.isConnected and self.smppClient.isBinded:
  35. self.info('Connected to %s:%s' % (self.host, self.port))
  36. self.smppClient.justConnected = True
  37. if self.message_waiting and self.smppClient.isConnected and self.smppClient.isBinded:
  38. msg = self.next_message()
  39. self.outgoing(msg)
  40. if not self.smppClient.listen():
  41. break
  42. self.info("Shutting down...")
  43. self.smppClient.disconnect()
  44. def outgoing (self, msg):
  45. try:
  46. senderAddress = msg.return_addr
  47. except AttributeError:
  48. senderAddress = self.sender
  49. target = msg.connection.identity
  50. self.info("sending to %s: %s", target, msg.text)
  51. self.smppClient.send_message(
  52. source_addr_ton = self.source_addr_ton,
  53. source_addr = senderAddress,
  54. dest_addr_ton = self.dest_addr_ton,
  55. destination_addr= target,
  56. short_message = msg.text)
  57. def recv_handler(self, **args):
  58. p = args['pdu']
  59. self.debug("%s >> %s : %s", p.source_addr,
  60. p.destination_addr, p.short_message)
  61. self.info("injecting message into router")
  62. con = Connection(self, p.source_addr)
  63. msg = self.message(con.identity, p.short_message)
  64. msg.return_addr = p.destination_addr
  65. self.route(msg)
  66. if smpplib is not None:
  67. class Client(smpplib.client.Client):
  68. '''I needed to override certain functionality that's the reason
  69. for this'''
  70. def __init__(self, host, port, debug=False):
  71. self.isConnected = False
  72. self.isBinded = False
  73. self.justConnected = False
  74. self.failedToConnect = False
  75. smpplib.client.Client.__init__(self, host, port)
  76. smpplib.client.DEBUG = debug
  77. def disconnect(self):
  78. try:
  79. self.unbind()
  80. except socket.error:
  81. pass
  82. except Exception:
  83. pass
  84. try:
  85. smpplib.client.Client.disconnect(self)
  86. self._socket.shutdown()
  87. except socket.error:
  88. pass
  89. except Exception:
  90. pass
  91. self.isConnected = False
  92. self.isBinded = False
  93. self.justConnected = False
  94. return True
  95. def connect(self):
  96. '''This method continues to attempt to connect to the server
  97. until it's either successful or there's an interrupt'''
  98. if not self.isConnected:
  99. self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  100. self._socket.settimeout(0.1)
  101. try:
  102. if self.failedToConnect:
  103. time.sleep(3)
  104. smpplib.client.Client.connect(self)
  105. self.isConnected = True
  106. return self.isConnected
  107. except KeyboardInterrupt:
  108. return self.isConnected
  109. except smpplib.client.ConnectionError:
  110. self.failedToConnect = True
  111. return self.isConnected
  112. # This gets called if there's an attempt to reconnect an
  113. # established connection
  114. return self.isConnected
  115. def connectAndBind(self, **args):
  116. '''Connects and binds to the server as a transceiver'''
  117. if not self.isBinded and self.connect():
  118. try:
  119. self.bind_transceiver(**(args))
  120. self.isBinded = True
  121. return self.isBinded
  122. except socket.error:
  123. self.disconnect()
  124. return self.isBinded
  125. except Exception:
  126. self.disconnect()
  127. return self.isBinded
  128. except KeyboardInterrupt:
  129. self.disconnect()
  130. return self.isBinded
  131. #return self.isBinded and self.isConnected
  132. return True
  133. def listen(self):
  134. """Listen for PDUs and act"""
  135. if not (self.isConnected and self.isBinded):
  136. return True
  137. if not self.receiver_mode:
  138. raise Exception('Client.listen() is not allowed to be ' \
  139. 'invoked manually for non receiver connection')
  140. p = None
  141. try:
  142. p = self.read_pdu()
  143. except socket.timeout:
  144. #smpplib.client.log('Socket timeout, listening again')
  145. return True
  146. except KeyboardInterrupt:
  147. return False
  148. except socket.error:
  149. # We have a socket error and we assume we've been disconnected
  150. # we don't want to implicitly shutdown the backend but we want
  151. # to attempt to reconnect
  152. self.disconnect()
  153. return True
  154. except smpplib.client.ConnectionError:
  155. self.disconnect()
  156. return True
  157. if p and p.command == 'unbind': #unbind_res
  158. smpplib.client.log('Unbind command received')
  159. # Don't know if I should return True on this one
  160. return True
  161. elif p and p.command == 'deliver_sm':
  162. self._message_received(p)
  163. elif p and p.command == 'enquire_link':
  164. self._enquire_link_received()
  165. else:
  166. if p:
  167. print "WARNING: Unhandled SMPP command '%s'" % p.command
  168. self.disconnect()
  169. #self.isConnected = False # Found out this happens
  170. return True
  171. # message was handled so return True
  172. return True