bones-rpc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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