qrpc 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +11 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +8 -8
- data/README.md +31 -9
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/qrpc/client.rb +277 -0
- data/lib/qrpc/client/exception.rb +72 -0
- data/lib/qrpc/client/job.rb +150 -0
- data/lib/qrpc/general.rb +10 -0
- data/lib/qrpc/locator.rb +9 -6
- data/lib/qrpc/protocol/exception-data.rb +227 -0
- data/lib/qrpc/protocol/qrpc-object.rb +103 -0
- data/lib/qrpc/protocol/request.rb +46 -0
- data/lib/qrpc/server.rb +7 -10
- data/lib/qrpc/server/job.rb +9 -14
- data/qrpc.gemspec +17 -11
- data/test-client.rb +24 -0
- metadata +16 -38
data/CHANGES.txt
ADDED
data/Gemfile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
# Add dependencies required to use your gem here.
|
3
3
|
# Example:
|
4
|
-
gem "json-rpc-objects", ">= 0.1
|
4
|
+
gem "json-rpc-objects", ">= 0.3.1"
|
5
5
|
gem "depq", ">= 0.4"
|
6
|
-
gem "em-
|
6
|
+
gem "em-jack", ">= 0.1.3"
|
7
7
|
gem "eventmachine", ">= 0.12.10"
|
8
8
|
|
9
9
|
# Add dependencies to develop your gem here.
|
data/Gemfile.lock
CHANGED
@@ -1,26 +1,26 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
addressable (2.2.
|
4
|
+
addressable (2.2.4)
|
5
5
|
depq (0.4)
|
6
|
-
em-
|
6
|
+
em-jack (0.1.3)
|
7
7
|
eventmachine
|
8
8
|
eventmachine (0.12.10)
|
9
9
|
git (1.2.5)
|
10
|
-
hash-utils (0.
|
10
|
+
hash-utils (0.4.1)
|
11
11
|
jeweler (1.5.2)
|
12
12
|
bundler (~> 1.0.0)
|
13
13
|
git (>= 1.2.5)
|
14
14
|
rake
|
15
|
-
json-rpc-objects (0.1
|
15
|
+
json-rpc-objects (0.3.1)
|
16
16
|
addressable (>= 2.2.2)
|
17
17
|
hash-utils (>= 0.3.0)
|
18
18
|
multitype-introspection (>= 0.1.0)
|
19
19
|
types (>= 0.1.0)
|
20
20
|
yajl-ruby (>= 0.7.8)
|
21
|
-
multitype-introspection (0.1.
|
21
|
+
multitype-introspection (0.1.2)
|
22
22
|
rake (0.8.7)
|
23
|
-
types (0.1.
|
23
|
+
types (0.1.2)
|
24
24
|
multitype-introspection (>= 0.1.0)
|
25
25
|
yajl-ruby (0.7.9)
|
26
26
|
|
@@ -30,7 +30,7 @@ PLATFORMS
|
|
30
30
|
DEPENDENCIES
|
31
31
|
bundler (~> 1.0.0)
|
32
32
|
depq (>= 0.4)
|
33
|
-
em-
|
33
|
+
em-jack (>= 0.1.3)
|
34
34
|
eventmachine (>= 0.12.10)
|
35
35
|
jeweler (~> 1.5.2)
|
36
|
-
json-rpc-objects (>= 0.1
|
36
|
+
json-rpc-objects (>= 0.3.1)
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
QRPC
|
2
2
|
====
|
3
3
|
|
4
|
-
**QRPC** currently implements queued JSON-RPC
|
5
|
-
normal RPC server, but through queue interface, so allows
|
6
|
-
scalable, distributed and asynchronous remote API implementation
|
7
|
-
fast data processing.
|
4
|
+
**QRPC** currently implements queued JSON-RPC both client and server
|
5
|
+
which works as normal RPC server, but through queue interface, so allows
|
6
|
+
highly scalable, distributed and asynchronous remote API implementation
|
7
|
+
and fast data processing.
|
8
8
|
|
9
9
|
It's based on [eventmachine][1] and [beanstalkd][2] so it's fast and
|
10
10
|
thread safe.
|
@@ -55,7 +55,7 @@ And in case of exception:
|
|
55
55
|
"backtrace" => <array of Base64 encoded strings>,
|
56
56
|
"dump" => {
|
57
57
|
"raw" => <Base 54 encoded marshaled exception object>,
|
58
|
-
"format" => "
|
58
|
+
"format" => "ruby"
|
59
59
|
}
|
60
60
|
}
|
61
61
|
},
|
@@ -69,7 +69,7 @@ And in case of exception:
|
|
69
69
|
Both `backtrace` and `dump` members are optional.
|
70
70
|
|
71
71
|
|
72
|
-
### Usage
|
72
|
+
### Server Usage
|
73
73
|
|
74
74
|
Usage is simple. Look example:
|
75
75
|
|
@@ -81,8 +81,8 @@ Usage is simple. Look example:
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
server = QRPC::Server::new
|
85
|
-
server.listen! QRPC::Locator::new
|
84
|
+
server = QRPC::Server::new Foo::new
|
85
|
+
server.listen! QRPC::Locator::new "test"
|
86
86
|
|
87
87
|
This creates an instance of `Foo` which will serve as API, creates
|
88
88
|
locator of the queue *test* at default server *localhost:11300*. Queue
|
@@ -98,7 +98,29 @@ or `#start_listening`.
|
|
98
98
|
|
99
99
|
Reponse will be put to the same queue server, to queue named
|
100
100
|
`qrpc-<client identifier>-output`, with structure described above.
|
101
|
-
|
101
|
+
|
102
|
+
### Client Usage
|
103
|
+
|
104
|
+
Client usage is simple too. Look example:
|
105
|
+
|
106
|
+
require "eventmachine"
|
107
|
+
require "qrpc/client"
|
108
|
+
|
109
|
+
EM::run do
|
110
|
+
client = QRPC::Client::new QRPC::Locator::new "test"
|
111
|
+
client.subtract(2, 3) { |result| puts result } # prints out -1
|
112
|
+
end
|
113
|
+
|
114
|
+
This connects to the *test* queue at default server *localhost:11300*,
|
115
|
+
puts request to the real queue name *qrpc-test-input* and waits and then
|
116
|
+
prints the result from `qrpc-<client-id>-output` queue. In case of
|
117
|
+
multiple requests should be noted, results can arrive in any order
|
118
|
+
because, of sure, QRPC is pseudo-fibered and asynchronous from
|
119
|
+
its principle.
|
120
|
+
|
121
|
+
Client is implemented as [evented][1] too, but in case of need you can
|
122
|
+
implement another one with non-evented interface using whatever
|
123
|
+
[beanstalkd][2] client you like of sure.
|
102
124
|
|
103
125
|
Contributing
|
104
126
|
------------
|
data/Rakefile
CHANGED
@@ -16,7 +16,7 @@ Jeweler::Tasks.new do |gem|
|
|
16
16
|
gem.name = "qrpc"
|
17
17
|
gem.homepage = "http://github.com/martinkozak/qrpc"
|
18
18
|
gem.license = "MIT"
|
19
|
-
gem.summary = 'Queued JSON-RPC server. Works as normal RPC server, but through queue interface, so allows highly scalable, distributed and asynchronous remote API implementation and fast data processing. It\'s based on eventmachine and beanstalkd, so it\'s fast and thread safe.'
|
19
|
+
gem.summary = 'Queued JSON-RPC client and server. Works as normal RPC server, but through queue interface, so allows highly scalable, distributed and asynchronous remote API implementation and fast data processing. It\'s based on eventmachine and beanstalkd, so it\'s fast and thread safe.'
|
20
20
|
gem.email = "martinkozak@martinkozak.net"
|
21
21
|
gem.authors = ["Martin Kozák"]
|
22
22
|
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/qrpc/client.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "em-jack"
|
3
|
+
require "uuid"
|
4
|
+
require "qrpc/general"
|
5
|
+
require "qrpc/client/job"
|
6
|
+
require "json-rpc-objects/response"
|
7
|
+
|
8
|
+
##
|
9
|
+
# General QRPC module.
|
10
|
+
#
|
11
|
+
|
12
|
+
module QRPC
|
13
|
+
|
14
|
+
##
|
15
|
+
# Queue RPC client.
|
16
|
+
# @since 0.2.0
|
17
|
+
#
|
18
|
+
|
19
|
+
class Client
|
20
|
+
|
21
|
+
##
|
22
|
+
# Holds locator of the target queue.
|
23
|
+
#
|
24
|
+
|
25
|
+
@locator
|
26
|
+
|
27
|
+
##
|
28
|
+
# Holds client session ID.
|
29
|
+
#
|
30
|
+
|
31
|
+
@id
|
32
|
+
|
33
|
+
##
|
34
|
+
# Holds input queue name.
|
35
|
+
#
|
36
|
+
|
37
|
+
@input_name
|
38
|
+
|
39
|
+
##
|
40
|
+
# Holds input queue instance.
|
41
|
+
#
|
42
|
+
|
43
|
+
@input_queue
|
44
|
+
|
45
|
+
##
|
46
|
+
# Holds output queue name.
|
47
|
+
#
|
48
|
+
|
49
|
+
@output_name
|
50
|
+
|
51
|
+
##
|
52
|
+
# Holds output queue instance.
|
53
|
+
#
|
54
|
+
|
55
|
+
@output_queue
|
56
|
+
|
57
|
+
##
|
58
|
+
# Indicates, results pooling is ran.
|
59
|
+
#
|
60
|
+
|
61
|
+
@pooling
|
62
|
+
|
63
|
+
##
|
64
|
+
# Holds clients for finalizing.
|
65
|
+
#
|
66
|
+
|
67
|
+
@@clients = { }
|
68
|
+
|
69
|
+
|
70
|
+
##
|
71
|
+
# Constructor.
|
72
|
+
# @param [QRPC::Locator] locator of the output queue
|
73
|
+
#
|
74
|
+
|
75
|
+
def initialize(locator)
|
76
|
+
@locator = locator
|
77
|
+
@pooling = false
|
78
|
+
@jobs = { }
|
79
|
+
|
80
|
+
# Destructor
|
81
|
+
ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
|
82
|
+
@@clients[self.object_id] = self
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Finalizer handler.
|
87
|
+
# @param [Integer] id id of finalized instance
|
88
|
+
#
|
89
|
+
|
90
|
+
def self.finalize(id)
|
91
|
+
if @@clients.has_key? id
|
92
|
+
@@clients[id].finalize!
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Destructor.
|
98
|
+
#
|
99
|
+
|
100
|
+
def finalize!
|
101
|
+
if not @input_queue.nil?
|
102
|
+
@input_queue.watch("default") do
|
103
|
+
@input_queue.ignore(@input_name.to_s) do
|
104
|
+
@input_queue.close
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if not @output_queue.nil?
|
110
|
+
@output_queue.use("default") do
|
111
|
+
@output_queue.close
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Handles call to RPC. (*********)
|
118
|
+
#
|
119
|
+
# Be warn, arguments will be serialized to JSON, so they should
|
120
|
+
# be serializable nativelly or implement +#to_json+ method.
|
121
|
+
#
|
122
|
+
# @param [Symbol] name name of the called methods
|
123
|
+
# @param [Array] args arguments of the called methods
|
124
|
+
# @param [Proc] block callback for returning result
|
125
|
+
#
|
126
|
+
|
127
|
+
def method_missing(name, *args, &block)
|
128
|
+
self.put(self.create_job(name, args, &block))
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Creates job associated to this client session.
|
133
|
+
#
|
134
|
+
# @param [Symbol] name name of the method of the job
|
135
|
+
# @param [Array] args arguments of the method call
|
136
|
+
# @param [Integer] priority job priority
|
137
|
+
# @param [Proc] block result returning callback
|
138
|
+
# @return [QRPC::Client::Job] new job
|
139
|
+
#
|
140
|
+
|
141
|
+
def create_job(name, args, priority = QRPC::DEFAULT_PRIORITY, &block)
|
142
|
+
Client::Job::new(self.id, name, args, priority, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Puts job to client.
|
147
|
+
# @param [QRPC::Client::Job] job job for put to output queue
|
148
|
+
|
149
|
+
def put(job)
|
150
|
+
if not job.notification?
|
151
|
+
@jobs[job.id] = job
|
152
|
+
end
|
153
|
+
|
154
|
+
self.output_queue do |queue|
|
155
|
+
queue.put(job.to_json)
|
156
|
+
end
|
157
|
+
|
158
|
+
if (not @pooling) and (@jobs.length > 0)
|
159
|
+
self.pool!
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Starts input (results) pooling.
|
165
|
+
#
|
166
|
+
|
167
|
+
def pool!
|
168
|
+
|
169
|
+
# Results processing logic
|
170
|
+
processor = Proc::new do |job|
|
171
|
+
response = JsonRpcObjects::Response::parse(job.body)
|
172
|
+
id = response.id.to_sym
|
173
|
+
job.delete()
|
174
|
+
|
175
|
+
if @jobs.include? id
|
176
|
+
@jobs[id].assign_result(response)
|
177
|
+
end
|
178
|
+
|
179
|
+
@jobs.delete(id)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Runs processor for each job
|
183
|
+
parent = self
|
184
|
+
worker = EM.spawn do
|
185
|
+
parent.input_queue { |q| q.each_job(&processor) }
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
|
190
|
+
worker.run
|
191
|
+
@pooling = true
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Returns input name.
|
197
|
+
# @return [Symbol] input queue name
|
198
|
+
#
|
199
|
+
|
200
|
+
def input_name
|
201
|
+
if @input_name.nil?
|
202
|
+
@input_name = (QRPC::QUEUE_PREFIX.dup << "-" << self.id.to_s << "-" << QRPC::QUEUE_POSTFIX_OUTPUT).to_sym
|
203
|
+
end
|
204
|
+
|
205
|
+
return @input_name
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# Returns input queue.
|
210
|
+
# @param [Proc] block block to which will be input queue given
|
211
|
+
#
|
212
|
+
|
213
|
+
def input_queue(&block)
|
214
|
+
if @input_queue.nil?
|
215
|
+
@input_queue = EMJack::Connection::new(:host => @locator.host, :port => @locator.port)
|
216
|
+
@input_queue.watch(self.input_name.to_s) do
|
217
|
+
@input_queue.ignore("default") do
|
218
|
+
|
219
|
+
# Results pooler error handler
|
220
|
+
@input_queue.on_error do |error|
|
221
|
+
raise Exception::new("Beanstalk error: " << error.to_s)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Returns
|
225
|
+
block.call(@input_queue)
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
229
|
+
else
|
230
|
+
block.call(@input_queue)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Returns output name.
|
236
|
+
# @return [Symbol] output queue name
|
237
|
+
#
|
238
|
+
|
239
|
+
def output_name
|
240
|
+
if @output_name.nil?
|
241
|
+
@output_name = (QRPC::QUEUE_PREFIX.dup << "-" << @locator.queue << "-" << QRPC::QUEUE_POSTFIX_INPUT).to_sym
|
242
|
+
end
|
243
|
+
|
244
|
+
return @output_name
|
245
|
+
end
|
246
|
+
|
247
|
+
##
|
248
|
+
# Returns output queue.
|
249
|
+
# @param [Proc] block block to which will be output queue given
|
250
|
+
#
|
251
|
+
|
252
|
+
def output_queue(&block)
|
253
|
+
if @output_queue.nil?
|
254
|
+
@output_queue = EMJack::Connection::new(:host => @locator.host, :port => @locator.port)
|
255
|
+
@output_queue.use(self.output_name.to_s) do
|
256
|
+
block.call(@output_queue)
|
257
|
+
end
|
258
|
+
else
|
259
|
+
block.call(@output_queue)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Returns client (or maybe session is better) ID.
|
265
|
+
# @return [Symbol] client (session) ID
|
266
|
+
#
|
267
|
+
|
268
|
+
def id
|
269
|
+
if @id.nil?
|
270
|
+
@id = UUID.generate.to_sym
|
271
|
+
end
|
272
|
+
|
273
|
+
return @id
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "qrpc/protocol/exception-data"
|
3
|
+
|
4
|
+
##
|
5
|
+
# General QRPC module.
|
6
|
+
#
|
7
|
+
|
8
|
+
module QRPC
|
9
|
+
|
10
|
+
##
|
11
|
+
# Queue RPC client.
|
12
|
+
#
|
13
|
+
|
14
|
+
class Client
|
15
|
+
|
16
|
+
##
|
17
|
+
# Queue RPC client exception.
|
18
|
+
# @since 0.2.0
|
19
|
+
#
|
20
|
+
|
21
|
+
class Exception < ::Exception
|
22
|
+
|
23
|
+
##
|
24
|
+
# Gets one from protocol exception object.
|
25
|
+
# Returns marshalled original or reconstructed version.
|
26
|
+
#
|
27
|
+
# @param [JsonRpcObjects::Generic::Error] proto JSON-RPC error object
|
28
|
+
# @return [Exception, QRPC::Client::Exception] exception according
|
29
|
+
# to sucessfullness of the unmarshaling
|
30
|
+
#
|
31
|
+
|
32
|
+
def self.get(proto)
|
33
|
+
|
34
|
+
# Converts protocol exception to exception data object
|
35
|
+
proto = QRPC::Protocol::ExceptionData::new(proto.data)
|
36
|
+
|
37
|
+
# Tries to unmarshall
|
38
|
+
if proto.dump.format == :ruby
|
39
|
+
begin
|
40
|
+
exception = Marshal.load(Base64.decode64(proto.dump.raw))
|
41
|
+
rescue
|
42
|
+
# pass
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# If unsuccessfull, creates from data
|
47
|
+
if exception.nil?
|
48
|
+
exception = self::new(proto)
|
49
|
+
end
|
50
|
+
|
51
|
+
return exception
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Constructor.
|
56
|
+
# Initializes from protocol exception data object.
|
57
|
+
#
|
58
|
+
# @param [QRPC::Protocol::ExceptionData] data exception data object
|
59
|
+
#
|
60
|
+
|
61
|
+
def initialize(data)
|
62
|
+
message = data.name.dup << ": " << data.message
|
63
|
+
backtrace = data.backtrace.map { |i| Base64.decode64(i) }
|
64
|
+
backtrace.reject! { |i| i.empty? }
|
65
|
+
|
66
|
+
super(message)
|
67
|
+
self.set_backtrace(backtrace)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|