hayeah-ASS 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +0 -162
- data/Rakefile +1 -1
- data/VERSION.yml +2 -2
- data/lib/ass.rb +137 -338
- metadata +3 -4
- data/ASS.gemspec +0 -48
data/README.textile
CHANGED
@@ -1,162 +0,0 @@
|
|
1
|
-
|
2
|
-
Asynchronous Service Stages (ASS) is a way to
|
3
|
-
organize distributed services by decoupling the
|
4
|
-
what and how of computation from the when and
|
5
|
-
where. Built on top of RabbitMQ (an implementation
|
6
|
-
of AMQP), ASS helps you to build robust and
|
7
|
-
scalable distributed applications.
|
8
|
-
|
9
|
-
End of project pimping, let's get started.
|
10
|
-
|
11
|
-
h1. Install
|
12
|
-
|
13
|
-
You need
|
14
|
-
|
15
|
-
(1) Erlang
|
16
|
-
(2) RabbitMQ
|
17
|
-
(3) AMQP gem
|
18
|
-
|
19
|
-
Thread.new { EM.run }
|
20
|
-
AMQP.start
|
21
|
-
|
22
|
-
^C to exit
|
23
|
-
|
24
|
-
|
25
|
-
h1. The Basics
|
26
|
-
|
27
|
-
A service component is a ruby script that
|
28
|
-
communicates with RabbitMQ. You need to define the
|
29
|
-
AMQP server your ASS depends on. Something like this,
|
30
|
-
|
31
|
-
|
32
|
-
require 'rubygems'
|
33
|
-
require 'ass'
|
34
|
-
AMQP.start(:host => 'localhost',
|
35
|
-
#:vhost => "/ass-test",
|
36
|
-
:logging => false) do
|
37
|
-
# ASS definition
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
To start a server
|
42
|
-
|
43
|
-
server = ASS.new("echo")
|
44
|
-
# => #<ASS::Server echo>
|
45
|
-
|
46
|
-
But it doesn't do anything yet. You define the
|
47
|
-
behaviour of the server by setting its
|
48
|
-
callback. The callback can be a class, so that for
|
49
|
-
each client request an object is created from the
|
50
|
-
class to process the request. Like so,
|
51
|
-
|
52
|
-
|
53
|
-
server.react(SomeReactorClass)
|
54
|
-
|
55
|
-
|
56
|
-
However, often you just want something simple. The
|
57
|
-
react method can take a block and construct an
|
58
|
-
anonymous callback class from which the server
|
59
|
-
creates an callback object for each request. Here
|
60
|
-
we ask the server to react to @foo@ or @bar@.
|
61
|
-
|
62
|
-
|
63
|
-
server.react {
|
64
|
-
def foo(input)
|
65
|
-
[:server,:foo,input]
|
66
|
-
end
|
67
|
-
|
68
|
-
def oof(input)
|
69
|
-
[:server,:oof,input]
|
70
|
-
end
|
71
|
-
}
|
72
|
-
|
73
|
-
|
74
|
-
The react method accepts for the callback either
|
75
|
-
a Class, a Module, a block, or any object. When an
|
76
|
-
object is used, it's considered a singleton, which
|
77
|
-
is used to process all the requests.
|
78
|
-
|
79
|
-
Now that we have a server, we need to get a client
|
80
|
-
so to call the server. Because the call is
|
81
|
-
asynchronous (the client doesn't wait for the
|
82
|
-
result), to process the result when it gets back,
|
83
|
-
we need to define callback for the client (just as
|
84
|
-
we did for the server). For each call to the
|
85
|
-
remote server, the result is processed at the
|
86
|
-
client side by a method of the same name,
|
87
|
-
|
88
|
-
|
89
|
-
client = server.client.react {
|
90
|
-
def foo(output)
|
91
|
-
p [:client,:foo,output]
|
92
|
-
end
|
93
|
-
def oof(output)
|
94
|
-
p [:client,:oof,output]
|
95
|
-
end
|
96
|
-
}
|
97
|
-
|
98
|
-
c.call(:foo,42)
|
99
|
-
c.call(:oof,24)
|
100
|
-
|
101
|
-
# [:client,:foo,[:server,:foo,42]]
|
102
|
-
# [:client,:foo,[:server,:foo,24]]
|
103
|
-
|
104
|
-
|
105
|
-
> ruby server.rb
|
106
|
-
> ruby client.rb
|
107
|
-
|
108
|
-
> ruby server.rb
|
109
|
-
^C
|
110
|
-
|
111
|
-
While the server is down, the requests the client
|
112
|
-
is making is queued by the underlying message
|
113
|
-
middleware (RabbitMQ), so in some future time when
|
114
|
-
we restart the server, we wouldn't lose any
|
115
|
-
request. Let's restart the server.
|
116
|
-
|
117
|
-
> ruby server.rb
|
118
|
-
|
119
|
-
See that the server caught up with all the pending
|
120
|
-
requests. To increase service capacity, we can
|
121
|
-
just increase the number of server instances.
|
122
|
-
|
123
|
-
> ruby server.rb
|
124
|
-
|
125
|
-
Now the load is distributed between these two
|
126
|
-
instances. We can also start more clients to
|
127
|
-
handle more load.
|
128
|
-
|
129
|
-
> ruby client.rb
|
130
|
-
|
131
|
-
You can see requests coming in from two clients.
|
132
|
-
|
133
|
-
|
134
|
-
h1. Service Configuration
|
135
|
-
|
136
|
-
-how the name of a service map to AMQP entities.
|
137
|
-
-various options for different functional characteristics.
|
138
|
-
|
139
|
-
-using routing_key
|
140
|
-
-using reply_to
|
141
|
-
|
142
|
-
|
143
|
-
rabbitmqctl list_exchanges
|
144
|
-
rabbitmqctl list_queues
|
145
|
-
|
146
|
-
|
147
|
-
h1. RPC service
|
148
|
-
|
149
|
-
The RPC client provides a synchronous API to the
|
150
|
-
asynchronous services. This is so the users of an
|
151
|
-
ASS application don't have to bother with the
|
152
|
-
difficulties of asynchronous programming style
|
153
|
-
with callbacks.
|
154
|
-
|
155
|
-
The RPC client is intended to be used as the
|
156
|
-
gateway into some reliable internal
|
157
|
-
services. While the internal services coordinated
|
158
|
-
with ASS needs to be robust to component failures,
|
159
|
-
there's no such requirements for gateways. It is
|
160
|
-
ok for a gateway to fail, and fail in delivering a
|
161
|
-
response, as long as the internal services carry
|
162
|
-
out the task without a hitch.
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ begin
|
|
9
9
|
gem.email = "hayeah@gmail.com"
|
10
10
|
gem.homepage = "http://github.com/hayeah/ass"
|
11
11
|
gem.authors = ["Howard Yeh"]
|
12
|
-
gem.add_dependency "
|
12
|
+
gem.add_dependency "mq"
|
13
13
|
gem.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
14
14
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
15
|
end
|
data/VERSION.yml
CHANGED
data/lib/ass.rb
CHANGED
@@ -1,209 +1,137 @@
|
|
1
1
|
require 'mq'
|
2
|
+
class Ass
|
3
|
+
|
4
|
+
attr_reader :server_exchange
|
2
5
|
|
3
|
-
|
4
|
-
|
6
|
+
def self.declare(server_exchange)
|
7
|
+
self.new([server_exchange,{:passive => true}])
|
8
|
+
end
|
9
|
+
|
10
|
+
# uh... we'll assume that the exchanges are all direct exchanges.
|
11
|
+
def initialize(server_exchange)
|
12
|
+
@server_exchange = get_exchange(server_exchange)
|
13
|
+
end
|
5
14
|
|
6
|
-
|
7
|
-
|
8
|
-
|
15
|
+
def client(client_exchange,*args)
|
16
|
+
@client_exchange ||= get_exchange(client_exchange)
|
17
|
+
q = get_queue(@client_exchange,*args)
|
18
|
+
Client.new(@server_exchange,@client_exchange,q)
|
9
19
|
end
|
10
20
|
|
11
|
-
def
|
12
|
-
|
21
|
+
def server(*args)
|
22
|
+
Server.new(@server_exchange,get_queue(@server_exchange,*args))
|
13
23
|
end
|
14
24
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
25
|
+
def get_exchange(arg)
|
26
|
+
case arg
|
27
|
+
when Array
|
28
|
+
exchanges = exchange[0]
|
29
|
+
opts = exchange[1]
|
30
|
+
else
|
31
|
+
exchange = arg
|
32
|
+
opts = nil
|
33
|
+
end
|
34
|
+
opts = {} if opts.nil?
|
35
|
+
exchange = exchange.is_a?(MQ::Exchange) ? exchange : MQ.direct(exchange,opts)
|
36
|
+
raise "accepts only direct exchanges" unless exchange.type == :direct
|
37
|
+
exchange
|
18
38
|
end
|
19
|
-
|
39
|
+
|
40
|
+
# can specify a key to create a queue for that subdomain
|
41
|
+
def get_queue(exchange,*args)
|
42
|
+
case args[0]
|
43
|
+
when Hash
|
44
|
+
key = nil
|
45
|
+
opts = args[0]
|
46
|
+
when String
|
47
|
+
key = args[0]
|
48
|
+
opts = args[1]
|
49
|
+
end
|
50
|
+
opts = {} if opts.nil?
|
51
|
+
if key
|
52
|
+
name = "#{exchange.name}--#{key}"
|
53
|
+
q = MQ.queue(name,opts)
|
54
|
+
q.bind(exchange,{ :routing_key => key})
|
55
|
+
else
|
56
|
+
q = MQ.queue(exchange.name,opts)
|
57
|
+
q.bind(exchange,{ :routing_key => exchange.name })
|
58
|
+
end
|
59
|
+
q
|
60
|
+
end
|
61
|
+
|
20
62
|
module Callback
|
21
|
-
module MagicMethods
|
22
|
-
def header
|
23
|
-
@__header__
|
24
|
-
end
|
25
|
-
|
26
|
-
def meta
|
27
|
-
@__meta__
|
28
|
-
end
|
29
|
-
|
30
|
-
def service
|
31
|
-
@__service__
|
32
|
-
end
|
33
|
-
|
34
|
-
def call(method,data=nil,meta=nil,opts={})
|
35
|
-
@__service__.call(method,data,meta,opts)
|
36
|
-
end
|
37
|
-
end
|
38
63
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
Class.new &callback
|
44
|
-
when Class
|
45
|
-
callback
|
46
|
-
when Module
|
47
|
-
Class.new { include callback }
|
48
|
-
when Object
|
49
|
-
callback # use singleton objcet as callback
|
50
|
-
end
|
51
|
-
case c
|
64
|
+
def build_callback_klass(callback)
|
65
|
+
case callback
|
66
|
+
when Proc
|
67
|
+
Class.new &callback
|
52
68
|
when Class
|
53
|
-
|
54
|
-
|
55
|
-
|
69
|
+
callback
|
70
|
+
when Module
|
71
|
+
Class.new { include callback }
|
56
72
|
end
|
57
|
-
c
|
58
73
|
end
|
59
|
-
|
60
|
-
|
61
|
-
def prepare_callback(callback,info,payload)
|
74
|
+
|
75
|
+
def callback(info,payload)
|
62
76
|
# method,data,meta
|
63
|
-
if
|
64
|
-
|
65
|
-
klass = callback.get_version(payload[:version])
|
66
|
-
else
|
67
|
-
klass = callback
|
68
|
-
end
|
69
|
-
obj = klass.new
|
77
|
+
if @callback_klass.respond_to? :version
|
78
|
+
klass = @callback_klass.get_version(payload[:version])
|
70
79
|
else
|
71
|
-
|
80
|
+
klass = @callback_klass
|
72
81
|
end
|
73
|
-
obj.
|
82
|
+
obj = klass.new
|
83
|
+
service = self
|
84
|
+
obj.instance_variable_set("@__service__",service)
|
74
85
|
obj.instance_variable_set("@__header__",info)
|
75
86
|
obj.instance_variable_set("@__meta__",payload[:meta])
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
class Server
|
82
|
-
include Callback
|
83
|
-
|
84
|
-
def initialize(name,opts={})
|
85
|
-
@server_exchange = MQ.fanout(name,opts)
|
86
|
-
end
|
87
|
-
|
88
|
-
def name
|
89
|
-
self.exchange.name
|
90
|
-
end
|
91
|
-
|
92
|
-
def exchange
|
93
|
-
@server_exchange
|
94
|
-
end
|
95
|
-
|
96
|
-
# takes options available to MQ::Exchange
|
97
|
-
def client(opts={})
|
98
|
-
ASS::Client.new(self,opts)
|
99
|
-
end
|
100
|
-
|
101
|
-
def client_name
|
102
|
-
"#{self.exchange.name}--"
|
103
|
-
end
|
104
|
-
|
105
|
-
# takes options available to MQ::Queue# takes options available to MQ::Queue#subscribe
|
106
|
-
def rpc(opts={})
|
107
|
-
ASS::RPC.new(self,opts)
|
108
|
-
end
|
87
|
+
class << obj
|
88
|
+
def header
|
89
|
+
@__header__
|
90
|
+
end
|
109
91
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
92
|
+
def meta
|
93
|
+
@__meta__
|
94
|
+
end
|
95
|
+
|
96
|
+
def service
|
97
|
+
@__service__
|
98
|
+
end
|
117
99
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
opts = callback
|
122
|
-
callback = block
|
123
|
-
end
|
124
|
-
opts = {} if opts.nil?
|
125
|
-
|
126
|
-
@callback = build_callback(callback)
|
127
|
-
@ack = opts[:ack]
|
128
|
-
self.queue unless @queue
|
129
|
-
@queue.subscribe(opts) do |info,payload|
|
130
|
-
payload = ::Marshal.load(payload)
|
131
|
-
#p [info,info.reply_to,payload]
|
132
|
-
obj = prepare_callback(@callback,info,payload)
|
133
|
-
data2 = obj.send(payload[:method],payload[:data])
|
134
|
-
payload2 = payload.merge :data => data2
|
135
|
-
# the client MUST exist, otherwise it's an error.
|
136
|
-
## FIXME it's bad if the server dies b/c
|
137
|
-
## the client isn't there. It's bad that
|
138
|
-
## this can cause the server to fail.
|
139
|
-
MQ.direct(info.reply_to,:passive => true).
|
140
|
-
publish(::Marshal.dump(payload2),
|
141
|
-
:routing_key => info.routing_key,
|
142
|
-
:message_id => info.message_id) if info.reply_to
|
143
|
-
info.ack if @ack
|
100
|
+
def call(method,data=nil,meta=nil,opts={})
|
101
|
+
@__service__.call(method,data,meta,opts)
|
102
|
+
end
|
144
103
|
end
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
def inspect
|
149
|
-
"#<#{self.class} #{self.name}>"
|
104
|
+
#p [:call,payload]
|
105
|
+
obj.send(payload[:method],
|
106
|
+
payload[:data])
|
150
107
|
end
|
151
108
|
end
|
152
109
|
|
153
110
|
class Client
|
154
111
|
include Callback
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
@
|
159
|
-
# the routing key is also used as the name of the client
|
160
|
-
@key = opts.delete :key
|
161
|
-
@key = @key.to_s if @key
|
162
|
-
@client_exchange = MQ.direct @server.client_name, opts
|
112
|
+
def initialize(server_exchange,client_exchange,queue)
|
113
|
+
@server_exchange = server_exchange
|
114
|
+
@client_exchange = client_exchange
|
115
|
+
@queue = queue
|
163
116
|
end
|
164
|
-
|
165
|
-
def name
|
166
|
-
self.exchange.name
|
167
|
-
end
|
168
|
-
|
169
|
-
def exchange
|
170
|
-
@client_exchange
|
171
|
-
end
|
172
|
-
|
173
|
-
# takes options available to MQ::Queue
|
174
|
-
def queue(opts={})
|
175
|
-
unless @queue
|
176
|
-
# if key is not given, the queue name is
|
177
|
-
# the same as the exchange name.
|
178
|
-
@queue ||= MQ.queue("#{self.name}#{@key}",opts)
|
179
|
-
@queue.bind(self.exchange,:routing_key => @key || self.name)
|
180
|
-
end
|
181
|
-
self # return self to allow chaining
|
182
|
-
end
|
183
|
-
|
184
|
-
# takes options available to MQ::Queue#subscribe
|
117
|
+
|
185
118
|
def react(callback=nil,opts=nil,&block)
|
186
119
|
if block
|
187
120
|
opts = callback
|
188
121
|
callback = block
|
189
122
|
end
|
190
123
|
opts = {} if opts.nil?
|
191
|
-
|
192
|
-
@
|
124
|
+
|
125
|
+
@callback_klass = build_callback_klass(callback)
|
193
126
|
@ack = opts[:ack]
|
194
|
-
# ensure queue is set
|
195
|
-
self.queue unless @queue
|
196
127
|
@queue.subscribe(opts) do |info,payload|
|
197
128
|
payload = ::Marshal.load(payload)
|
198
|
-
|
199
|
-
obj.send(payload[:method],payload[:data])
|
129
|
+
callback(info,payload)
|
200
130
|
info.ack if @ack
|
201
131
|
end
|
202
132
|
self
|
203
133
|
end
|
204
|
-
|
205
|
-
# note that we can redirect the result to some
|
206
|
-
# place else by setting :key and :reply_to
|
134
|
+
|
207
135
|
def call(method,data=nil,meta=nil,opts={})
|
208
136
|
# opts passed to publish
|
209
137
|
# if no routing key is given, use receiver's name as the routing key.
|
@@ -215,199 +143,70 @@ module ASS
|
|
215
143
|
:version => version
|
216
144
|
}
|
217
145
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
146
|
+
# set it up s.t. server would respond to
|
147
|
+
# private queue if key is given, otherwise
|
148
|
+
# the server would respond to public queue.
|
149
|
+
key = opts.delete(:key)
|
150
|
+
@server_exchange.publish Marshal.dump(payload), {
|
151
|
+
:key => (key ? key : @server_exchange.name),
|
152
|
+
:reply_to => @client_exchange.name
|
222
153
|
}.merge(opts)
|
223
154
|
end
|
224
|
-
|
155
|
+
|
225
156
|
# for casting, just null the reply_to field, so server doesn't respond.
|
226
157
|
def cast(method,data=nil,meta=nil,opts={})
|
227
158
|
self.call(method,data,meta,opts.merge({:reply_to => nil}))
|
228
159
|
end
|
229
160
|
|
230
|
-
|
231
|
-
"#<#{self.class} #{self.name}>"
|
232
|
-
end
|
161
|
+
|
233
162
|
end
|
234
163
|
|
235
|
-
|
236
|
-
|
237
|
-
class RPC
|
238
|
-
require 'thread'
|
239
|
-
|
240
|
-
# i don't want deferrable. I want actual blockage when waiting.
|
241
|
-
## subscribe prolly run in a different thread.
|
242
|
-
# hmmm. I guess deferrable is a better idea.
|
243
|
-
class Future
|
244
|
-
attr_reader :message_id
|
245
|
-
attr_accessor :header, :data, :meta, :timeout
|
246
|
-
def initialize(rpc,message_id)
|
247
|
-
@message_id = message_id
|
248
|
-
@rpc = rpc
|
249
|
-
@timeout = false
|
250
|
-
@done = false
|
251
|
-
end
|
252
|
-
|
253
|
-
def wait(timeout=nil,&block)
|
254
|
-
# TODO timeout with eventmachine
|
255
|
-
@rpc.wait(self,timeout,&block) # synchronous call that will block
|
256
|
-
# EM.cancel_timer(ticket)
|
257
|
-
end
|
258
|
-
|
259
|
-
def done!
|
260
|
-
@done = true
|
261
|
-
end
|
262
|
-
|
263
|
-
def done?
|
264
|
-
@done
|
265
|
-
end
|
266
|
-
|
267
|
-
def timeout?
|
268
|
-
@timeout
|
269
|
-
end
|
270
|
-
|
271
|
-
def inspect
|
272
|
-
"#<#{self.class} #{message_id}>"
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
class Reactor
|
277
|
-
# want to minimize name conflicts here.
|
278
|
-
def initialize(rpc)
|
279
|
-
@rpc = rpc
|
280
|
-
end
|
281
|
-
|
282
|
-
def method_missing(_method,data)
|
283
|
-
@rpc.buffer << [header,data,meta]
|
284
|
-
end
|
285
|
-
end
|
164
|
+
class Server
|
165
|
+
include Callback
|
286
166
|
|
287
|
-
|
288
|
-
|
289
|
-
@
|
290
|
-
@seq = 0
|
291
|
-
# queue is used be used to synchronize RPC
|
292
|
-
# user thread and the AMQP eventmachine thread.
|
293
|
-
@buffer = Queue.new
|
294
|
-
@ready = {} # the ready results not yet waited
|
295
|
-
@futures = {} # all futures not yet waited for.
|
296
|
-
@reactor = Reactor.new(self)
|
297
|
-
# Creates an exclusive queue to serve the RPC client.
|
298
|
-
@client = @server.client(:key => "rpc.#{rand(999_999_999_999)}").
|
299
|
-
queue(:exclusive => true).react(@reactor,opts)
|
167
|
+
def initialize(server_exchange,q)
|
168
|
+
@queue = q
|
169
|
+
@server_exchange = server_exchange
|
300
170
|
end
|
301
171
|
|
302
|
-
|
303
|
-
|
304
|
-
@
|
305
|
-
@seq += 1
|
306
|
-
@futures[message_id] = Future.new(self,message_id)
|
172
|
+
attr_reader :queue
|
173
|
+
def exchange
|
174
|
+
@server_exchange
|
307
175
|
end
|
308
176
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
# calling wait is the same as the EventMachine
|
314
|
-
# thread.
|
315
|
-
def wait(future,timeout=nil)
|
316
|
-
return future.data if future.done? # future was waited before
|
317
|
-
timer = nil
|
318
|
-
if timeout
|
319
|
-
timer = EM.add_timer(timeout) {
|
320
|
-
@buffer << [:timeout,future.message_id,nil]
|
321
|
-
}
|
177
|
+
def react(callback=nil,opts=nil,&block)
|
178
|
+
if block
|
179
|
+
opts = callback
|
180
|
+
callback = block
|
322
181
|
end
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
# If we didn't find the future among the
|
341
|
-
# future, it must have timedout. Just
|
342
|
-
# throw result away and keep processing.
|
343
|
-
next unless some_future
|
344
|
-
some_future.header = header
|
345
|
-
some_future.data = data
|
346
|
-
some_future.meta = meta
|
347
|
-
if some_future == future
|
348
|
-
# The future we are waiting for
|
349
|
-
EM.cancel_timer(timer)
|
350
|
-
ready_future = future
|
351
|
-
break
|
352
|
-
else
|
353
|
-
# Ready, but we are not waiting for it. Save for later.
|
354
|
-
@ready[some_future.message_id] = some_future
|
355
|
-
end
|
182
|
+
opts = {} if opts.nil?
|
183
|
+
|
184
|
+
@callback_klass = build_callback_klass(callback)
|
185
|
+
@ack = opts[:ack]
|
186
|
+
@queue.subscribe(opts) do |info,payload|
|
187
|
+
payload = ::Marshal.load(payload)
|
188
|
+
#p [info,info.reply_to,payload]
|
189
|
+
data2 = callback(info,payload)
|
190
|
+
payload2 = payload.merge :data => data2
|
191
|
+
if info.routing_key == @server_exchange.name
|
192
|
+
# addressed to the server's public
|
193
|
+
# queue, respond to the routing_key of
|
194
|
+
# the client's public queue.
|
195
|
+
key = info.reply_to
|
196
|
+
else
|
197
|
+
# addressed to the private queue
|
198
|
+
key = info.routing_key
|
356
199
|
end
|
200
|
+
MQ.direct(info.reply_to).publish(::Marshal.dump(payload2),:routing_key => key) if info.reply_to
|
201
|
+
info.ack if @ack
|
357
202
|
end
|
358
|
-
|
359
|
-
@futures.delete ready_future.message_id
|
360
|
-
return ready_future.data
|
361
|
-
end
|
362
|
-
|
363
|
-
def waitall
|
364
|
-
@futures.values.map { |k,v|
|
365
|
-
wait(v)
|
366
|
-
}
|
367
|
-
end
|
368
|
-
|
369
|
-
def inspect
|
370
|
-
"#<#{self.class} #{self.name}>"
|
203
|
+
self
|
371
204
|
end
|
372
205
|
end
|
373
206
|
|
374
|
-
# TODO should prolly have the option of using
|
375
|
-
# non auto-delete queues. This would be useful
|
376
|
-
# for logger. Maybe if a peeper name is given,
|
377
|
-
# then create queues with options.
|
378
207
|
class Peeper
|
379
|
-
|
380
|
-
|
381
|
-
def initialize(server_name,callback)
|
382
|
-
@server_name = server_name
|
383
|
-
@clients = {}
|
384
|
-
@callback = build_callback(callback)
|
385
|
-
|
386
|
-
uid = "#{@server_name}.peeper.#{rand 999_999_999_999}"
|
387
|
-
q = MQ.queue uid, :auto_delete => true
|
388
|
-
q.bind(@server_name) # messages to the server would be duplicated here.
|
389
|
-
q.subscribe { |info,payload|
|
390
|
-
payload = ::Marshal.load(payload)
|
391
|
-
# sets context, but doesn't make the call
|
392
|
-
obj = prepare_callback(@callback,info,payload)
|
393
|
-
# there is a specific method we want to call.
|
394
|
-
obj.server(payload[:method],payload[:data])
|
395
|
-
|
396
|
-
# bind to peep client message queue if we've not seen it before.
|
397
|
-
unless @clients.has_key? info.routing_key
|
398
|
-
@clients[info.routing_key] = true
|
399
|
-
client_q = MQ.queue "#{uid}--#{info.routing_key}",
|
400
|
-
:auto_delete => true
|
401
|
-
# messages to the client would be duplicated here.
|
402
|
-
client_q.bind("#{server_name}--", :routing_key => info.routing_key)
|
403
|
-
client_q.subscribe { |info,payload|
|
404
|
-
payload = ::Marshal.load(payload)
|
405
|
-
obj = prepare_callback(@callback,info,payload)
|
406
|
-
obj.client(payload[:method],payload[:data])
|
407
|
-
}
|
408
|
-
end
|
409
|
-
}
|
208
|
+
def initialize(exchange,callback)
|
209
|
+
# create a temporary queue that binds to an exchange
|
410
210
|
end
|
411
211
|
end
|
412
|
-
|
413
212
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hayeah-ASS
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Howard Yeh
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-08-
|
12
|
+
date: 2009-08-13 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: mq
|
17
17
|
type: :runtime
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -32,7 +32,6 @@ extra_rdoc_files:
|
|
32
32
|
- LICENSE
|
33
33
|
- README.textile
|
34
34
|
files:
|
35
|
-
- ASS.gemspec
|
36
35
|
- LICENSE
|
37
36
|
- README.textile
|
38
37
|
- Rakefile
|
data/ASS.gemspec
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
Gem::Specification.new do |s|
|
4
|
-
s.name = %q{ASS}
|
5
|
-
s.version = "0.0.2"
|
6
|
-
|
7
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
-
s.authors = ["Howard Yeh"]
|
9
|
-
s.date = %q{2009-08-17}
|
10
|
-
s.email = %q{hayeah@gmail.com}
|
11
|
-
s.extra_rdoc_files = [
|
12
|
-
"LICENSE",
|
13
|
-
"README.textile"
|
14
|
-
]
|
15
|
-
s.files = [
|
16
|
-
"ASS.gemspec",
|
17
|
-
"LICENSE",
|
18
|
-
"README.textile",
|
19
|
-
"Rakefile",
|
20
|
-
"VERSION.yml",
|
21
|
-
"lib/ass.rb",
|
22
|
-
"test/ass_test.rb",
|
23
|
-
"test/test_helper.rb"
|
24
|
-
]
|
25
|
-
s.has_rdoc = true
|
26
|
-
s.homepage = %q{http://github.com/hayeah/ass}
|
27
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
28
|
-
s.require_paths = ["lib"]
|
29
|
-
s.rubygems_version = %q{1.3.1}
|
30
|
-
s.summary = %q{Asynchronous Service Stages for Distributed Services}
|
31
|
-
s.test_files = [
|
32
|
-
"test/ass_test.rb",
|
33
|
-
"test/test_helper.rb"
|
34
|
-
]
|
35
|
-
|
36
|
-
if s.respond_to? :specification_version then
|
37
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
38
|
-
s.specification_version = 2
|
39
|
-
|
40
|
-
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
41
|
-
s.add_runtime_dependency(%q<amqp>, [">= 0"])
|
42
|
-
else
|
43
|
-
s.add_dependency(%q<amqp>, [">= 0"])
|
44
|
-
end
|
45
|
-
else
|
46
|
-
s.add_dependency(%q<amqp>, [">= 0"])
|
47
|
-
end
|
48
|
-
end
|