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 ADDED
@@ -0,0 +1,11 @@
1
+
2
+ 0.2.0 (2011-02-04)
3
+ * client implemented
4
+ * switch to em-jack
5
+
6
+ 0.1.1 (2011-01-29)
7
+ * small unimportant bug while destroying corrected
8
+ * some constants move between modules
9
+
10
+ 0.1.0 (2011-01-18)
11
+ * initial version (implemented server only)
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.2"
4
+ gem "json-rpc-objects", ">= 0.3.1"
5
5
  gem "depq", ">= 0.4"
6
- gem "em-beanstalk", ">= 0.0.10"
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.2)
4
+ addressable (2.2.4)
5
5
  depq (0.4)
6
- em-beanstalk (0.0.10)
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.3.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.3)
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.0)
21
+ multitype-introspection (0.1.2)
22
22
  rake (0.8.7)
23
- types (0.1.0)
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-beanstalk (>= 0.0.10)
33
+ em-jack (>= 0.1.3)
34
34
  eventmachine (>= 0.12.10)
35
35
  jeweler (~> 1.5.2)
36
- json-rpc-objects (>= 0.1.2)
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 server which works as
5
- normal RPC server, but through queue interface, so allows highly
6
- scalable, distributed and asynchronous remote API implementation and
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" => "Ruby"
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(Foo::new)
85
- server.listen! QRPC::Locator::new("test")
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
- Client isn't implemented at this time.
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.1
1
+ 0.2.0
@@ -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