PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/mopidy/utils/network.py

https://github.com/bfosberry/mopidy
Python | 395 lines | 381 code | 8 blank | 6 comment | 7 complexity | 448d23eaf8c124d195758acbc8ad1bbf MD5 | raw file
Possible License(s): Apache-2.0
  1. import errno
  2. import gobject
  3. import logging
  4. import re
  5. import socket
  6. import threading
  7. from pykka import ActorDeadError
  8. from pykka.actor import ThreadingActor
  9. from pykka.registry import ActorRegistry
  10. logger = logging.getLogger('mopidy.utils.server')
  11. class ShouldRetrySocketCall(Exception):
  12. """Indicate that attempted socket call should be retried"""
  13. def try_ipv6_socket():
  14. """Determine if system really supports IPv6"""
  15. if not socket.has_ipv6:
  16. return False
  17. try:
  18. socket.socket(socket.AF_INET6).close()
  19. return True
  20. except IOError, e:
  21. logger.debug(u'Platform supports IPv6, but socket '
  22. 'creation failed, disabling: %s', e)
  23. return False
  24. #: Boolean value that indicates if creating an IPv6 socket will succeed.
  25. has_ipv6 = try_ipv6_socket()
  26. def create_socket():
  27. """Create a TCP socket with or without IPv6 depending on system support"""
  28. if has_ipv6:
  29. sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  30. # Explicitly configure socket to work for both IPv4 and IPv6
  31. sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
  32. else:
  33. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  34. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  35. return sock
  36. def format_hostname(hostname):
  37. """Format hostname for display."""
  38. if (has_ipv6 and re.match('\d+.\d+.\d+.\d+', hostname) is not None):
  39. hostname = '::ffff:%s' % hostname
  40. return hostname
  41. class Server(object):
  42. """Setup listener and register it with gobject's event loop."""
  43. def __init__(self, host, port, protocol, max_connections=5, timeout=30):
  44. self.protocol = protocol
  45. self.max_connections = max_connections
  46. self.timeout = timeout
  47. self.server_socket = self.create_server_socket(host, port)
  48. self.register_server_socket(self.server_socket.fileno())
  49. def create_server_socket(self, host, port):
  50. sock = create_socket()
  51. sock.setblocking(False)
  52. sock.bind((host, port))
  53. sock.listen(1)
  54. return sock
  55. def register_server_socket(self, fileno):
  56. gobject.io_add_watch(fileno, gobject.IO_IN, self.handle_connection)
  57. def handle_connection(self, fd, flags):
  58. try:
  59. sock, addr = self.accept_connection()
  60. except ShouldRetrySocketCall:
  61. return True
  62. if self.maximum_connections_exceeded():
  63. self.reject_connection(sock, addr)
  64. else:
  65. self.init_connection(sock, addr)
  66. return True
  67. def accept_connection(self):
  68. try:
  69. return self.server_socket.accept()
  70. except socket.error as e:
  71. if e.errno in (errno.EAGAIN, errno.EINTR):
  72. raise ShouldRetrySocketCall
  73. raise
  74. def maximum_connections_exceeded(self):
  75. return (self.max_connections is not None and
  76. self.number_of_connections() >= self.max_connections)
  77. def number_of_connections(self):
  78. return len(ActorRegistry.get_by_class(self.protocol))
  79. def reject_connection(self, sock, addr):
  80. # FIXME provide more context in logging?
  81. logger.warning(u'Rejected connection from [%s]:%s', addr[0], addr[1])
  82. try:
  83. sock.close()
  84. except socket.error:
  85. pass
  86. def init_connection(self, sock, addr):
  87. Connection(self.protocol, sock, addr, self.timeout)
  88. class Connection(object):
  89. # NOTE: the callback code is _not_ run in the actor's thread, but in the
  90. # same one as the event loop. If code in the callbacks blocks, the rest of
  91. # gobject code will likely be blocked as well...
  92. #
  93. # Also note that source_remove() return values are ignored on purpose, a
  94. # false return value would only tell us that what we thought was registered
  95. # is already gone, there is really nothing more we can do.
  96. def __init__(self, protocol, sock, addr, timeout):
  97. sock.setblocking(False)
  98. self.host, self.port = addr[:2] # IPv6 has larger addr
  99. self.sock = sock
  100. self.protocol = protocol
  101. self.timeout = timeout
  102. self.send_lock = threading.Lock()
  103. self.send_buffer = ''
  104. self.stopping = False
  105. self.recv_id = None
  106. self.send_id = None
  107. self.timeout_id = None
  108. self.actor_ref = self.protocol.start(self)
  109. self.enable_recv()
  110. self.enable_timeout()
  111. def stop(self, reason, level=logging.DEBUG):
  112. if self.stopping:
  113. logger.log(level, 'Already stopping: %s' % reason)
  114. return
  115. else:
  116. self.stopping = True
  117. logger.log(level, reason)
  118. try:
  119. self.actor_ref.stop()
  120. except ActorDeadError:
  121. pass
  122. self.disable_timeout()
  123. self.disable_recv()
  124. self.disable_send()
  125. try:
  126. self.sock.close()
  127. except socket.error:
  128. pass
  129. def queue_send(self, data):
  130. """Try to send data to client exactly as is and queue rest."""
  131. self.send_lock.acquire(True)
  132. self.send_buffer = self.send(self.send_buffer + data)
  133. self.send_lock.release()
  134. if self.send_buffer:
  135. self.enable_send()
  136. def send(self, data):
  137. """Send data to client, return any unsent data."""
  138. try:
  139. sent = self.sock.send(data)
  140. return data[sent:]
  141. except socket.error as e:
  142. if e.errno in (errno.EWOULDBLOCK, errno.EINTR):
  143. return data
  144. self.stop(u'Unexpected client error: %s' % e)
  145. return ''
  146. def enable_timeout(self):
  147. """Reactivate timeout mechanism."""
  148. if self.timeout <= 0:
  149. return
  150. self.disable_timeout()
  151. self.timeout_id = gobject.timeout_add_seconds(
  152. self.timeout, self.timeout_callback)
  153. def disable_timeout(self):
  154. """Deactivate timeout mechanism."""
  155. if self.timeout_id is None:
  156. return
  157. gobject.source_remove(self.timeout_id)
  158. self.timeout_id = None
  159. def enable_recv(self):
  160. if self.recv_id is not None:
  161. return
  162. try:
  163. self.recv_id = gobject.io_add_watch(self.sock.fileno(),
  164. gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
  165. self.recv_callback)
  166. except socket.error as e:
  167. self.stop(u'Problem with connection: %s' % e)
  168. def disable_recv(self):
  169. if self.recv_id is None:
  170. return
  171. gobject.source_remove(self.recv_id)
  172. self.recv_id = None
  173. def enable_send(self):
  174. if self.send_id is not None:
  175. return
  176. try:
  177. self.send_id = gobject.io_add_watch(self.sock.fileno(),
  178. gobject.IO_OUT | gobject.IO_ERR | gobject.IO_HUP,
  179. self.send_callback)
  180. except socket.error as e:
  181. self.stop(u'Problem with connection: %s' % e)
  182. def disable_send(self):
  183. if self.send_id is None:
  184. return
  185. gobject.source_remove(self.send_id)
  186. self.send_id = None
  187. def recv_callback(self, fd, flags):
  188. if flags & (gobject.IO_ERR | gobject.IO_HUP):
  189. self.stop(u'Bad client flags: %s' % flags)
  190. return True
  191. try:
  192. data = self.sock.recv(4096)
  193. except socket.error as e:
  194. if e.errno not in (errno.EWOULDBLOCK, errno.EINTR):
  195. self.stop(u'Unexpected client error: %s' % e)
  196. return True
  197. if not data:
  198. self.stop(u'Client most likely disconnected.')
  199. return True
  200. try:
  201. self.actor_ref.send_one_way({'received': data})
  202. except ActorDeadError:
  203. self.stop(u'Actor is dead.')
  204. return True
  205. def send_callback(self, fd, flags):
  206. if flags & (gobject.IO_ERR | gobject.IO_HUP):
  207. self.stop(u'Bad client flags: %s' % flags)
  208. return True
  209. # If with can't get the lock, simply try again next time socket is
  210. # ready for sending.
  211. if not self.send_lock.acquire(False):
  212. return True
  213. try:
  214. self.send_buffer = self.send(self.send_buffer)
  215. if not self.send_buffer:
  216. self.disable_send()
  217. finally:
  218. self.send_lock.release()
  219. return True
  220. def timeout_callback(self):
  221. self.stop(u'Client timeout out after %s seconds' % self.timeout)
  222. return False
  223. class LineProtocol(ThreadingActor):
  224. """
  225. Base class for handling line based protocols.
  226. Takes care of receiving new data from server's client code, decoding and
  227. then splitting data along line boundaries.
  228. """
  229. #: Line terminator to use for outputed lines.
  230. terminator = '\n'
  231. #: Regex to use for spliting lines, will be set compiled version of its
  232. #: own value, or to ``terminator``s value if it is not set itself.
  233. delimeter = None
  234. #: What encoding to expect incomming data to be in, can be :class:`None`.
  235. encoding = 'utf-8'
  236. def __init__(self, connection):
  237. super(LineProtocol, self).__init__()
  238. self.connection = connection
  239. self.prevent_timeout = False
  240. self.recv_buffer = ''
  241. if self.delimeter:
  242. self.delimeter = re.compile(self.delimeter)
  243. else:
  244. self.delimeter = re.compile(self.terminator)
  245. @property
  246. def host(self):
  247. return self.connection.host
  248. @property
  249. def port(self):
  250. return self.connection.port
  251. def on_line_received(self, line):
  252. """
  253. Called whenever a new line is found.
  254. Should be implemented by subclasses.
  255. """
  256. raise NotImplementedError
  257. def on_receive(self, message):
  258. """Handle messages with new data from server."""
  259. if 'received' not in message:
  260. return
  261. self.connection.disable_timeout()
  262. self.recv_buffer += message['received']
  263. for line in self.parse_lines():
  264. line = self.decode(line)
  265. if line is not None:
  266. self.on_line_received(line)
  267. if not self.prevent_timeout:
  268. self.connection.enable_timeout()
  269. def on_stop(self):
  270. """Ensure that cleanup when actor stops."""
  271. self.connection.stop(u'Actor is shutting down.')
  272. def parse_lines(self):
  273. """Consume new data and yield any lines found."""
  274. while re.search(self.terminator, self.recv_buffer):
  275. line, self.recv_buffer = self.delimeter.split(
  276. self.recv_buffer, 1)
  277. yield line
  278. def encode(self, line):
  279. """
  280. Handle encoding of line.
  281. Can be overridden by subclasses to change encoding behaviour.
  282. """
  283. try:
  284. return line.encode(self.encoding)
  285. except UnicodeError:
  286. logger.warning(u'Stopping actor due to encode problem, data '
  287. 'supplied by client was not valid %s', self.encoding)
  288. self.stop()
  289. def decode(self, line):
  290. """
  291. Handle decoding of line.
  292. Can be overridden by subclasses to change decoding behaviour.
  293. """
  294. try:
  295. return line.decode(self.encoding)
  296. except UnicodeError:
  297. logger.warning(u'Stopping actor due to decode problem, data '
  298. 'supplied by client was not valid %s', self.encoding)
  299. self.stop()
  300. def join_lines(self, lines):
  301. if not lines:
  302. return u''
  303. return self.terminator.join(lines) + self.terminator
  304. def send_lines(self, lines):
  305. """
  306. Send array of lines to client via connection.
  307. Join lines using the terminator that is set for this class, encode it
  308. and send it to the client.
  309. """
  310. if not lines:
  311. return
  312. data = self.join_lines(lines)
  313. self.connection.queue_send(self.encode(data))