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.

connection.py 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. """AMQP Connections"""
  2. # Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
  3. #
  4. # This library is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU Lesser General Public
  6. # License as published by the Free Software Foundation; either
  7. # version 2.1 of the License, or (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # Lesser General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Lesser General Public
  15. # License along with this library; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
  17. from __future__ import absolute_import
  18. import logging
  19. import socket
  20. from array import array
  21. try:
  22. from ssl import SSLError
  23. except ImportError:
  24. class SSLError(Exception): # noqa
  25. pass
  26. from . import __version__
  27. from .abstract_channel import AbstractChannel
  28. from .channel import Channel
  29. from .exceptions import (
  30. AMQPNotImplementedError, ChannelError, ResourceError,
  31. ConnectionForced, ConnectionError, error_for_code,
  32. RecoverableConnectionError, RecoverableChannelError,
  33. )
  34. from .five import items, range, values, monotonic
  35. from .method_framing import MethodReader, MethodWriter
  36. from .serialization import AMQPWriter
  37. from .transport import create_transport
  38. HAS_MSG_PEEK = hasattr(socket, 'MSG_PEEK')
  39. START_DEBUG_FMT = """
  40. Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s
  41. """.strip()
  42. __all__ = ['Connection']
  43. #
  44. # Client property info that gets sent to the server on connection startup
  45. #
  46. LIBRARY_PROPERTIES = {
  47. 'product': 'py-amqp',
  48. 'product_version': __version__,
  49. 'capabilities': {},
  50. }
  51. AMQP_LOGGER = logging.getLogger('amqp')
  52. class Connection(AbstractChannel):
  53. """The connection class provides methods for a client to establish a
  54. network connection to a server, and for both peers to operate the
  55. connection thereafter.
  56. GRAMMAR::
  57. connection = open-connection *use-connection close-connection
  58. open-connection = C:protocol-header
  59. S:START C:START-OK
  60. *challenge
  61. S:TUNE C:TUNE-OK
  62. C:OPEN S:OPEN-OK
  63. challenge = S:SECURE C:SECURE-OK
  64. use-connection = *channel
  65. close-connection = C:CLOSE S:CLOSE-OK
  66. / S:CLOSE C:CLOSE-OK
  67. """
  68. Channel = Channel
  69. #: Final heartbeat interval value (in float seconds) after negotiation
  70. heartbeat = None
  71. #: Original heartbeat interval value proposed by client.
  72. client_heartbeat = None
  73. #: Original heartbeat interval proposed by server.
  74. server_heartbeat = None
  75. #: Time of last heartbeat sent (in monotonic time, if available).
  76. last_heartbeat_sent = 0
  77. #: Time of last heartbeat received (in monotonic time, if available).
  78. last_heartbeat_received = 0
  79. #: Number of bytes sent to socket at the last heartbeat check.
  80. prev_sent = None
  81. #: Number of bytes received from socket at the last heartbeat check.
  82. prev_recv = None
  83. def __init__(self, host='localhost', userid='guest', password='guest',
  84. login_method='AMQPLAIN', login_response=None,
  85. virtual_host='/', locale='en_US', client_properties=None,
  86. ssl=False, connect_timeout=None, channel_max=None,
  87. frame_max=None, heartbeat=0, on_blocked=None,
  88. on_unblocked=None, confirm_publish=False, **kwargs):
  89. """Create a connection to the specified host, which should be
  90. a 'host[:port]', such as 'localhost', or '1.2.3.4:5672'
  91. (defaults to 'localhost', if a port is not specified then
  92. 5672 is used)
  93. If login_response is not specified, one is built up for you from
  94. userid and password if they are present.
  95. The 'ssl' parameter may be simply True/False, or for Python >= 2.6
  96. a dictionary of options to pass to ssl.wrap_socket() such as
  97. requiring certain certificates.
  98. """
  99. channel_max = channel_max or 65535
  100. frame_max = frame_max or 131072
  101. if (login_response is None) \
  102. and (userid is not None) \
  103. and (password is not None):
  104. login_response = AMQPWriter()
  105. login_response.write_table({'LOGIN': userid, 'PASSWORD': password})
  106. # Skip the length at the beginning
  107. login_response = login_response.getvalue()[4:]
  108. d = dict(LIBRARY_PROPERTIES, **client_properties or {})
  109. self._method_override = {(60, 50): self._dispatch_basic_return}
  110. self.channels = {}
  111. # The connection object itself is treated as channel 0
  112. super(Connection, self).__init__(self, 0)
  113. self.transport = None
  114. # Properties set in the Tune method
  115. self.channel_max = channel_max
  116. self.frame_max = frame_max
  117. self.client_heartbeat = heartbeat
  118. self.confirm_publish = confirm_publish
  119. # Callbacks
  120. self.on_blocked = on_blocked
  121. self.on_unblocked = on_unblocked
  122. self._avail_channel_ids = array('H', range(self.channel_max, 0, -1))
  123. # Properties set in the Start method
  124. self.version_major = 0
  125. self.version_minor = 0
  126. self.server_properties = {}
  127. self.mechanisms = []
  128. self.locales = []
  129. # Let the transport.py module setup the actual
  130. # socket connection to the broker.
  131. #
  132. self.transport = self.Transport(host, connect_timeout, ssl)
  133. self.method_reader = MethodReader(self.transport)
  134. self.method_writer = MethodWriter(self.transport, self.frame_max)
  135. self.wait(allowed_methods=[
  136. (10, 10), # start
  137. ])
  138. self._x_start_ok(d, login_method, login_response, locale)
  139. self._wait_tune_ok = True
  140. while self._wait_tune_ok:
  141. self.wait(allowed_methods=[
  142. (10, 20), # secure
  143. (10, 30), # tune
  144. ])
  145. return self._x_open(virtual_host)
  146. def Transport(self, host, connect_timeout, ssl=False):
  147. return create_transport(host, connect_timeout, ssl)
  148. @property
  149. def connected(self):
  150. return self.transport and self.transport.connected
  151. def _do_close(self):
  152. try:
  153. self.transport.close()
  154. temp_list = [x for x in values(self.channels) if x is not self]
  155. for ch in temp_list:
  156. ch._do_close()
  157. except socket.error:
  158. pass # connection already closed on the other end
  159. finally:
  160. self.transport = self.connection = self.channels = None
  161. def _get_free_channel_id(self):
  162. try:
  163. return self._avail_channel_ids.pop()
  164. except IndexError:
  165. raise ResourceError(
  166. 'No free channel ids, current={0}, channel_max={1}'.format(
  167. len(self.channels), self.channel_max), (20, 10))
  168. def _claim_channel_id(self, channel_id):
  169. try:
  170. return self._avail_channel_ids.remove(channel_id)
  171. except ValueError:
  172. raise ConnectionError(
  173. 'Channel %r already open' % (channel_id, ))
  174. def _wait_method(self, channel_id, allowed_methods, timeout=None):
  175. """Wait for a method from the server destined for
  176. a particular channel."""
  177. #
  178. # Check the channel's deferred methods
  179. #
  180. method_queue = self.channels[channel_id].method_queue
  181. for queued_method in method_queue:
  182. method_sig = queued_method[0]
  183. if (allowed_methods is None) \
  184. or (method_sig in allowed_methods) \
  185. or (method_sig == (20, 40)):
  186. method_queue.remove(queued_method)
  187. return queued_method
  188. #
  189. # Nothing queued, need to wait for a method from the peer
  190. #
  191. read_timeout = self.read_timeout
  192. wait = self.wait
  193. while 1:
  194. channel, method_sig, args, content = read_timeout(timeout)
  195. if channel == channel_id and (
  196. allowed_methods is None or
  197. method_sig in allowed_methods or
  198. method_sig == (20, 40)):
  199. return method_sig, args, content
  200. #
  201. # Certain methods like basic_return should be dispatched
  202. # immediately rather than being queued, even if they're not
  203. # one of the 'allowed_methods' we're looking for.
  204. #
  205. if channel and method_sig in self.Channel._IMMEDIATE_METHODS:
  206. self.channels[channel].dispatch_method(
  207. method_sig, args, content,
  208. )
  209. continue
  210. #
  211. # Not the channel and/or method we were looking for. Queue
  212. # this method for later
  213. #
  214. self.channels[channel].method_queue.append(
  215. (method_sig, args, content),
  216. )
  217. #
  218. # If we just queued up a method for channel 0 (the Connection
  219. # itself) it's probably a close method in reaction to some
  220. # error, so deal with it right away.
  221. #
  222. if not channel:
  223. wait()
  224. def channel(self, channel_id=None):
  225. """Fetch a Channel object identified by the numeric channel_id, or
  226. create that object if it doesn't already exist."""
  227. try:
  228. return self.channels[channel_id]
  229. except KeyError:
  230. return self.Channel(self, channel_id)
  231. def is_alive(self):
  232. if HAS_MSG_PEEK:
  233. sock = self.sock
  234. prev = sock.gettimeout()
  235. sock.settimeout(0.0001)
  236. try:
  237. sock.recv(1, socket.MSG_PEEK)
  238. except socket.timeout:
  239. pass
  240. except socket.error:
  241. return False
  242. finally:
  243. sock.settimeout(prev)
  244. return True
  245. def drain_events(self, timeout=None):
  246. """Wait for an event on a channel."""
  247. chanmap = self.channels
  248. chanid, method_sig, args, content = self._wait_multiple(
  249. chanmap, None, timeout=timeout,
  250. )
  251. channel = chanmap[chanid]
  252. if (content and
  253. channel.auto_decode and
  254. hasattr(content, 'content_encoding')):
  255. try:
  256. content.body = content.body.decode(content.content_encoding)
  257. except Exception:
  258. pass
  259. amqp_method = (self._method_override.get(method_sig) or
  260. channel._METHOD_MAP.get(method_sig, None))
  261. if amqp_method is None:
  262. raise AMQPNotImplementedError(
  263. 'Unknown AMQP method {0!r}'.format(method_sig))
  264. if content is None:
  265. return amqp_method(channel, args)
  266. else:
  267. return amqp_method(channel, args, content)
  268. def read_timeout(self, timeout=None):
  269. if timeout is None:
  270. return self.method_reader.read_method()
  271. sock = self.sock
  272. prev = sock.gettimeout()
  273. if prev != timeout:
  274. sock.settimeout(timeout)
  275. try:
  276. try:
  277. return self.method_reader.read_method()
  278. except SSLError as exc:
  279. # http://bugs.python.org/issue10272
  280. if 'timed out' in str(exc):
  281. raise socket.timeout()
  282. # Non-blocking SSL sockets can throw SSLError
  283. if 'The operation did not complete' in str(exc):
  284. raise socket.timeout()
  285. raise
  286. finally:
  287. if prev != timeout:
  288. sock.settimeout(prev)
  289. def _wait_multiple(self, channels, allowed_methods, timeout=None):
  290. for channel_id, channel in items(channels):
  291. method_queue = channel.method_queue
  292. for queued_method in method_queue:
  293. method_sig = queued_method[0]
  294. if (allowed_methods is None or
  295. method_sig in allowed_methods or
  296. method_sig == (20, 40)):
  297. method_queue.remove(queued_method)
  298. method_sig, args, content = queued_method
  299. return channel_id, method_sig, args, content
  300. # Nothing queued, need to wait for a method from the peer
  301. read_timeout = self.read_timeout
  302. wait = self.wait
  303. while 1:
  304. channel, method_sig, args, content = read_timeout(timeout)
  305. if channel in channels and (
  306. allowed_methods is None or
  307. method_sig in allowed_methods or
  308. method_sig == (20, 40)):
  309. return channel, method_sig, args, content
  310. # Not the channel and/or method we were looking for. Queue
  311. # this method for later
  312. channels[channel].method_queue.append((method_sig, args, content))
  313. #
  314. # If we just queued up a method for channel 0 (the Connection
  315. # itself) it's probably a close method in reaction to some
  316. # error, so deal with it right away.
  317. #
  318. if channel == 0:
  319. wait()
  320. def _dispatch_basic_return(self, channel, args, msg):
  321. reply_code = args.read_short()
  322. reply_text = args.read_shortstr()
  323. exchange = args.read_shortstr()
  324. routing_key = args.read_shortstr()
  325. exc = error_for_code(reply_code, reply_text, (50, 60), ChannelError)
  326. handlers = channel.events.get('basic_return')
  327. if not handlers:
  328. raise exc
  329. for callback in handlers:
  330. callback(exc, exchange, routing_key, msg)
  331. def close(self, reply_code=0, reply_text='', method_sig=(0, 0)):
  332. """Request a connection close
  333. This method indicates that the sender wants to close the
  334. connection. This may be due to internal conditions (e.g. a
  335. forced shut-down) or due to an error handling a specific
  336. method, i.e. an exception. When a close is due to an
  337. exception, the sender provides the class and method id of the
  338. method which caused the exception.
  339. RULE:
  340. After sending this method any received method except the
  341. Close-OK method MUST be discarded.
  342. RULE:
  343. The peer sending this method MAY use a counter or timeout
  344. to detect failure of the other peer to respond correctly
  345. with the Close-OK method.
  346. RULE:
  347. When a server receives the Close method from a client it
  348. MUST delete all server-side resources associated with the
  349. client's context. A client CANNOT reconnect to a context
  350. after sending or receiving a Close method.
  351. PARAMETERS:
  352. reply_code: short
  353. The reply code. The AMQ reply codes are defined in AMQ
  354. RFC 011.
  355. reply_text: shortstr
  356. The localised reply text. This text can be logged as an
  357. aid to resolving issues.
  358. class_id: short
  359. failing method class
  360. When the close is provoked by a method exception, this
  361. is the class of the method.
  362. method_id: short
  363. failing method ID
  364. When the close is provoked by a method exception, this
  365. is the ID of the method.
  366. """
  367. if self.transport is None:
  368. # already closed
  369. return
  370. args = AMQPWriter()
  371. args.write_short(reply_code)
  372. args.write_shortstr(reply_text)
  373. args.write_short(method_sig[0]) # class_id
  374. args.write_short(method_sig[1]) # method_id
  375. self._send_method((10, 50), args)
  376. return self.wait(allowed_methods=[
  377. (10, 50), # Connection.close
  378. (10, 51), # Connection.close_ok
  379. ])
  380. def _close(self, args):
  381. """Request a connection close
  382. This method indicates that the sender wants to close the
  383. connection. This may be due to internal conditions (e.g. a
  384. forced shut-down) or due to an error handling a specific
  385. method, i.e. an exception. When a close is due to an
  386. exception, the sender provides the class and method id of the
  387. method which caused the exception.
  388. RULE:
  389. After sending this method any received method except the
  390. Close-OK method MUST be discarded.
  391. RULE:
  392. The peer sending this method MAY use a counter or timeout
  393. to detect failure of the other peer to respond correctly
  394. with the Close-OK method.
  395. RULE:
  396. When a server receives the Close method from a client it
  397. MUST delete all server-side resources associated with the
  398. client's context. A client CANNOT reconnect to a context
  399. after sending or receiving a Close method.
  400. PARAMETERS:
  401. reply_code: short
  402. The reply code. The AMQ reply codes are defined in AMQ
  403. RFC 011.
  404. reply_text: shortstr
  405. The localised reply text. This text can be logged as an
  406. aid to resolving issues.
  407. class_id: short
  408. failing method class
  409. When the close is provoked by a method exception, this
  410. is the class of the method.
  411. method_id: short
  412. failing method ID
  413. When the close is provoked by a method exception, this
  414. is the ID of the method.
  415. """
  416. reply_code = args.read_short()
  417. reply_text = args.read_shortstr()
  418. class_id = args.read_short()
  419. method_id = args.read_short()
  420. self._x_close_ok()
  421. raise error_for_code(reply_code, reply_text,
  422. (class_id, method_id), ConnectionError)
  423. def _blocked(self, args):
  424. """RabbitMQ Extension."""
  425. reason = args.read_shortstr()
  426. if self.on_blocked:
  427. return self.on_blocked(reason)
  428. def _unblocked(self, *args):
  429. if self.on_unblocked:
  430. return self.on_unblocked()
  431. def _x_close_ok(self):
  432. """Confirm a connection close
  433. This method confirms a Connection.Close method and tells the
  434. recipient that it is safe to release resources for the
  435. connection and close the socket.
  436. RULE:
  437. A peer that detects a socket closure without having
  438. received a Close-Ok handshake method SHOULD log the error.
  439. """
  440. self._send_method((10, 51))
  441. self._do_close()
  442. def _close_ok(self, args):
  443. """Confirm a connection close
  444. This method confirms a Connection.Close method and tells the
  445. recipient that it is safe to release resources for the
  446. connection and close the socket.
  447. RULE:
  448. A peer that detects a socket closure without having
  449. received a Close-Ok handshake method SHOULD log the error.
  450. """
  451. self._do_close()
  452. def _x_open(self, virtual_host, capabilities=''):
  453. """Open connection to virtual host
  454. This method opens a connection to a virtual host, which is a
  455. collection of resources, and acts to separate multiple
  456. application domains within a server.
  457. RULE:
  458. The client MUST open the context before doing any work on
  459. the connection.
  460. PARAMETERS:
  461. virtual_host: shortstr
  462. virtual host name
  463. The name of the virtual host to work with.
  464. RULE:
  465. If the server supports multiple virtual hosts, it
  466. MUST enforce a full separation of exchanges,
  467. queues, and all associated entities per virtual
  468. host. An application, connected to a specific
  469. virtual host, MUST NOT be able to access resources
  470. of another virtual host.
  471. RULE:
  472. The server SHOULD verify that the client has
  473. permission to access the specified virtual host.
  474. RULE:
  475. The server MAY configure arbitrary limits per
  476. virtual host, such as the number of each type of
  477. entity that may be used, per connection and/or in
  478. total.
  479. capabilities: shortstr
  480. required capabilities
  481. The client may specify a number of capability names,
  482. delimited by spaces. The server can use this string
  483. to how to process the client's connection request.
  484. """
  485. args = AMQPWriter()
  486. args.write_shortstr(virtual_host)
  487. args.write_shortstr(capabilities)
  488. args.write_bit(False)
  489. self._send_method((10, 40), args)
  490. return self.wait(allowed_methods=[
  491. (10, 41), # Connection.open_ok
  492. ])
  493. def _open_ok(self, args):
  494. """Signal that the connection is ready
  495. This method signals to the client that the connection is ready
  496. for use.
  497. PARAMETERS:
  498. known_hosts: shortstr (deprecated)
  499. """
  500. AMQP_LOGGER.debug('Open OK!')
  501. def _secure(self, args):
  502. """Security mechanism challenge
  503. The SASL protocol works by exchanging challenges and responses
  504. until both peers have received sufficient information to
  505. authenticate each other. This method challenges the client to
  506. provide more information.
  507. PARAMETERS:
  508. challenge: longstr
  509. security challenge data
  510. Challenge information, a block of opaque binary data
  511. passed to the security mechanism.
  512. """
  513. challenge = args.read_longstr() # noqa
  514. def _x_secure_ok(self, response):
  515. """Security mechanism response
  516. This method attempts to authenticate, passing a block of SASL
  517. data for the security mechanism at the server side.
  518. PARAMETERS:
  519. response: longstr
  520. security response data
  521. A block of opaque data passed to the security
  522. mechanism. The contents of this data are defined by
  523. the SASL security mechanism.
  524. """
  525. args = AMQPWriter()
  526. args.write_longstr(response)
  527. self._send_method((10, 21), args)
  528. def _start(self, args):
  529. """Start connection negotiation
  530. This method starts the connection negotiation process by
  531. telling the client the protocol version that the server
  532. proposes, along with a list of security mechanisms which the
  533. client can use for authentication.
  534. RULE:
  535. If the client cannot handle the protocol version suggested
  536. by the server it MUST close the socket connection.
  537. RULE:
  538. The server MUST provide a protocol version that is lower
  539. than or equal to that requested by the client in the
  540. protocol header. If the server cannot support the
  541. specified protocol it MUST NOT send this method, but MUST
  542. close the socket connection.
  543. PARAMETERS:
  544. version_major: octet
  545. protocol major version
  546. The protocol major version that the server agrees to
  547. use, which cannot be higher than the client's major
  548. version.
  549. version_minor: octet
  550. protocol major version
  551. The protocol minor version that the server agrees to
  552. use, which cannot be higher than the client's minor
  553. version.
  554. server_properties: table
  555. server properties
  556. mechanisms: longstr
  557. available security mechanisms
  558. A list of the security mechanisms that the server
  559. supports, delimited by spaces. Currently ASL supports
  560. these mechanisms: PLAIN.
  561. locales: longstr
  562. available message locales
  563. A list of the message locales that the server
  564. supports, delimited by spaces. The locale defines the
  565. language in which the server will send reply texts.
  566. RULE:
  567. All servers MUST support at least the en_US
  568. locale.
  569. """
  570. self.version_major = args.read_octet()
  571. self.version_minor = args.read_octet()
  572. self.server_properties = args.read_table()
  573. self.mechanisms = args.read_longstr().split(' ')
  574. self.locales = args.read_longstr().split(' ')
  575. AMQP_LOGGER.debug(
  576. START_DEBUG_FMT,
  577. self.version_major, self.version_minor,
  578. self.server_properties, self.mechanisms, self.locales,
  579. )
  580. def _x_start_ok(self, client_properties, mechanism, response, locale):
  581. """Select security mechanism and locale
  582. This method selects a SASL security mechanism. ASL uses SASL
  583. (RFC2222) to negotiate authentication and encryption.
  584. PARAMETERS:
  585. client_properties: table
  586. client properties
  587. mechanism: shortstr
  588. selected security mechanism
  589. A single security mechanisms selected by the client,
  590. which must be one of those specified by the server.
  591. RULE:
  592. The client SHOULD authenticate using the highest-
  593. level security profile it can handle from the list
  594. provided by the server.
  595. RULE:
  596. The mechanism field MUST contain one of the
  597. security mechanisms proposed by the server in the
  598. Start method. If it doesn't, the server MUST close
  599. the socket.
  600. response: longstr
  601. security response data
  602. A block of opaque data passed to the security
  603. mechanism. The contents of this data are defined by
  604. the SASL security mechanism. For the PLAIN security
  605. mechanism this is defined as a field table holding two
  606. fields, LOGIN and PASSWORD.
  607. locale: shortstr
  608. selected message locale
  609. A single message local selected by the client, which
  610. must be one of those specified by the server.
  611. """
  612. if self.server_capabilities.get('consumer_cancel_notify'):
  613. if 'capabilities' not in client_properties:
  614. client_properties['capabilities'] = {}
  615. client_properties['capabilities']['consumer_cancel_notify'] = True
  616. if self.server_capabilities.get('connection.blocked'):
  617. if 'capabilities' not in client_properties:
  618. client_properties['capabilities'] = {}
  619. client_properties['capabilities']['connection.blocked'] = True
  620. args = AMQPWriter()
  621. args.write_table(client_properties)
  622. args.write_shortstr(mechanism)
  623. args.write_longstr(response)
  624. args.write_shortstr(locale)
  625. self._send_method((10, 11), args)
  626. def _tune(self, args):
  627. """Propose connection tuning parameters
  628. This method proposes a set of connection configuration values
  629. to the client. The client can accept and/or adjust these.
  630. PARAMETERS:
  631. channel_max: short
  632. proposed maximum channels
  633. The maximum total number of channels that the server
  634. allows per connection. Zero means that the server does
  635. not impose a fixed limit, but the number of allowed
  636. channels may be limited by available server resources.
  637. frame_max: long
  638. proposed maximum frame size
  639. The largest frame size that the server proposes for
  640. the connection. The client can negotiate a lower
  641. value. Zero means that the server does not impose any
  642. specific limit but may reject very large frames if it
  643. cannot allocate resources for them.
  644. RULE:
  645. Until the frame-max has been negotiated, both
  646. peers MUST accept frames of up to 4096 octets
  647. large. The minimum non-zero value for the frame-
  648. max field is 4096.
  649. heartbeat: short
  650. desired heartbeat delay
  651. The delay, in seconds, of the connection heartbeat
  652. that the server wants. Zero means the server does not
  653. want a heartbeat.
  654. """
  655. client_heartbeat = self.client_heartbeat or 0
  656. self.channel_max = args.read_short() or self.channel_max
  657. self.frame_max = args.read_long() or self.frame_max
  658. self.method_writer.frame_max = self.frame_max
  659. self.server_heartbeat = args.read_short() or 0
  660. # negotiate the heartbeat interval to the smaller of the
  661. # specified values
  662. if self.server_heartbeat == 0 or client_heartbeat == 0:
  663. self.heartbeat = max(self.server_heartbeat, client_heartbeat)
  664. else:
  665. self.heartbeat = min(self.server_heartbeat, client_heartbeat)
  666. # Ignore server heartbeat if client_heartbeat is disabled
  667. if not self.client_heartbeat:
  668. self.heartbeat = 0
  669. self._x_tune_ok(self.channel_max, self.frame_max, self.heartbeat)
  670. def send_heartbeat(self):
  671. self.transport.write_frame(8, 0, bytes())
  672. def heartbeat_tick(self, rate=2):
  673. """Send heartbeat packets, if necessary, and fail if none have been
  674. received recently. This should be called frequently, on the order of
  675. once per second.
  676. :keyword rate: Ignored
  677. """
  678. if not self.heartbeat:
  679. return
  680. # treat actual data exchange in either direction as a heartbeat
  681. sent_now = self.method_writer.bytes_sent
  682. recv_now = self.method_reader.bytes_recv
  683. if self.prev_sent is None or self.prev_sent != sent_now:
  684. self.last_heartbeat_sent = monotonic()
  685. if self.prev_recv is None or self.prev_recv != recv_now:
  686. self.last_heartbeat_received = monotonic()
  687. self.prev_sent, self.prev_recv = sent_now, recv_now
  688. # send a heartbeat if it's time to do so
  689. if monotonic() > self.last_heartbeat_sent + self.heartbeat:
  690. self.send_heartbeat()
  691. self.last_heartbeat_sent = monotonic()
  692. # if we've missed two intervals' heartbeats, fail; this gives the
  693. # server enough time to send heartbeats a little late
  694. if (self.last_heartbeat_received and
  695. self.last_heartbeat_received + 2 *
  696. self.heartbeat < monotonic()):
  697. raise ConnectionForced('Too many heartbeats missed')
  698. def _x_tune_ok(self, channel_max, frame_max, heartbeat):
  699. """Negotiate connection tuning parameters
  700. This method sends the client's connection tuning parameters to
  701. the server. Certain fields are negotiated, others provide
  702. capability information.
  703. PARAMETERS:
  704. channel_max: short
  705. negotiated maximum channels
  706. The maximum total number of channels that the client
  707. will use per connection. May not be higher than the
  708. value specified by the server.
  709. RULE:
  710. The server MAY ignore the channel-max value or MAY
  711. use it for tuning its resource allocation.
  712. frame_max: long
  713. negotiated maximum frame size
  714. The largest frame size that the client and server will
  715. use for the connection. Zero means that the client
  716. does not impose any specific limit but may reject very
  717. large frames if it cannot allocate resources for them.
  718. Note that the frame-max limit applies principally to
  719. content frames, where large contents can be broken
  720. into frames of arbitrary size.
  721. RULE:
  722. Until the frame-max has been negotiated, both
  723. peers must accept frames of up to 4096 octets
  724. large. The minimum non-zero value for the frame-
  725. max field is 4096.
  726. heartbeat: short
  727. desired heartbeat delay
  728. The delay, in seconds, of the connection heartbeat
  729. that the client wants. Zero means the client does not
  730. want a heartbeat.
  731. """
  732. args = AMQPWriter()
  733. args.write_short(channel_max)
  734. args.write_long(frame_max)
  735. args.write_short(heartbeat or 0)
  736. self._send_method((10, 31), args)
  737. self._wait_tune_ok = False
  738. @property
  739. def sock(self):
  740. return self.transport.sock
  741. @property
  742. def server_capabilities(self):
  743. return self.server_properties.get('capabilities') or {}
  744. _METHOD_MAP = {
  745. (10, 10): _start,
  746. (10, 20): _secure,
  747. (10, 30): _tune,
  748. (10, 41): _open_ok,
  749. (10, 50): _close,
  750. (10, 51): _close_ok,
  751. (10, 60): _blocked,
  752. (10, 61): _unblocked,
  753. }
  754. _IMMEDIATE_METHODS = []
  755. connection_errors = (
  756. ConnectionError,
  757. socket.error,
  758. IOError,
  759. OSError,
  760. )
  761. channel_errors = (ChannelError, )
  762. recoverable_connection_errors = (
  763. RecoverableConnectionError,
  764. socket.error,
  765. IOError,
  766. OSError,
  767. )
  768. recoverable_channel_errors = (
  769. RecoverableChannelError,
  770. )