Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

amqplib.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. """
  2. kombu.transport.amqplib
  3. =======================
  4. amqplib transport.
  5. """
  6. from __future__ import absolute_import
  7. import errno
  8. import socket
  9. from kombu.five import items
  10. from kombu.utils.encoding import str_to_bytes
  11. from kombu.utils.amq_manager import get_manager
  12. from . import base
  13. try:
  14. from ssl import SSLError
  15. except ImportError:
  16. class SSLError(Exception): # noqa
  17. pass
  18. from struct import unpack
  19. class NA(object):
  20. pass
  21. try:
  22. from amqplib import client_0_8 as amqp
  23. from amqplib.client_0_8 import transport
  24. from amqplib.client_0_8.channel import Channel as _Channel
  25. from amqplib.client_0_8.exceptions import AMQPConnectionException
  26. from amqplib.client_0_8.exceptions import AMQPChannelException
  27. except ImportError: # pragma: no cover
  28. class NAx(object):
  29. pass
  30. amqp = NA
  31. amqp.Connection = NA
  32. transport = _Channel = NA # noqa
  33. # Sphinx crashes if this is NA, must be different class
  34. transport.TCPTransport = transport.SSLTransport = NAx
  35. AMQPConnectionException = AMQPChannelException = NA # noqa
  36. DEFAULT_PORT = 5672
  37. HAS_MSG_PEEK = hasattr(socket, 'MSG_PEEK')
  38. # amqplib's handshake mistakenly identifies as protocol version 1191,
  39. # this breaks in RabbitMQ tip, which no longer falls back to
  40. # 0-8 for unknown ids.
  41. transport.AMQP_PROTOCOL_HEADER = str_to_bytes('AMQP\x01\x01\x08\x00')
  42. # - fixes warnings when socket is not connected.
  43. class TCPTransport(transport.TCPTransport):
  44. def read_frame(self):
  45. frame_type, channel, size = unpack('>BHI', self._read(7, True))
  46. payload = self._read(size)
  47. ch = ord(self._read(1))
  48. if ch == 206: # '\xce'
  49. return frame_type, channel, payload
  50. else:
  51. raise Exception(
  52. 'Framing Error, received 0x%02x while expecting 0xce' % ch)
  53. def _read(self, n, initial=False):
  54. read_buffer = self._read_buffer
  55. while len(read_buffer) < n:
  56. try:
  57. s = self.sock.recv(n - len(read_buffer))
  58. except socket.error as exc:
  59. if not initial and exc.errno in (errno.EAGAIN, errno.EINTR):
  60. continue
  61. raise
  62. if not s:
  63. raise IOError('Socket closed')
  64. read_buffer += s
  65. result = read_buffer[:n]
  66. self._read_buffer = read_buffer[n:]
  67. return result
  68. def __del__(self):
  69. try:
  70. self.close()
  71. except Exception:
  72. pass
  73. finally:
  74. self.sock = None
  75. transport.TCPTransport = TCPTransport
  76. class SSLTransport(transport.SSLTransport):
  77. def __init__(self, host, connect_timeout, ssl):
  78. if isinstance(ssl, dict):
  79. self.sslopts = ssl
  80. self.sslobj = None
  81. transport._AbstractTransport.__init__(self, host, connect_timeout)
  82. def read_frame(self):
  83. frame_type, channel, size = unpack('>BHI', self._read(7, True))
  84. payload = self._read(size)
  85. ch = ord(self._read(1))
  86. if ch == 206: # '\xce'
  87. return frame_type, channel, payload
  88. else:
  89. raise Exception(
  90. 'Framing Error, received 0x%02x while expecting 0xce' % ch)
  91. def _read(self, n, initial=False):
  92. result = ''
  93. while len(result) < n:
  94. try:
  95. s = self.sslobj.read(n - len(result))
  96. except socket.error as exc:
  97. if not initial and exc.errno in (errno.EAGAIN, errno.EINTR):
  98. continue
  99. raise
  100. if not s:
  101. raise IOError('Socket closed')
  102. result += s
  103. return result
  104. def __del__(self):
  105. try:
  106. self.close()
  107. except Exception:
  108. pass
  109. finally:
  110. self.sock = None
  111. transport.SSLTransport = SSLTransport
  112. class Connection(amqp.Connection): # pragma: no cover
  113. connected = True
  114. def _do_close(self, *args, **kwargs):
  115. # amqplib does not ignore socket errors when connection
  116. # is closed on the remote end.
  117. try:
  118. super(Connection, self)._do_close(*args, **kwargs)
  119. except socket.error:
  120. pass
  121. def _dispatch_basic_return(self, channel, args, msg):
  122. reply_code = args.read_short()
  123. reply_text = args.read_shortstr()
  124. exchange = args.read_shortstr()
  125. routing_key = args.read_shortstr()
  126. exc = AMQPChannelException(reply_code, reply_text, (50, 60))
  127. if channel.events['basic_return']:
  128. for callback in channel.events['basic_return']:
  129. callback(exc, exchange, routing_key, msg)
  130. else:
  131. raise exc
  132. def __init__(self, *args, **kwargs):
  133. super(Connection, self).__init__(*args, **kwargs)
  134. self._method_override = {(60, 50): self._dispatch_basic_return}
  135. def drain_events(self, timeout=None):
  136. """Wait for an event on a channel."""
  137. chanmap = self.channels
  138. chanid, method_sig, args, content = self._wait_multiple(
  139. chanmap, None, timeout=timeout)
  140. channel = chanmap[chanid]
  141. if (content and
  142. channel.auto_decode and
  143. hasattr(content, 'content_encoding')):
  144. try:
  145. content.body = content.body.decode(content.content_encoding)
  146. except Exception:
  147. pass
  148. amqp_method = self._method_override.get(method_sig) or \
  149. channel._METHOD_MAP.get(method_sig, None)
  150. if amqp_method is None:
  151. raise Exception('Unknown AMQP method (%d, %d)' % method_sig)
  152. if content is None:
  153. return amqp_method(channel, args)
  154. else:
  155. return amqp_method(channel, args, content)
  156. def read_timeout(self, timeout=None):
  157. if timeout is None:
  158. return self.method_reader.read_method()
  159. sock = self.transport.sock
  160. prev = sock.gettimeout()
  161. if prev != timeout:
  162. sock.settimeout(timeout)
  163. try:
  164. try:
  165. return self.method_reader.read_method()
  166. except SSLError as exc:
  167. # http://bugs.python.org/issue10272
  168. if 'timed out' in str(exc):
  169. raise socket.timeout()
  170. # Non-blocking SSL sockets can throw SSLError
  171. if 'The operation did not complete' in str(exc):
  172. raise socket.timeout()
  173. raise
  174. finally:
  175. if prev != timeout:
  176. sock.settimeout(prev)
  177. def _wait_multiple(self, channels, allowed_methods, timeout=None):
  178. for channel_id, channel in items(channels):
  179. method_queue = channel.method_queue
  180. for queued_method in method_queue:
  181. method_sig = queued_method[0]
  182. if (allowed_methods is None or
  183. method_sig in allowed_methods or
  184. method_sig == (20, 40)):
  185. method_queue.remove(queued_method)
  186. method_sig, args, content = queued_method
  187. return channel_id, method_sig, args, content
  188. # Nothing queued, need to wait for a method from the peer
  189. read_timeout = self.read_timeout
  190. wait = self.wait
  191. while 1:
  192. channel, method_sig, args, content = read_timeout(timeout)
  193. if (channel in channels and
  194. allowed_methods is None or
  195. method_sig in allowed_methods or
  196. method_sig == (20, 40)):
  197. return channel, method_sig, args, content
  198. # Not the channel and/or method we were looking for. Queue
  199. # this method for later
  200. channels[channel].method_queue.append((method_sig, args, content))
  201. #
  202. # If we just queued up a method for channel 0 (the Connection
  203. # itself) it's probably a close method in reaction to some
  204. # error, so deal with it right away.
  205. #
  206. if channel == 0:
  207. wait()
  208. def channel(self, channel_id=None):
  209. try:
  210. return self.channels[channel_id]
  211. except KeyError:
  212. return Channel(self, channel_id)
  213. class Message(base.Message):
  214. def __init__(self, channel, msg, **kwargs):
  215. props = msg.properties
  216. super(Message, self).__init__(
  217. channel,
  218. body=msg.body,
  219. delivery_tag=msg.delivery_tag,
  220. content_type=props.get('content_type'),
  221. content_encoding=props.get('content_encoding'),
  222. delivery_info=msg.delivery_info,
  223. properties=msg.properties,
  224. headers=props.get('application_headers') or {},
  225. **kwargs)
  226. class Channel(_Channel, base.StdChannel):
  227. Message = Message
  228. events = {'basic_return': set()}
  229. def __init__(self, *args, **kwargs):
  230. self.no_ack_consumers = set()
  231. super(Channel, self).__init__(*args, **kwargs)
  232. def prepare_message(self, body, priority=None, content_type=None,
  233. content_encoding=None, headers=None, properties=None):
  234. """Encapsulate data into a AMQP message."""
  235. return amqp.Message(body, priority=priority,
  236. content_type=content_type,
  237. content_encoding=content_encoding,
  238. application_headers=headers,
  239. **properties)
  240. def message_to_python(self, raw_message):
  241. """Convert encoded message body back to a Python value."""
  242. return self.Message(self, raw_message)
  243. def close(self):
  244. try:
  245. super(Channel, self).close()
  246. finally:
  247. self.connection = None
  248. def basic_consume(self, *args, **kwargs):
  249. consumer_tag = super(Channel, self).basic_consume(*args, **kwargs)
  250. if kwargs['no_ack']:
  251. self.no_ack_consumers.add(consumer_tag)
  252. return consumer_tag
  253. def basic_cancel(self, consumer_tag, **kwargs):
  254. self.no_ack_consumers.discard(consumer_tag)
  255. return super(Channel, self).basic_cancel(consumer_tag, **kwargs)
  256. class Transport(base.Transport):
  257. Connection = Connection
  258. default_port = DEFAULT_PORT
  259. # it's very annoying that amqplib sometimes raises AttributeError
  260. # if the connection is lost, but nothing we can do about that here.
  261. connection_errors = (
  262. base.Transport.connection_errors + (
  263. AMQPConnectionException,
  264. socket.error, IOError, OSError, AttributeError)
  265. )
  266. channel_errors = base.Transport.channel_errors + (AMQPChannelException, )
  267. driver_name = 'amqplib'
  268. driver_type = 'amqp'
  269. supports_ev = True
  270. def __init__(self, client, **kwargs):
  271. self.client = client
  272. self.default_port = kwargs.get('default_port') or self.default_port
  273. if amqp is NA:
  274. raise ImportError('Missing amqplib library (pip install amqplib)')
  275. def create_channel(self, connection):
  276. return connection.channel()
  277. def drain_events(self, connection, **kwargs):
  278. return connection.drain_events(**kwargs)
  279. def establish_connection(self):
  280. """Establish connection to the AMQP broker."""
  281. conninfo = self.client
  282. for name, default_value in items(self.default_connection_params):
  283. if not getattr(conninfo, name, None):
  284. setattr(conninfo, name, default_value)
  285. if conninfo.hostname == 'localhost':
  286. conninfo.hostname = '127.0.0.1'
  287. conn = self.Connection(host=conninfo.host,
  288. userid=conninfo.userid,
  289. password=conninfo.password,
  290. login_method=conninfo.login_method,
  291. virtual_host=conninfo.virtual_host,
  292. insist=conninfo.insist,
  293. ssl=conninfo.ssl,
  294. connect_timeout=conninfo.connect_timeout)
  295. conn.client = self.client
  296. return conn
  297. def close_connection(self, connection):
  298. """Close the AMQP broker connection."""
  299. connection.client = None
  300. connection.close()
  301. def is_alive(self, connection):
  302. if HAS_MSG_PEEK:
  303. sock = connection.transport.sock
  304. prev = sock.gettimeout()
  305. sock.settimeout(0.0001)
  306. try:
  307. sock.recv(1, socket.MSG_PEEK)
  308. except socket.timeout:
  309. pass
  310. except socket.error:
  311. return False
  312. finally:
  313. sock.settimeout(prev)
  314. return True
  315. def verify_connection(self, connection):
  316. return connection.channels is not None and self.is_alive(connection)
  317. def register_with_event_loop(self, connection, loop):
  318. loop.add_reader(connection.method_reader.source.sock,
  319. self.on_readable, connection, loop)
  320. @property
  321. def default_connection_params(self):
  322. return {'userid': 'guest', 'password': 'guest',
  323. 'port': self.default_port,
  324. 'hostname': 'localhost', 'login_method': 'AMQPLAIN'}
  325. def get_manager(self, *args, **kwargs):
  326. return get_manager(self.client, *args, **kwargs)