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,32 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Node
5
+ class Registry
6
+
7
+ def initialize
8
+ @registry = {}
9
+ end
10
+
11
+ def flush(exception = Errors::ConnectionFailure.new("Socket closed"))
12
+ return true if @registry.empty?
13
+ @registry.each do |channel, futures|
14
+ futures.each do |id, future|
15
+ future.signal(FutureValue.new(exception)) rescue nil
16
+ end
17
+ end
18
+ @registry.clear
19
+ end
20
+
21
+ def get(channel, id)
22
+ (@registry[channel] ||= {}).delete(id)
23
+ end
24
+
25
+ def set(channel, id, future)
26
+ (@registry[channel] ||= {})[id] = future
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require 'bones/rpc/parser/buffer'
3
+
4
+ module Bones
5
+ module RPC
6
+ class Parser
7
+
8
+ EXT8 = [0xC7].pack('C').freeze
9
+ EXT16 = [0xC8].pack('C').freeze
10
+ EXT32 = [0xC9].pack('C').freeze
11
+
12
+ attr_reader :stream, :adapter
13
+
14
+ def buffer
15
+ @buffer ||= Bones::RPC::Parser::Buffer.new(@stream)
16
+ end
17
+
18
+ def initialize(stream, adapter)
19
+ @stream = stream.force_encoding('BINARY')
20
+ @adapter = Adapter.get(adapter)
21
+ end
22
+
23
+ def parser
24
+ @parser ||= @adapter.parser(@stream)
25
+ end
26
+
27
+ def read
28
+ sync do
29
+ b = buffer.getc
30
+ buffer.ungetc(b)
31
+ if b.nil?
32
+ raise EOFError
33
+ elsif b.start_with?(EXT8)
34
+ buffer.skip(1)
35
+ len, = buffer.read(1).unpack('C')
36
+ type, = buffer.read(1).unpack('C')
37
+ check_ext!(type)
38
+ head, = buffer.read(1).unpack('C')
39
+ data = buffer.read(len-1)
40
+ parse_ext!(head, data)
41
+ elsif b.start_with?(EXT16)
42
+ buffer.skip(1)
43
+ len, = buffer.read(2).unpack('n')
44
+ type, = buffer.read(1).unpack('C')
45
+ check_ext!(type)
46
+ head, = buffer.read(1).unpack('C')
47
+ data = buffer.read(len-1)
48
+ parse_ext!(head, data)
49
+ elsif b.start_with?(EXT32)
50
+ buffer.skip(1)
51
+ len, = buffer.read(4).unpack('N')
52
+ type, = buffer.read(1).unpack('C')
53
+ check_ext!(type)
54
+ head, = buffer.read(1).unpack('C')
55
+ data = buffer.read(len-1)
56
+ parse_ext!(head, data)
57
+ else
58
+ object = parser.read
59
+ buffer.seek(parser.unpacker_pos)
60
+ map_from!(object)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def check_ext!(type)
68
+ raise(Errors::InvalidExtMessage, "bad ext message received of type #{type.inspect} (should be #{0x0D.inspect})") unless valid_ext?(type)
69
+ end
70
+
71
+ def map_from!(object)
72
+ case object
73
+ when Array
74
+ if (3..4).include?(object.size)
75
+ case object.first
76
+ when 0
77
+ Protocol::Request.map_from(object)
78
+ when 1
79
+ Protocol::Response.map_from(object)
80
+ when 2
81
+ Protocol::Notify.map_from(object)
82
+ else
83
+ object
84
+ end
85
+ end
86
+ else
87
+ object
88
+ end
89
+ end
90
+
91
+ def parse_ext!(head, data)
92
+ message = Protocol.get_by_ext_head(head)
93
+ if message
94
+ message.unpack(data)
95
+ else
96
+ map_from!(Adapter.get_by_ext_head(head).unpack(data))
97
+ end
98
+ end
99
+
100
+ def sync
101
+ buffer.transaction do
102
+ yield
103
+ end
104
+ ensure
105
+ parser.unpacker_seek(buffer.pos) if parser.unpacker_pos != buffer.pos
106
+ end
107
+
108
+ def valid_ext?(type)
109
+ type == 0x0D
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Parser
5
+ class Buffer
6
+
7
+ attr_reader :io
8
+
9
+ def initialize(data)
10
+ @io = StringIO.new(data)
11
+ end
12
+
13
+ def getc
14
+ io.getc
15
+ end
16
+
17
+ def pos
18
+ io.pos
19
+ end
20
+
21
+ def read(n)
22
+ i = pos
23
+ data = io.read(n)
24
+ if data.bytesize < n
25
+ seek(i)
26
+ raise EOFError
27
+ else
28
+ data
29
+ end
30
+ end
31
+
32
+ def rewind
33
+ io.rewind
34
+ end
35
+
36
+ def seek(pos)
37
+ io.seek(pos)
38
+ end
39
+
40
+ def size
41
+ io.size
42
+ end
43
+
44
+ def skip(n)
45
+ seek(pos + n)
46
+ end
47
+
48
+ def sync(*others)
49
+ yield
50
+ ensure
51
+ others.each { |other| other.seek(pos) }
52
+ end
53
+
54
+ def to_str
55
+ i = pos
56
+ begin
57
+ io.read || ""
58
+ ensure
59
+ seek(i)
60
+ end
61
+ end
62
+
63
+ def transaction
64
+ i = pos
65
+ begin
66
+ yield
67
+ rescue EOFError => e
68
+ seek(i)
69
+ raise e
70
+ end
71
+ end
72
+
73
+ def ungetc(c)
74
+ io.ungetc(c)
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,106 @@
1
+ module Bones #:nodoc:
2
+ module RPC
3
+
4
+ # The +Bones::RPC::Protocol+ namespace contains convenience classes for
5
+ # building all of the possible messages defined in the Bones RPC Protocol.
6
+ module Protocol
7
+ extend self
8
+
9
+ def get_by_ext_head(head)
10
+ ext_heads[head]
11
+ end
12
+
13
+ def register_ext_head(message, head)
14
+ ext_heads[head] ||= message
15
+ return message
16
+ end
17
+
18
+ private
19
+
20
+ def ext_heads
21
+ @ext_heads ||= {}
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ require 'bones/rpc/protocol/adapter_helper'
28
+ require 'bones/rpc/protocol/binary_helper'
29
+
30
+ require 'bones/rpc/protocol/ext_message'
31
+ require 'bones/rpc/protocol/acknowledge'
32
+ require 'bones/rpc/protocol/synchronize'
33
+
34
+ require 'bones/rpc/protocol/notify'
35
+ require 'bones/rpc/protocol/request'
36
+ require 'bones/rpc/protocol/response'
37
+
38
+ module Bones
39
+ module RPC
40
+ module Protocol
41
+
42
+ def deserialize(buffer, adapter = nil)
43
+ char = buffer.getc
44
+ buffer.ungetc(char)
45
+ if sub = MAP[char]
46
+ sub.deserialize(buffer, adapter)
47
+ elsif adapter
48
+ Adapter.get(adapter).deserialize(buffer)
49
+ else
50
+ raise NotImplementedError, "Unknown data received: #{char.inspect}"
51
+ end
52
+ end
53
+
54
+ module MessagePackExtended
55
+ extend self
56
+
57
+ def deserialize(buffer, adapter = nil)
58
+ ext8 = buffer.getc
59
+ len = buffer.getc
60
+ type = buffer.getc
61
+ buffer.ungetc(type)
62
+ buffer.ungetc(len)
63
+ buffer.ungetc(ext8)
64
+ if sub = MAP[type]
65
+ sub.deserialize(buffer, adapter)
66
+ else
67
+ raise NotImplementedError, "Unknown MessagePackExtended data received: {ext8: #{ext8.inspect}, len: #{len.inspect}, type: #{type.inspect}}"
68
+ end
69
+ end
70
+
71
+ module BonesRPC
72
+ extend self
73
+
74
+ def deserialize(buffer, adapter = nil)
75
+ ext8 = buffer.getc
76
+ len = buffer.getc
77
+ type = buffer.getc
78
+ head = buffer.getc
79
+ buffer.ungetc(head)
80
+ buffer.ungetc(type)
81
+ buffer.ungetc(len)
82
+ buffer.ungetc(ext8)
83
+ if sub = MAP[head]
84
+ sub.deserialize(buffer, adapter)
85
+ else
86
+ raise NotImplementedError, "Unknown BonesRPC data received: {ext8: #{ext8.inspect}, len: #{len.inspect}, type: #{type.inspect}, head: #{head.inspect}}"
87
+ end
88
+ end
89
+
90
+ MAP = {
91
+ [0].pack('C').freeze => Synchronize,
92
+ [1].pack('C').freeze => Acknowledge
93
+ }.freeze
94
+ end
95
+
96
+ MAP = {
97
+ [0x0d].pack('C').freeze => BonesRPC
98
+ }.freeze
99
+ end
100
+
101
+ MAP = {
102
+ [0xc7].pack('C').freeze => MessagePackExtended
103
+ }.freeze
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ module Protocol
5
+ class Acknowledge < ExtMessage
6
+
7
+ uint32 :id
8
+ uint8 :ready
9
+
10
+ finalize
11
+
12
+ def initialize(id, ready)
13
+ self.id = id
14
+ self.ready = ready
15
+ end
16
+
17
+ undef ext_head
18
+ undef ready
19
+ undef :ready=
20
+ undef serialize_ready
21
+
22
+ def ext_head
23
+ 1
24
+ end
25
+
26
+ def ready
27
+ @ready
28
+ end
29
+
30
+ def ready=(val)
31
+ @ready = case val
32
+ when 0xC2
33
+ false
34
+ when 0xC3
35
+ true
36
+ else
37
+ !!val
38
+ end
39
+ end
40
+
41
+ def serialize_ready(buffer)
42
+ buffer << [ready ? 0xC3 : 0xC2].pack('C')
43
+ end
44
+
45
+ def log_inspect
46
+ type = "ACKNOWLEDGE"
47
+ fields = []
48
+ fields << ["%-12s", type]
49
+ fields << ["id=%s", id]
50
+ fields << ["ready=%s", ready]
51
+ f, v = fields.transpose
52
+ f.join(" ") % v
53
+ end
54
+
55
+ def self.deserialize(buffer, adapter = nil)
56
+ message = super
57
+ message.deserialize_id(buffer)
58
+ message.deserialize_ready(buffer)
59
+ message
60
+ end
61
+
62
+ def self.unpack(data)
63
+ buffer = StringIO.new(data)
64
+ id, = buffer.read(4).unpack('N')
65
+ ready = buffer.read(1)
66
+ new(id, ready)
67
+ end
68
+
69
+ def get(node)
70
+ node.detach(:synack, id)
71
+ end
72
+
73
+ def signal(future)
74
+ future.signal(FutureValue.new(self))
75
+ end
76
+
77
+ Protocol.register_ext_head self, 1
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,164 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ module Protocol
5
+
6
+ module AdapterHelper
7
+
8
+ # Default implementation for a message is to do nothing when receiving
9
+ # replies.
10
+ #
11
+ # @example Receive replies.
12
+ # message.receive_replies(connection)
13
+ #
14
+ # @param [ Connection ] connection The connection.
15
+ #
16
+ # @return [ nil ] nil.
17
+ #
18
+ # @since 1.0.0
19
+ def receive_replies(connection); end
20
+
21
+ # Serializes the message and all of its fields to a new buffer or to the
22
+ # provided buffer.
23
+ #
24
+ # @example Serliaze the message.
25
+ # message.serialize
26
+ #
27
+ # @param [ String ] buffer A buffer to serialize to.
28
+ #
29
+ # @return [ String ] The result of serliazing this message
30
+ #
31
+ # @since 1.0.0
32
+ def serialize(buffer, adapter)
33
+ Adapter.get(adapter).serialize(process, buffer)
34
+ end
35
+
36
+ class << self
37
+
38
+ # Extends the including class with +ClassMethods+.
39
+ #
40
+ # @param [Class] subclass the inheriting class
41
+ def included(base)
42
+ super
43
+ base.extend(ClassMethods)
44
+ end
45
+ private :included
46
+ end
47
+
48
+ # Provides a DSL for defining struct-like fields for building messages
49
+ # for the Mongo Wire.
50
+ #
51
+ # @example
52
+ # class Command
53
+ # extend Message::ClassMethods
54
+ #
55
+ # int32 :length
56
+ # end
57
+ #
58
+ # Command.fields # => [:length]
59
+ # command = Command.new
60
+ # command.length = 12
61
+ # command.serialize_length("") # => "\f\x00\x00\x00"
62
+ module ClassMethods
63
+
64
+ def deserialize(adapter, buffer="")
65
+ adapter = Adapter.get(adapter)
66
+ message = adapter.deserialize(buffer)
67
+ message.shift
68
+ new(adapter, *message)
69
+ end
70
+
71
+ # @return [Array] the fields defined for this message
72
+ def fields
73
+ @fields ||= []
74
+ end
75
+
76
+ def any(name)
77
+ attr_accessor name
78
+
79
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
80
+ def process_#{name}
81
+ #{name}
82
+ end
83
+ RUBY
84
+
85
+ fields << name
86
+ end
87
+
88
+ # Declare a binary field.
89
+ #
90
+ # @example
91
+ # class Query < Message
92
+ # binary :collection
93
+ # end
94
+ #
95
+ # @param [String] name the name of this field
96
+ def binary(name)
97
+ attr_accessor name
98
+
99
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
100
+ def process_#{name}
101
+ #{name}
102
+ end
103
+ RUBY
104
+
105
+ fields << name
106
+ end
107
+
108
+ def integer(name)
109
+ attr_accessor name
110
+
111
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
112
+ def #{name}
113
+ @#{name} ||= 0
114
+ end
115
+
116
+ def process_#{name}
117
+ #{name}
118
+ end
119
+ RUBY
120
+
121
+ fields << name
122
+ end
123
+
124
+ def list(name)
125
+ attr_accessor name
126
+
127
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
128
+ def #{name}
129
+ @#{name} ||= []
130
+ end
131
+
132
+ def process_#{name}
133
+ #{name}
134
+ end
135
+ RUBY
136
+
137
+ fields << name
138
+ end
139
+
140
+ # Declares the message class as complete, and defines its serialization
141
+ # method from the declared fields.
142
+ def finalize
143
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
144
+ def process
145
+ list = []
146
+ #{fields.map { |f| "list << process_#{f}" }.join("\n")}
147
+ list
148
+ end
149
+ EOS
150
+ end
151
+
152
+ private
153
+
154
+ # This ensures that subclasses of the primary wire message classes have
155
+ # identical fields.
156
+ def inherited(subclass)
157
+ super
158
+ subclass.fields.replace(fields)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end