bones-rpc 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bones-rpc.gemspec +29 -0
- data/lib/bones-rpc.rb +2 -0
- data/lib/bones/rpc.rb +23 -0
- data/lib/bones/rpc/adapter.rb +49 -0
- data/lib/bones/rpc/adapter/base.rb +41 -0
- data/lib/bones/rpc/adapter/erlang.rb +28 -0
- data/lib/bones/rpc/adapter/json.rb +23 -0
- data/lib/bones/rpc/adapter/msgpack.rb +52 -0
- data/lib/bones/rpc/adapter/parser.rb +37 -0
- data/lib/bones/rpc/address.rb +167 -0
- data/lib/bones/rpc/cluster.rb +266 -0
- data/lib/bones/rpc/connection.rb +146 -0
- data/lib/bones/rpc/connection/reader.rb +49 -0
- data/lib/bones/rpc/connection/socket.rb +4 -0
- data/lib/bones/rpc/connection/socket/connectable.rb +196 -0
- data/lib/bones/rpc/connection/socket/ssl.rb +35 -0
- data/lib/bones/rpc/connection/socket/tcp.rb +28 -0
- data/lib/bones/rpc/connection/writer.rb +51 -0
- data/lib/bones/rpc/context.rb +48 -0
- data/lib/bones/rpc/errors.rb +33 -0
- data/lib/bones/rpc/failover.rb +38 -0
- data/lib/bones/rpc/failover/disconnect.rb +33 -0
- data/lib/bones/rpc/failover/ignore.rb +31 -0
- data/lib/bones/rpc/failover/retry.rb +39 -0
- data/lib/bones/rpc/future.rb +26 -0
- data/lib/bones/rpc/instrumentable.rb +41 -0
- data/lib/bones/rpc/instrumentable/log.rb +45 -0
- data/lib/bones/rpc/instrumentable/noop.rb +33 -0
- data/lib/bones/rpc/loggable.rb +112 -0
- data/lib/bones/rpc/node.rb +317 -0
- data/lib/bones/rpc/node/registry.rb +32 -0
- data/lib/bones/rpc/parser.rb +114 -0
- data/lib/bones/rpc/parser/buffer.rb +80 -0
- data/lib/bones/rpc/protocol.rb +106 -0
- data/lib/bones/rpc/protocol/acknowledge.rb +82 -0
- data/lib/bones/rpc/protocol/adapter_helper.rb +164 -0
- data/lib/bones/rpc/protocol/binary_helper.rb +431 -0
- data/lib/bones/rpc/protocol/ext_message.rb +86 -0
- data/lib/bones/rpc/protocol/notify.rb +38 -0
- data/lib/bones/rpc/protocol/request.rb +45 -0
- data/lib/bones/rpc/protocol/response.rb +58 -0
- data/lib/bones/rpc/protocol/synchronize.rb +70 -0
- data/lib/bones/rpc/read_preference.rb +43 -0
- data/lib/bones/rpc/read_preference/nearest.rb +57 -0
- data/lib/bones/rpc/read_preference/selectable.rb +81 -0
- data/lib/bones/rpc/readable.rb +57 -0
- data/lib/bones/rpc/session.rb +195 -0
- data/lib/bones/rpc/uri.rb +222 -0
- data/lib/bones/rpc/version.rb +6 -0
- metadata +198 -0
@@ -0,0 +1,431 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
# The base class for building all messages needed to implement the Bones
|
7
|
+
# RPC Protocol. It provides a minimal DSL for defining typed fields for
|
8
|
+
# serialization and deserialization over the wire.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# class KillCursors < Bones::RPC::Protocol::Message
|
13
|
+
# # header fields
|
14
|
+
# int32 :length
|
15
|
+
# int32 :request_id
|
16
|
+
# int32 :response_to
|
17
|
+
# int32 :op_code
|
18
|
+
#
|
19
|
+
# # message fields
|
20
|
+
# int32 :reserved
|
21
|
+
# int32 :number_of_cursors
|
22
|
+
# int64 :cursor_ids, type: :array
|
23
|
+
#
|
24
|
+
# # Customize field reader
|
25
|
+
# def number_of_cursors
|
26
|
+
# cursor_ids.length
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
module BinaryHelper
|
31
|
+
|
32
|
+
# Default implementation for a message is to do nothing when receiving
|
33
|
+
# replies.
|
34
|
+
#
|
35
|
+
# @example Receive replies.
|
36
|
+
# message.receive_replies(connection)
|
37
|
+
#
|
38
|
+
# @param [ Connection ] connection The connection.
|
39
|
+
#
|
40
|
+
# @return [ nil ] nil.
|
41
|
+
#
|
42
|
+
# @since 1.0.0
|
43
|
+
def receive_replies(connection); end
|
44
|
+
|
45
|
+
# Serializes the message and all of its fields to a new buffer or to the
|
46
|
+
# provided buffer.
|
47
|
+
#
|
48
|
+
# @example Serliaze the message.
|
49
|
+
# message.serialize
|
50
|
+
#
|
51
|
+
# @param [ String ] buffer A buffer to serialize to.
|
52
|
+
#
|
53
|
+
# @return [ String ] The result of serliazing this message
|
54
|
+
#
|
55
|
+
# @since 1.0.0
|
56
|
+
def serialize(buffer = "", adapter = nil)
|
57
|
+
raise NotImplementedError, "This method is generated after calling #finalize on a message class"
|
58
|
+
end
|
59
|
+
alias :to_s :serialize
|
60
|
+
|
61
|
+
# @return [String] the nicely formatted version of the message
|
62
|
+
def inspect
|
63
|
+
fields = self.class.fields.map do |field|
|
64
|
+
"@#{field}=" + __send__(field).inspect
|
65
|
+
end
|
66
|
+
"#<#{self.class.name}\n" <<
|
67
|
+
" #{fields * "\n "}>"
|
68
|
+
end
|
69
|
+
class << self
|
70
|
+
|
71
|
+
# Extends the including class with +ClassMethods+.
|
72
|
+
#
|
73
|
+
# @param [Class] subclass the inheriting class
|
74
|
+
def included(base)
|
75
|
+
super
|
76
|
+
base.extend(ClassMethods)
|
77
|
+
end
|
78
|
+
private :included
|
79
|
+
end
|
80
|
+
|
81
|
+
# Provides a DSL for defining struct-like fields for building messages
|
82
|
+
# for the Mongo Wire.
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# class Command
|
86
|
+
# extend Message::ClassMethods
|
87
|
+
#
|
88
|
+
# int32 :length
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# Command.fields # => [:length]
|
92
|
+
# command = Command.new
|
93
|
+
# command.length = 12
|
94
|
+
# command.serialize_length("") # => "\f\x00\x00\x00"
|
95
|
+
module ClassMethods
|
96
|
+
|
97
|
+
# @return [Array] the fields defined for this message
|
98
|
+
def fields
|
99
|
+
@fields ||= []
|
100
|
+
end
|
101
|
+
|
102
|
+
# Declare a binary field.
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# class Query < Message
|
106
|
+
# binary :collection
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# @param [String] name the name of this field
|
110
|
+
def binary(name)
|
111
|
+
attr_accessor name
|
112
|
+
|
113
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
114
|
+
def serialize_#{name}(buffer)
|
115
|
+
buffer << #{name}
|
116
|
+
end
|
117
|
+
RUBY
|
118
|
+
|
119
|
+
fields << name
|
120
|
+
end
|
121
|
+
|
122
|
+
# Declare a null terminated string field.
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# class Query < Message
|
126
|
+
# cstring :collection
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# @param [String] name the name of this field
|
130
|
+
def cstring(name)
|
131
|
+
attr_accessor name
|
132
|
+
|
133
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
134
|
+
def serialize_#{name}(buffer)
|
135
|
+
buffer << #{name}
|
136
|
+
buffer << 0
|
137
|
+
end
|
138
|
+
RUBY
|
139
|
+
|
140
|
+
fields << name
|
141
|
+
end
|
142
|
+
|
143
|
+
# Declare a BSON Document field.
|
144
|
+
#
|
145
|
+
# @example
|
146
|
+
# class Update < Message
|
147
|
+
# document :selector
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# @example optional document field
|
151
|
+
# class Query < Message
|
152
|
+
# document :selector
|
153
|
+
# document :fields, optional: true
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# @example array of documents
|
157
|
+
# class Reply < Message
|
158
|
+
# document :documents, type: :array
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# @param [String] name the name of this field
|
162
|
+
# @param [Hash] options the options for this field
|
163
|
+
# @option options [:array] :type specify an array of documents
|
164
|
+
# @option options [Boolean] :optional specify this field as optional
|
165
|
+
def document(name, options = {})
|
166
|
+
attr_accessor name
|
167
|
+
|
168
|
+
if options[:optional]
|
169
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
170
|
+
def serialize_#{name}(buffer)
|
171
|
+
buffer << #{name}.to_bson if #{name}
|
172
|
+
end
|
173
|
+
RUBY
|
174
|
+
elsif options[:type] == :array
|
175
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
176
|
+
def serialize_#{name}(buffer)
|
177
|
+
#{name}.each do |document|
|
178
|
+
buffer << document.to_bson
|
179
|
+
end
|
180
|
+
end
|
181
|
+
RUBY
|
182
|
+
else
|
183
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
184
|
+
def serialize_#{name}(buffer)
|
185
|
+
buffer << #{name}.to_bson
|
186
|
+
end
|
187
|
+
RUBY
|
188
|
+
end
|
189
|
+
|
190
|
+
fields << name
|
191
|
+
end
|
192
|
+
|
193
|
+
# Declare a flag field (32 bit signed integer)
|
194
|
+
#
|
195
|
+
# @example
|
196
|
+
# class Update < Message
|
197
|
+
# flags :flags, upsert: 2 ** 0,
|
198
|
+
# multi: 2 ** 1
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
# @param [String] name the name of this field
|
202
|
+
# @param [Hash{Symbol => Number}] flags the flags for this flag field
|
203
|
+
def flags(name, flag_map = {})
|
204
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
205
|
+
def #{name}
|
206
|
+
@#{name} ||= []
|
207
|
+
end
|
208
|
+
|
209
|
+
def #{name}=(flags)
|
210
|
+
if flags.is_a? Numeric
|
211
|
+
@#{name} = #{name}_from_int(flags)
|
212
|
+
else
|
213
|
+
@#{name} = flags
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def #{name}_as_int
|
218
|
+
bits = 0
|
219
|
+
flags = self.#{name}
|
220
|
+
#{flag_map.map { |flag, value| "bits |= #{value} if flags.include? #{flag.inspect}" }.join "\n"}
|
221
|
+
bits
|
222
|
+
end
|
223
|
+
|
224
|
+
def #{name}_from_int(bits)
|
225
|
+
flags = []
|
226
|
+
#{flag_map.map { |flag, value| "flags << #{flag.inspect} if #{value} & bits == #{value}" }.join "\n"}
|
227
|
+
flags
|
228
|
+
end
|
229
|
+
|
230
|
+
def serialize_#{name}(buffer)
|
231
|
+
buffer << [#{name}_as_int].pack('l<')
|
232
|
+
end
|
233
|
+
|
234
|
+
def deserialize_#{name}(buffer)
|
235
|
+
bits, = buffer.read(4).unpack('l<')
|
236
|
+
|
237
|
+
self.#{name} = bits
|
238
|
+
end
|
239
|
+
RUBY
|
240
|
+
|
241
|
+
fields << name
|
242
|
+
end
|
243
|
+
|
244
|
+
# Declare a 8 bit unsigned integer field.
|
245
|
+
#
|
246
|
+
# @example
|
247
|
+
# class Query < Message
|
248
|
+
# uint8 :length
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# @param [String] name the name of this field
|
252
|
+
def uint8(name)
|
253
|
+
attr_writer name
|
254
|
+
|
255
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
256
|
+
def #{name}
|
257
|
+
@#{name} ||= 0
|
258
|
+
end
|
259
|
+
|
260
|
+
def serialize_#{name}(buffer)
|
261
|
+
buffer << [#{name}].pack('C')
|
262
|
+
end
|
263
|
+
|
264
|
+
def deserialize_#{name}(buffer)
|
265
|
+
self.#{name}, = buffer.read(1).unpack('C')
|
266
|
+
end
|
267
|
+
RUBY
|
268
|
+
|
269
|
+
fields << name
|
270
|
+
end
|
271
|
+
|
272
|
+
# Declare a 8 bit signed integer field.
|
273
|
+
#
|
274
|
+
# @example
|
275
|
+
# class Query < Message
|
276
|
+
# int8 :length
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
# @param [String] name the name of this field
|
280
|
+
def int8(name)
|
281
|
+
attr_writer name
|
282
|
+
|
283
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
284
|
+
def #{name}
|
285
|
+
@#{name} ||= 0
|
286
|
+
end
|
287
|
+
|
288
|
+
def serialize_#{name}(buffer)
|
289
|
+
buffer << [#{name}].pack('c')
|
290
|
+
end
|
291
|
+
|
292
|
+
def deserialize_#{name}(buffer)
|
293
|
+
self.#{name}, = buffer.read(1).unpack('c')
|
294
|
+
end
|
295
|
+
RUBY
|
296
|
+
|
297
|
+
fields << name
|
298
|
+
end
|
299
|
+
|
300
|
+
# Declare a 32 bit signed integer field.
|
301
|
+
#
|
302
|
+
# @example
|
303
|
+
# class Query < Message
|
304
|
+
# int32 :length
|
305
|
+
# end
|
306
|
+
#
|
307
|
+
# @param [String] name the name of this field
|
308
|
+
def int32(name)
|
309
|
+
attr_writer name
|
310
|
+
|
311
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
312
|
+
def #{name}
|
313
|
+
@#{name} ||= 0
|
314
|
+
end
|
315
|
+
|
316
|
+
def serialize_#{name}(buffer)
|
317
|
+
buffer << [#{name}].pack('l<')
|
318
|
+
end
|
319
|
+
|
320
|
+
def deserialize_#{name}(buffer)
|
321
|
+
self.#{name}, = buffer.read(4).unpack('l<')
|
322
|
+
end
|
323
|
+
RUBY
|
324
|
+
|
325
|
+
fields << name
|
326
|
+
end
|
327
|
+
|
328
|
+
# Declare a 32 bit unsigned integer field.
|
329
|
+
#
|
330
|
+
# @example
|
331
|
+
# class Query < Message
|
332
|
+
# uint32 :length
|
333
|
+
# end
|
334
|
+
#
|
335
|
+
# @param [String] name the name of this field
|
336
|
+
def uint32(name)
|
337
|
+
attr_writer name
|
338
|
+
|
339
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
340
|
+
def #{name}
|
341
|
+
@#{name} ||= 0
|
342
|
+
end
|
343
|
+
|
344
|
+
def serialize_#{name}(buffer)
|
345
|
+
buffer << [#{name}].pack('N')
|
346
|
+
end
|
347
|
+
|
348
|
+
def deserialize_#{name}(buffer)
|
349
|
+
self.#{name}, = buffer.read(4).unpack('N')
|
350
|
+
end
|
351
|
+
RUBY
|
352
|
+
|
353
|
+
fields << name
|
354
|
+
end
|
355
|
+
|
356
|
+
# Declare a 64 bit signed integer field.
|
357
|
+
#
|
358
|
+
# @example
|
359
|
+
# class Query < Message
|
360
|
+
# int64 :cursor_id
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# @example with array type
|
364
|
+
# class KillCursors < Message
|
365
|
+
# int64 :cursor_ids, type: :array
|
366
|
+
# end
|
367
|
+
#
|
368
|
+
# @param [String] name the name of this field
|
369
|
+
# @param [Hash] options the options for this field
|
370
|
+
# @option options [:array] :type specify an array of 64 bit ints
|
371
|
+
def int64(name, options = {})
|
372
|
+
attr_writer name
|
373
|
+
|
374
|
+
if options[:type] == :array
|
375
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
376
|
+
def #{name}
|
377
|
+
@#{name} ||= []
|
378
|
+
end
|
379
|
+
|
380
|
+
def serialize_#{name}(buffer)
|
381
|
+
buffer << #{name}.pack('q<*')
|
382
|
+
end
|
383
|
+
|
384
|
+
def deserialize_#{name}(buffer)
|
385
|
+
raise NotImplementedError
|
386
|
+
end
|
387
|
+
RUBY
|
388
|
+
else
|
389
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
390
|
+
def #{name}
|
391
|
+
@#{name} ||= 0
|
392
|
+
end
|
393
|
+
|
394
|
+
def serialize_#{name}(buffer)
|
395
|
+
buffer << [#{name}].pack('q<')
|
396
|
+
end
|
397
|
+
|
398
|
+
def deserialize_#{name}(buffer)
|
399
|
+
self.#{name}, = buffer.read(8).unpack('q<')
|
400
|
+
end
|
401
|
+
RUBY
|
402
|
+
end
|
403
|
+
|
404
|
+
fields << name
|
405
|
+
end
|
406
|
+
|
407
|
+
# Declares the message class as complete, and defines its serialization
|
408
|
+
# method from the declared fields.
|
409
|
+
def finalize
|
410
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
411
|
+
def serialize(buffer = "", adapter = nil)
|
412
|
+
#{fields.map { |f| "serialize_#{f}(buffer)" }.join("\n")}
|
413
|
+
buffer
|
414
|
+
end
|
415
|
+
alias :to_s :serialize
|
416
|
+
EOS
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
420
|
+
|
421
|
+
# This ensures that subclasses of the primary wire message classes have
|
422
|
+
# identical fields.
|
423
|
+
def inherited(subclass)
|
424
|
+
super
|
425
|
+
subclass.fields.replace(fields)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
module Protocol
|
5
|
+
class ExtMessage
|
6
|
+
include BinaryHelper
|
7
|
+
|
8
|
+
uint8 :ext_code
|
9
|
+
binary :ext_length
|
10
|
+
int8 :ext_type
|
11
|
+
uint8 :ext_head
|
12
|
+
|
13
|
+
undef ext_code
|
14
|
+
undef ext_length
|
15
|
+
undef ext_type
|
16
|
+
undef serialize_ext_length
|
17
|
+
|
18
|
+
def ext_code
|
19
|
+
@ext_code ||= begin
|
20
|
+
len = datasize
|
21
|
+
if len <= 0xFF
|
22
|
+
0xC7
|
23
|
+
elsif len <= 0xFFFF
|
24
|
+
0xC8
|
25
|
+
elsif len <= 0xFFFFFFFF
|
26
|
+
0xC9
|
27
|
+
else
|
28
|
+
raise ArgumentError, "datasize too large: #{len} (max #{0xFFFFFFFF} bytes)"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ext_length
|
34
|
+
@ext_length ||= datasize + 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def ext_type
|
38
|
+
@ext_type ||= 0x0D
|
39
|
+
end
|
40
|
+
|
41
|
+
def deserialize_ext_length(buffer)
|
42
|
+
self.ext_length, = case ext_code
|
43
|
+
when 0xC7
|
44
|
+
buffer.read(1).unpack('C')
|
45
|
+
when 0xC8
|
46
|
+
buffer.read(2).unpack('n')
|
47
|
+
when 0xC9
|
48
|
+
buffer.read(4).unpack('N')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def serialize_ext_length(buffer)
|
53
|
+
packer = case ext_code
|
54
|
+
when 0xC7
|
55
|
+
'C'
|
56
|
+
when 0xC8
|
57
|
+
'n'
|
58
|
+
when 0xC9
|
59
|
+
'N'
|
60
|
+
end
|
61
|
+
buffer << [ext_length].pack(packer)
|
62
|
+
end
|
63
|
+
|
64
|
+
def data
|
65
|
+
(self.class.fields - ExtMessage.fields).inject("".force_encoding('BINARY')) do |buffer, field|
|
66
|
+
send("serialize_#{field}", buffer)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def datasize
|
71
|
+
data.bytesize
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.deserialize(buffer, adapter = nil)
|
75
|
+
message = allocate
|
76
|
+
message.deserialize_ext_code(buffer)
|
77
|
+
message.deserialize_ext_length(buffer)
|
78
|
+
message.deserialize_ext_type(buffer)
|
79
|
+
message.deserialize_ext_head(buffer)
|
80
|
+
message
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|