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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/bones-rpc.gemspec +29 -0
  10. data/lib/bones-rpc.rb +2 -0
  11. data/lib/bones/rpc.rb +23 -0
  12. data/lib/bones/rpc/adapter.rb +49 -0
  13. data/lib/bones/rpc/adapter/base.rb +41 -0
  14. data/lib/bones/rpc/adapter/erlang.rb +28 -0
  15. data/lib/bones/rpc/adapter/json.rb +23 -0
  16. data/lib/bones/rpc/adapter/msgpack.rb +52 -0
  17. data/lib/bones/rpc/adapter/parser.rb +37 -0
  18. data/lib/bones/rpc/address.rb +167 -0
  19. data/lib/bones/rpc/cluster.rb +266 -0
  20. data/lib/bones/rpc/connection.rb +146 -0
  21. data/lib/bones/rpc/connection/reader.rb +49 -0
  22. data/lib/bones/rpc/connection/socket.rb +4 -0
  23. data/lib/bones/rpc/connection/socket/connectable.rb +196 -0
  24. data/lib/bones/rpc/connection/socket/ssl.rb +35 -0
  25. data/lib/bones/rpc/connection/socket/tcp.rb +28 -0
  26. data/lib/bones/rpc/connection/writer.rb +51 -0
  27. data/lib/bones/rpc/context.rb +48 -0
  28. data/lib/bones/rpc/errors.rb +33 -0
  29. data/lib/bones/rpc/failover.rb +38 -0
  30. data/lib/bones/rpc/failover/disconnect.rb +33 -0
  31. data/lib/bones/rpc/failover/ignore.rb +31 -0
  32. data/lib/bones/rpc/failover/retry.rb +39 -0
  33. data/lib/bones/rpc/future.rb +26 -0
  34. data/lib/bones/rpc/instrumentable.rb +41 -0
  35. data/lib/bones/rpc/instrumentable/log.rb +45 -0
  36. data/lib/bones/rpc/instrumentable/noop.rb +33 -0
  37. data/lib/bones/rpc/loggable.rb +112 -0
  38. data/lib/bones/rpc/node.rb +317 -0
  39. data/lib/bones/rpc/node/registry.rb +32 -0
  40. data/lib/bones/rpc/parser.rb +114 -0
  41. data/lib/bones/rpc/parser/buffer.rb +80 -0
  42. data/lib/bones/rpc/protocol.rb +106 -0
  43. data/lib/bones/rpc/protocol/acknowledge.rb +82 -0
  44. data/lib/bones/rpc/protocol/adapter_helper.rb +164 -0
  45. data/lib/bones/rpc/protocol/binary_helper.rb +431 -0
  46. data/lib/bones/rpc/protocol/ext_message.rb +86 -0
  47. data/lib/bones/rpc/protocol/notify.rb +38 -0
  48. data/lib/bones/rpc/protocol/request.rb +45 -0
  49. data/lib/bones/rpc/protocol/response.rb +58 -0
  50. data/lib/bones/rpc/protocol/synchronize.rb +70 -0
  51. data/lib/bones/rpc/read_preference.rb +43 -0
  52. data/lib/bones/rpc/read_preference/nearest.rb +57 -0
  53. data/lib/bones/rpc/read_preference/selectable.rb +81 -0
  54. data/lib/bones/rpc/readable.rb +57 -0
  55. data/lib/bones/rpc/session.rb +195 -0
  56. data/lib/bones/rpc/uri.rb +222 -0
  57. data/lib/bones/rpc/version.rb +6 -0
  58. 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