protobuf 2.0.0.rc3 → 2.0.0.rc4

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