protobuf 2.0.0.rc3 → 2.0.0.rc4

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 (43) hide show
  1. data/lib/protobuf/cli.rb +1 -2
  2. data/lib/protobuf/{common/exceptions.rb → exceptions.rb} +0 -0
  3. data/lib/protobuf/field/base_field.rb +1 -1
  4. data/lib/protobuf/{common/logger.rb → logger.rb} +21 -0
  5. data/lib/protobuf/message/decoder.rb +2 -2
  6. data/lib/protobuf/message/encoder.rb +6 -4
  7. data/lib/protobuf/rpc/buffer.rb +2 -2
  8. data/lib/protobuf/rpc/client.rb +18 -18
  9. data/lib/protobuf/rpc/connectors/base.rb +3 -8
  10. data/lib/protobuf/rpc/connectors/common.rb +29 -28
  11. data/lib/protobuf/rpc/connectors/em_client.rb +9 -9
  12. data/lib/protobuf/rpc/connectors/eventmachine.rb +11 -9
  13. data/lib/protobuf/rpc/connectors/socket.rb +13 -17
  14. data/lib/protobuf/rpc/connectors/zmq.rb +13 -17
  15. data/lib/protobuf/rpc/error.rb +3 -3
  16. data/lib/protobuf/rpc/server.rb +41 -93
  17. data/lib/protobuf/rpc/servers/evented/server.rb +7 -9
  18. data/lib/protobuf/rpc/servers/evented_runner.rb +0 -11
  19. data/lib/protobuf/rpc/servers/socket/server.rb +8 -7
  20. data/lib/protobuf/rpc/servers/socket/worker.rb +22 -15
  21. data/lib/protobuf/rpc/servers/zmq/server.rb +3 -3
  22. data/lib/protobuf/rpc/servers/zmq/util.rb +1 -1
  23. data/lib/protobuf/rpc/servers/zmq/worker.rb +6 -15
  24. data/lib/protobuf/rpc/service.rb +145 -228
  25. data/lib/protobuf/rpc/service_dispatcher.rb +114 -0
  26. data/lib/protobuf/rpc/stat.rb +46 -33
  27. data/lib/protobuf/version.rb +1 -1
  28. data/lib/protobuf/{common/wire_type.rb → wire_type.rb} +0 -0
  29. data/spec/benchmark/tasks.rb +18 -18
  30. data/spec/functional/evented_server_spec.rb +3 -4
  31. data/spec/functional/socket_server_spec.rb +3 -3
  32. data/spec/functional/zmq_server_spec.rb +3 -3
  33. data/spec/lib/protobuf/{common/logger_spec.rb → logger_spec.rb} +46 -36
  34. data/spec/lib/protobuf/rpc/client_spec.rb +10 -58
  35. data/spec/lib/protobuf/rpc/connectors/base_spec.rb +1 -39
  36. data/spec/lib/protobuf/rpc/connectors/common_spec.rb +3 -6
  37. data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +0 -12
  38. data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +1 -6
  39. data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +94 -0
  40. data/spec/lib/protobuf/rpc/service_spec.rb +132 -45
  41. data/spec/spec_helper.rb +4 -3
  42. data/spec/support/server.rb +8 -4
  43. metadata +41 -35
@@ -1,3 +1,6 @@
1
+ require 'protobuf/rpc/server'
2
+ require 'protobuf/logger'
3
+
1
4
  module Protobuf
2
5
  module Rpc
3
6
  module Socket
@@ -7,34 +10,26 @@ module Protobuf
7
10
  include ::Protobuf::Logger::LogMethods
8
11
 
9
12
  def initialize(sock, &complete_cb)
10
- @did_response = false
11
13
  @socket = sock
12
- @request = Protobuf::Socketrpc::Request.new
13
- @response = Protobuf::Socketrpc::Response.new
14
+ initialize_request!
15
+
14
16
  request_buffer = Protobuf::Rpc::Buffer.new(:read)
15
- @stats = Protobuf::Rpc::Stat.new(:SERVER, true)
16
17
  @complete_cb = complete_cb
17
- log_debug { "[#{log_signature}] Post init, new read buffer created" }
18
18
 
19
- @stats.client = ::Socket.unpack_sockaddr_in(@socket.getpeername)
20
- log_debug { "stats are #{@stats.to_s}" }
19
+ log_debug { sign_message("stats are #{@stats.to_s}") }
21
20
  request_buffer << read_data
22
21
  @request_data = request_buffer.data
23
22
 
24
23
  @stats.request_size = request_buffer.size
25
24
 
26
- log_debug { "[#{log_signature}] handling request" }
25
+ log_debug { sign_message("handling request") }
27
26
  handle_client if request_buffer.flushed?
28
27
  end
29
28
 
30
- def log_signature
31
- @log_signature ||= "server-#{self.class}-#{object_id}"
32
- end
33
-
34
29
  def read_data
35
30
  size_io = StringIO.new
36
31
 
37
- while((size_reader = @socket.getc) != "-")
32
+ until (size_reader = @socket.getc) == "-"
38
33
  size_io << size_reader
39
34
  end
40
35
  str_size_io = size_io.string
@@ -43,15 +38,27 @@ module Protobuf
43
38
  end
44
39
 
45
40
  def send_data
46
- raise 'Socket closed unexpectedly' if(@socket.nil? || @socket.closed?)
41
+ raise 'Socket closed unexpectedly' unless socket_writable?
47
42
  response_buffer = Protobuf::Rpc::Buffer.new(:write)
48
43
  response_buffer.set_data(@response)
49
44
  @stats.response_size = response_buffer.size
50
- log_debug { "[#{log_signature}] sending data : %s" % response_buffer.data }
45
+ log_debug { sign_message("sending data : #{response_buffer.data}") }
51
46
  @socket.write(response_buffer.write)
52
47
  @socket.flush
53
48
  @complete_cb.call(@socket)
54
49
  end
50
+
51
+ def log_signature
52
+ @_log_signature ||= "server-#{self.class}-#{object_id}"
53
+ end
54
+
55
+ def set_peer
56
+ @stats.client = ::Socket.unpack_sockaddr_in(@socket.getpeername)
57
+ end
58
+
59
+ def socket_writable?
60
+ ! @socket.nil? && ! @socket.closed?
61
+ end
55
62
  end
56
63
 
57
64
  end
@@ -12,18 +12,18 @@ module Protobuf
12
12
  # Class Methods
13
13
  #
14
14
  def self.run(opts = {})
15
- log_debug { "[#{log_signature}] initializing broker" }
15
+ log_debug { sign_message("initializing broker") }
16
16
  @broker = ::Protobuf::Rpc::Zmq::Broker.new(opts)
17
17
  local_worker_threads = opts.fetch(:threads, 5)
18
18
 
19
19
  worker_options = opts.merge(:port => opts.fetch(:port, 9399) + 1)
20
- log_debug { "[#{log_signature}] starting server workers" }
20
+ log_debug { sign_message("starting server workers") }
21
21
  local_worker_threads.times do
22
22
  @threads << Thread.new { ::Protobuf::Rpc::Zmq::Worker.new(worker_options).run }
23
23
  end
24
24
  @running = true
25
25
 
26
- log_debug { "[#{log_signature}] server started" }
26
+ log_debug { sign_message("server started") }
27
27
  while self.running? do
28
28
  @broker.poll
29
29
  end
@@ -13,7 +13,7 @@ module Protobuf
13
13
  end
14
14
 
15
15
  def log_signature
16
- @log_signature ||= "server-#{self.class}-#{object_id}"
16
+ @_log_signature ||= "server-#{self.class}-#{object_id}"
17
17
  end
18
18
 
19
19
  def resolve_ip(hostname)
@@ -13,9 +13,9 @@ module Protobuf
13
13
  #
14
14
  def initialize(opts={})
15
15
  @options = opts
16
- host = @options.fetch(:host, "127.0.0.1")
17
- port = @options.fetch(:port, 9400)
18
- protocol = @options.fetch(:protocol, "tcp")
16
+ host = @options.fetch(:host) { "127.0.0.1" }
17
+ port = @options.fetch(:port) { 9400 }
18
+ protocol = @options.fetch(:protocol) { "tcp" }
19
19
 
20
20
  @zmq_context = ::ZMQ::Context.new
21
21
  @socket = @zmq_context.socket(::ZMQ::REP)
@@ -31,15 +31,7 @@ module Protobuf
31
31
  def handle_request(socket)
32
32
  @request_data = ''
33
33
  zmq_error_check(socket.recv_string(@request_data))
34
- log_debug { "[#{log_signature}] handling request" } if(!@request_data.nil?)
35
- end
36
-
37
- def initialize_buffers
38
- @did_respond = false
39
- @request = ::Protobuf::Socketrpc::Request.new
40
- @response = ::Protobuf::Socketrpc::Response.new
41
- @stats = ::Protobuf::Rpc::Stat.new(:SERVER, true)
42
- log_debug { "[#{log_signature}] Post init" }
34
+ log_debug { sign_message("handling request") } if !@request_data.nil?
43
35
  end
44
36
 
45
37
  def run
@@ -48,9 +40,9 @@ module Protobuf
48
40
  # This lets us see whether we need to die
49
41
  @poller.poll(1_000)
50
42
  @poller.readables.each do |socket|
51
- initialize_buffers
43
+ initialize_request!
52
44
  handle_request(socket)
53
- handle_client unless(@request_data.nil?)
45
+ handle_client unless @request_data.nil?
54
46
  end
55
47
  end
56
48
  ensure
@@ -62,7 +54,6 @@ module Protobuf
62
54
  response_data = @response.is_a?(::Protobuf::Message) ? @response.serialize_to_string : @response.to_s
63
55
  @stats.response_size = response_data.size
64
56
  zmq_error_check(@socket.send_string(response_data))
65
- @did_respond = true
66
57
  end
67
58
  end
68
59
 
@@ -1,243 +1,160 @@
1
- require 'protobuf/common/logger'
1
+ require 'protobuf/logger'
2
2
  require 'protobuf/rpc/client'
3
3
  require 'protobuf/rpc/error'
4
4
 
5
5
  module Protobuf
6
6
  module Rpc
7
7
  # Object to encapsulate the request/response types for a given service method
8
- #
9
- RpcMethod = Struct.new("RpcMethod", :service, :method, :request_type, :response_type)
10
-
8
+ #
9
+ RpcMethod = Struct.new("RpcMethod", :method, :request_type, :response_type)
10
+
11
11
  class Service
12
12
  include Protobuf::Logger::LogMethods
13
-
14
- attr_reader :request
15
- attr_accessor :response, :async_responder
16
- private :request, :response, :response=
17
-
18
- DEFAULT_LOCATION = {
19
- :host => '127.0.0.1',
20
- :port => 9399
21
- }
22
-
23
- # Class methods are intended for use on the client-side.
24
- #
25
- class << self
26
-
27
- # You MUST add the method name to this list if you are adding
28
- # instance methods below, otherwise stuff will definitely break
29
- NON_RPC_METHODS = %w( rpcs call_rpc on_rpc_failed rpc_failed request response method_missing async_responder on_send_response send_response log_signature )
30
-
31
- # Override methods being added to the class
32
- # If the method isn't already a private instance method, or it doesn't start with rpc_,
33
- # or it isn't in the reserved method list (NON_RPC_METHODS),
34
- # We want to remap the method such that we can wrap it in before and after behavior,
35
- # most notably calling call_rpc against the method. See call_rpc for more info.
36
- def method_added(old)
37
- new_method = :"rpc_#{old}"
38
- return if private_instance_methods.include?(new_method) or old =~ /^rpc_/ or NON_RPC_METHODS.include?(old.to_s)
39
-
40
- alias_method new_method, old
41
- private new_method
42
-
43
- define_method(old) do |pb_request|
44
- call_rpc(old.to_sym, pb_request)
45
- end
46
- rescue ArgumentError => e
47
- # Wrap a known issue where an instance method was defined in the class without
48
- # it being ignored with NON_RPC_METHODS.
49
- raise ArgumentError, "#{e.message} (Note: This could mean that you need to add the method #{old} to the NON_RPC_METHODS list)"
50
- end
51
-
52
- # Generated service classes should call this method on themselves to add rpc methods
53
- # to the stack with a given request and response type
54
- def rpc(method, request_type, response_type)
55
- rpcs[self] ||= {}
56
- rpcs[self][method] = RpcMethod.new self, method, request_type, response_type
57
- end
58
-
59
- # Shorthand for @rpcs class instance var
60
- def rpcs
61
- @rpcs ||= {}
62
- end
63
-
64
- # Create a new client for the given service.
65
- # See Client#initialize and ClientConnection::DEFAULT_OPTIONS
66
- # for all available options.
67
- #
68
- def client(options={})
69
- configure
70
- Client.new({
71
- :service => self,
72
- :async => false,
73
- :host => self.host,
74
- :port => self.port
75
- }.merge(options))
76
- end
77
-
78
- # Allows service-level configuration of location.
79
- # Useful for system-startup configuration of a service
80
- # so that any Clients using the Service.client sugar
81
- # will not have to configure the location each time.
82
- #
83
- def configure(config={})
84
- locations[self] ||= {}
85
- locations[self][:host] = config[:host] if config.key?(:host)
86
- locations[self][:port] = config[:port] if config.key?(:port)
87
- end
88
-
89
- # Shorthand call to configure, passing a string formatted as hostname:port
90
- # e.g. 127.0.0.1:9933
91
- # e.g. localhost:0
92
- #
93
- def located_at(location)
94
- return if location.nil? or location.downcase.strip !~ /[a-z0-9.]+:\d+/
95
- host, port = location.downcase.strip.split ':'
96
- configure :host => host, :port => port.to_i
97
- end
98
-
99
- # The host location of the service
100
- def host
101
- configure
102
- locations[self][:host] || DEFAULT_LOCATION[:host]
103
- end
104
-
105
- # The port of the service on the destination server
106
- def port
107
- configure
108
- locations[self][:port] || DEFAULT_LOCATION[:port]
109
- end
110
-
111
- # Shorthand for @locations class instance var
112
- def locations
113
- @locations ||= {}
114
- end
115
-
116
- end
117
-
118
- def log_signature
119
- @log_signature ||= "service-#{self.class}"
120
- end
121
-
122
- # If a method comes through that hasn't been found, and it
123
- # is defined in the rpcs method list, we know that the rpc
124
- # stub has been created, but no implementing method provides the
125
- # functionality, so throw an appropriate error, otherwise go to super
126
- #
127
- def method_missing m, *params
128
- if rpcs.key?(m)
129
- exc = MethodNotFound.new "#{self}##{m} was defined as a valid rpc method, but was not implemented."
130
- log_error exc.message
131
- raise exc
132
- else
133
- log_error { "-------------- [#{log_signature}] %s#%s not rpc method, passing to super" % [self.class.name, m.to_s] }
134
- super m, params
135
- end
136
- end
137
-
138
- # Convenience wrapper around the rpc method list for a given class
13
+
14
+ attr_reader :response
15
+
16
+ DEFAULT_HOST = '127.0.0.1'.freeze
17
+ DEFAULT_PORT = 9399
18
+
19
+ ##
20
+ # Class Methods
21
+ #
22
+
23
+ # Create a new client for the given service.
24
+ # See Client#initialize and ClientConnection::DEFAULT_OPTIONS
25
+ # for all available options.
26
+ #
27
+ def self.client(options = {})
28
+ ::Protobuf::Rpc::Client.new({ :service => self,
29
+ :host => host,
30
+ :port => port }.merge(options))
31
+ end
32
+
33
+ # Allows service-level configuration of location.
34
+ # Useful for system-startup configuration of a service
35
+ # so that any Clients using the Service.client sugar
36
+ # will not have to configure the location each time.
37
+ #
38
+ def self.configure(config = {})
39
+ self.host = config[:host] if config.key?(:host)
40
+ self.port = config[:port] if config.key?(:port)
41
+ end
42
+
43
+ # The host location of the service.
44
+ #
45
+ def self.host
46
+ @_host ||= DEFAULT_HOST
47
+ end
48
+
49
+ # The host location setter.
50
+ #
51
+ def self.host=(new_host)
52
+ @_host = new_host
53
+ end
54
+
55
+ # Shorthand call to configure, passing a string formatted as hostname:port
56
+ # e.g. 127.0.0.1:9933
57
+ # e.g. localhost:0
58
+ #
59
+ def self.located_at(location)
60
+ return if location.nil? || location.downcase.strip !~ /.+:\d+/
61
+ host, port = location.downcase.strip.split ':'
62
+ configure(:host => host, :port => port.to_i)
63
+ end
64
+
65
+ # The port of the service on the destination server.
66
+ #
67
+ def self.port
68
+ @_port ||= DEFAULT_PORT
69
+ end
70
+
71
+ # The port location setter.
72
+ #
73
+ def self.port=(new_port)
74
+ @_port = new_port
75
+ end
76
+
77
+ # Define an rpc method with the given request and response types.
78
+ # This methods is only used by the generated service definitions
79
+ # and not useful for user code.
80
+ #
81
+ def self.rpc(method, request_type, response_type)
82
+ rpcs[method] = RpcMethod.new(method, request_type, response_type)
83
+ end
84
+
85
+ # Hash containing the set of methods defined via `rpc`.
86
+ #
87
+ def self.rpcs
88
+ @_rpcs ||= {}
89
+ end
90
+
91
+ # Check if the given method name is a known rpc endpoint.
92
+ #
93
+ def self.rpc_method?(name)
94
+ rpcs.key?(name)
95
+ end
96
+
97
+
98
+ ##
99
+ # Instance Methods
100
+ #
101
+
102
+ # Initialize a service with the rpc endpoint name and the bytes
103
+ # for the request.
104
+ def initialize(rpc, request_bytes)
105
+ @rpc = rpc
106
+ @request_bytes = request_bytes
107
+ end
108
+
109
+ # Register a failure callback for use when rpc_failed is invoked.
110
+ #
111
+ def on_rpc_failed(callable)
112
+ @rpc_failed_callback ||= callable
113
+ end
114
+
115
+ # Response object for this rpc cycle. Not assignable.
116
+ #
117
+ def response
118
+ @_response ||= rpcs[@rpc].response_type.new
119
+ end
120
+
121
+ # Convenience method to get back to class method.
122
+ #
123
+ def rpc_method?(name)
124
+ self.class.rpc_method?(name)
125
+ end
126
+
127
+ # Convenience method to get back to class rpcs hash.
128
+ #
139
129
  def rpcs
140
- self.class.rpcs[self.class]
130
+ self.class.rpcs
141
131
  end
142
-
143
- # Callback register for the server when a service
144
- # method calls rpc_failed. Called by Service#rpc_failed.
145
- def on_rpc_failed(&rpc_failure_cb)
146
- @rpc_failure_cb = rpc_failure_cb
132
+
133
+ private
134
+
135
+ # Request object for this rpc cycle. Not assignable.
136
+ #
137
+ def request
138
+ @_request ||= rpcs[@rpc].request_type.new.parse_from_string(@request_bytes)
139
+ rescue => e
140
+ raise BadRequestProto, "Unable to parse request: #{e.message}"
147
141
  end
148
-
142
+
143
+ # Sugar to make an rpc method feel like a controller method.
144
+ # If this method is not called, the response will be the memoized
145
+ # object returned by the response reader.
146
+ #
147
+ def respond_with(candidate)
148
+ @_response = candidate
149
+ end
150
+ alias_method :return_from_whence_you_came, :respond_with
151
+
149
152
  # Automatically fail a service method.
150
- # NOTE: This shortcuts the @async_responder paradigm. There is
151
- # not any way to get around this currently (and I'm not sure you should want to).
152
- #
153
- def rpc_failed(message="RPC Failed while executing service method #{@current_method}")
154
- error_message = 'Unable to invoke rpc_failed, no failure callback is setup.'
155
- log_and_raise_error(error_message) if @rpc_failure_cb.nil?
156
- error = message.is_a?(String) ? RpcFailed.new(message) : message
157
- log_warn "[#{log_signature}] RPC Failed: %s" % error.message
158
- @rpc_failure_cb.call(error)
159
- end
160
-
161
- # Callback register for the server to be notified
162
- # when it is appropriate to generate a response to the client.
163
- # Used in conjunciton with Service#send_response.
164
- #
165
- def on_send_response(&responder)
166
- @responder = responder
167
- end
168
-
169
- # Tell the server to generate response and send it to the client.
170
- #
171
- # NOTE: If @async_responder is set to true, this MUST be called by
172
- # the implementing service method, otherwise the connection
173
- # will timeout since no data will be sent.
174
- #
175
- def send_response
176
- error_message = "Unable to send response, responder is nil. It appears you aren't inside of an RPC request/response cycle."
177
- log_and_raise_error(error_message) if @responder.nil?
178
- @responder.call(@response)
179
- end
180
-
181
- private
182
-
183
- def log_and_raise_error(error_message)
184
- log_error(error_message)
185
- raise error_message
186
- end
187
-
188
- # Call the rpc method that was previously privatized.
189
- # call_rpc allows us to wrap the normal method call with
190
- # before and after behavior, most notably setting up the request
191
- # and response instances.
192
- #
193
- # Implementing rpc methods should be aware
194
- # that request and response are implicitly available, and
195
- # that response should be manipulated during the rpc method,
196
- # as there is no way to reliably determine the response like
197
- # a normal (http-based) controller method would be able to.
198
- #
199
- # Async behavior of responding can be achieved in the rpc method
200
- # by explicitly setting self.async_responder = true. It is then
201
- # the responsibility of the service method to send the response,
202
- # by calling self.send_response without any arguments. The rpc
203
- # server is setup to handle synchronous and asynchronous responses.
204
- #
205
- def call_rpc(method, pb_request)
206
- @current_method = method
207
-
208
- # Allows the service to set whether or not
209
- # it would like to asynchronously respond to the connected client(s)
210
- @async_responder = false
211
-
212
- # Setup the request
213
- @request = rpcs[method].request_type.new
214
- @request.parse_from_string(pb_request.request_proto)
215
- rescue
216
- exc = BadRequestProto.new 'Unable to parse request: %s' % $!.message
217
- log_error exc.message
218
- log_error $!.backtrace.join("\n")
219
- raise exc
220
- else # when no Exception was thrown
221
- # Setup the response
222
- @response = rpcs[method].response_type.new
223
-
224
- log_debug { "[#{log_signature}] calling service method %s#%s" % [self.class, method] }
225
- # Call the aliased rpc method (e.g. :rpc_find for :find)
226
- __send__("rpc_#{method}".to_sym)
227
- log_debug { "[#{log_signature}] completed service method %s#%s" % [self.class, method] }
228
-
229
- # Pass the populated response back to the server
230
- # Note this will only get called if the rpc method didn't explode (by design)
231
- if @async_responder
232
- log_debug { "[#{log_signature}] async request, not sending response (yet)" }
233
- else
234
- log_debug { "[#{log_signature}] trigger server send_response" }
235
- send_response
236
- end
237
- end
238
-
153
+ #
154
+ def rpc_failed(message)
155
+ @rpc_failed_callback.call(message)
156
+ end
157
+
239
158
  end
240
-
241
159
  end
242
-
243
160
  end