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.
- data/lib/protobuf/cli.rb +1 -2
- data/lib/protobuf/{common/exceptions.rb → exceptions.rb} +0 -0
- data/lib/protobuf/field/base_field.rb +1 -1
- data/lib/protobuf/{common/logger.rb → logger.rb} +21 -0
- data/lib/protobuf/message/decoder.rb +2 -2
- data/lib/protobuf/message/encoder.rb +6 -4
- data/lib/protobuf/rpc/buffer.rb +2 -2
- data/lib/protobuf/rpc/client.rb +18 -18
- data/lib/protobuf/rpc/connectors/base.rb +3 -8
- data/lib/protobuf/rpc/connectors/common.rb +29 -28
- data/lib/protobuf/rpc/connectors/em_client.rb +9 -9
- data/lib/protobuf/rpc/connectors/eventmachine.rb +11 -9
- data/lib/protobuf/rpc/connectors/socket.rb +13 -17
- data/lib/protobuf/rpc/connectors/zmq.rb +13 -17
- data/lib/protobuf/rpc/error.rb +3 -3
- data/lib/protobuf/rpc/server.rb +41 -93
- data/lib/protobuf/rpc/servers/evented/server.rb +7 -9
- data/lib/protobuf/rpc/servers/evented_runner.rb +0 -11
- data/lib/protobuf/rpc/servers/socket/server.rb +8 -7
- data/lib/protobuf/rpc/servers/socket/worker.rb +22 -15
- data/lib/protobuf/rpc/servers/zmq/server.rb +3 -3
- data/lib/protobuf/rpc/servers/zmq/util.rb +1 -1
- data/lib/protobuf/rpc/servers/zmq/worker.rb +6 -15
- data/lib/protobuf/rpc/service.rb +145 -228
- data/lib/protobuf/rpc/service_dispatcher.rb +114 -0
- data/lib/protobuf/rpc/stat.rb +46 -33
- data/lib/protobuf/version.rb +1 -1
- data/lib/protobuf/{common/wire_type.rb → wire_type.rb} +0 -0
- data/spec/benchmark/tasks.rb +18 -18
- data/spec/functional/evented_server_spec.rb +3 -4
- data/spec/functional/socket_server_spec.rb +3 -3
- data/spec/functional/zmq_server_spec.rb +3 -3
- data/spec/lib/protobuf/{common/logger_spec.rb → logger_spec.rb} +46 -36
- data/spec/lib/protobuf/rpc/client_spec.rb +10 -58
- data/spec/lib/protobuf/rpc/connectors/base_spec.rb +1 -39
- data/spec/lib/protobuf/rpc/connectors/common_spec.rb +3 -6
- data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +0 -12
- data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +1 -6
- data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +94 -0
- data/spec/lib/protobuf/rpc/service_spec.rb +132 -45
- data/spec/spec_helper.rb +4 -3
- data/spec/support/server.rb +8 -4
- 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
|
-
|
13
|
-
|
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
|
-
|
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 { "
|
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
|
-
|
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'
|
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 { "
|
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 { "
|
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 { "
|
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 { "
|
26
|
+
log_debug { sign_message("server started") }
|
27
27
|
while self.running? do
|
28
28
|
@broker.poll
|
29
29
|
end
|
@@ -13,9 +13,9 @@ module Protobuf
|
|
13
13
|
#
|
14
14
|
def initialize(opts={})
|
15
15
|
@options = opts
|
16
|
-
host = @options.fetch(:host
|
17
|
-
port = @options.fetch(:port
|
18
|
-
protocol = @options.fetch(:protocol
|
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 { "
|
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
|
-
|
43
|
+
initialize_request!
|
52
44
|
handle_request(socket)
|
53
|
-
handle_client unless
|
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
|
|
data/lib/protobuf/rpc/service.rb
CHANGED
@@ -1,243 +1,160 @@
|
|
1
|
-
require 'protobuf/
|
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", :
|
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 :
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
130
|
+
self.class.rpcs
|
141
131
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|