/
amqp.py
242 lines (197 loc) · 7.29 KB
/
amqp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
"""
"""
import os
import sys
from time import time
from zope.interface import implements
from twisted.internet import reactor
from twisted.internet import defer
from twisted.internet import protocol
from twisted.python import failure
from twisted.python import usage
from twisted.python import log
from twisted.plugin import IPlugin
from twisted.application.service import IServiceMaker
from txamqp import spec
from txamqp.content import Content
from txamqp.client import TwistedDelegate
from txamqp.protocol import AMQClient, AMQChannel, Frame
from txamqp.queue import TimeoutDeferredQueue
SPEC_PATH = os.path.join(os.path.abspath('.'), 'amqp0-8.xml')
class HeartbeatExpired(Exception):
"""
"""
class ChannelWithCallback(AMQChannel):
def __init__(self, *args):
AMQChannel.__init__(self, *args)
self.deliver_callback = lambda dev_null: dev_null
def set_consumer_callback(self, callback):
self.deliver_callback = callback
class AMQPProtocol(AMQClient):
""" Improvements to the txamqp implementation.
"""
channelClass=ChannelWithCallback
next_channel_id = 0
closed = True
def channel(self, id=None):
"""Overrides AMQClient. Changes:
1) no need to return deferred. The channelLock doesn't protect
against any race conditions; the channel reference is returned,
so any number of those references could exist already.
2) auto channel numbering
3) replace deferred queue for basic_deliver(s) with simple
buffer(list)
"""
if id is None:
self.next_channel_id += 1
id = self.next_channel_id
try:
ch = self.channels[id]
except KeyError:
ch = self.channelFactory(id, self.outgoing)
self.channels[id] = ch
return ch
def queue(self, key):
""" This is the channel basic_deliver queue
"""
try:
q = self.queues[key]
except KeyError:
q = TimeoutDeferredQueue()
self.queues[key] = q
return q
def connectionMade(self):
self.closed = False
AMQClient.connectionMade(self)
def processFrame(self, frame):
ch = self.channel(frame.channel)
if frame.payload.type == Frame.HEARTBEAT:
self.lastHBReceived = time()
log.msg('Heartbeat received %s' % (str(self.lastHBReceived),))
else:
ch.dispatch(frame, self.work)
if self.heartbeatInterval > 0:
self.reschedule_checkHB()
def frameLengthExceeded(self):
"""
"""
def checkHeartbeat(self):
if self.checkHB.active():
self.checkHB.cancel()
log.msg('AMQP Heartbeat expired on client side')
self.transport.loseConnection(failure.Failure(HeartbeatExpired('Heartbeat expired.')))
def reschedule_sendHB(self):
log.msg('Reschedule send heartbeat')
AMQClient.reschedule_sendHB(self)
def reschedule_checkHB(self):
log.msg('Reschedule check heartbeat')
AMQClient.reschedule_checkHB(self)
def sendHeartbeat(self):
log.msg('sendHeartbeat')
AMQClient.sendHeartbeat(self)
class ConnectionCreator(object):
"""Create AMQP Client.
The AMQP Client uses one persistent connection, so a Factory is not
necessary.
Client Creator is initialized with AMQP Broker configuration.
ConnectTCP is called with TCP configuration.
"""
protocol = AMQPProtocol
spec_cache = {}
def __init__(self, reactor, username='guest', password='guest',
vhost='/', delegate=None,
spec_path=SPEC_PATH,
heartbeat=0):
self.reactor = reactor
self.username = username
self.password = password
self.vhost = vhost
# Cache the specs for enormous speedups on subsequent connections
cache = ConnectionCreator.spec_cache
if spec_path in cache:
self.spec = cache[spec_path]
else:
self.spec = cache.setdefault(spec_path, spec.load(spec_path))
self.heartbeat = heartbeat
if delegate is None:
delegate = TwistedDelegate()
self.delegate = delegate
self.connector = None
def connectTCP(self, host, port, timeout=30, bindAddress=None):
"""Connect to remote Broker host, return a Deferred of resulting protocol
instance.
"""
d = defer.Deferred()
p = self.protocol(self.delegate,
self.vhost,
self.spec,
heartbeat=self.heartbeat)
p.factory = self
f = protocol._InstanceFactory(self.reactor, p, d)
self.connector = self.reactor.connectTCP(host, port, f, timeout=timeout,
bindAddress=bindAddress)
def auth_cb(conn):
d = conn.authenticate(self.username, self.password)
d.addCallback(lambda _: conn)
return d
d.addCallback(auth_cb)
return d
class AMQPEvents(TwistedDelegate):
"""
This class defines handlers for asynchronous amqp events (events the
broker can raise at any time).
"""
def basic_deliver(self, ch, msg):
"""
"""
#d = defer.maybeDeferred(ch.deliver_callback, msg)
def basic_return(self, ch, msg):
"""implement this to handle messages that could not be delivered to
a queue or consumer
"""
log.msg("""basic.return event received! This means the broker\
could not guarantee reliable delivery for a message sent\
from a process in this container.""")
def channel_flow(self, ch, msg):
"""implement this to handle a broker flow control request
"""
log.msg('channel.flow event received')
def channel_alert(self, ch, msg):
"""implement this to handle a broker channel.alert notification.
"""
log.msg('channel.alert event received')
def close(self, reason):
"""The AMQClient protocol calls this as a result of a
connectionLost event. The TwistedDelegate.close method finishes
shutting off the client, and we get a chance to react to the event
here.
"""
TwistedDelegate.close(self, reason)
log.err("AMQP Client Connection Closed")
log.err(reason)
class Options(usage.Options):
optParameters = [
['broker_host', 'h', 'localhost', ''],
['heartbeat', 'b', 0, 'Heartbeat in seconds'],
]
def main():
log.startLogging(sys.stdout)
config = Options()
config.parseOptions()
print type(config['heartbeat'])
amqpEvents = AMQPEvents()
clientCreator = ConnectionCreator(reactor,
vhost='/',
delegate=amqpEvents,
heartbeat=int(config['heartbeat']),
username='guest',
password='guest')
d = clientCreator.connectTCP(config['broker_host'], 5672)
def connected(client):
log.msg('AMQP Client Connected')
return client
d.addCallback(connected)
d.addErrback(defer.logError)
if __name__ == "__main__":
main()
reactor.run()