protobuf 1.0.0 → 1.0.1
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/Gemfile.lock +1 -1
- data/README.md +138 -126
- data/bin/rpc_server +2 -2
- data/bin/rprotoc +2 -2
- data/examples/reading_a_message.rb +3 -3
- data/examples/writing_a_message.rb +3 -3
- data/lib/protobuf.rb +3 -0
- data/lib/protobuf/compiler/nodes.rb +1 -1
- data/lib/protobuf/compiler/proto_parser.rb +16 -16
- data/lib/protobuf/compiler/visitors.rb +11 -25
- data/lib/protobuf/descriptor/descriptor_builder.rb +58 -58
- data/lib/protobuf/descriptor/field_descriptor.rb +2 -2
- data/lib/protobuf/ext/eventmachine.rb +16 -0
- data/lib/protobuf/message/decoder.rb +6 -6
- data/lib/protobuf/message/field.rb +14 -14
- data/lib/protobuf/message/message.rb +4 -4
- data/lib/protobuf/rpc/client.rb +42 -183
- data/lib/protobuf/rpc/connector.rb +19 -0
- data/lib/protobuf/rpc/connectors/base.rb +29 -0
- data/lib/protobuf/rpc/connectors/em_client.rb +227 -0
- data/lib/protobuf/rpc/connectors/eventmachine.rb +84 -0
- data/lib/protobuf/rpc/connectors/socket.rb +14 -0
- data/lib/protobuf/rpc/service.rb +4 -4
- data/lib/protobuf/version.rb +1 -1
- data/protobuf.gemspec +3 -3
- data/spec/helper/all.rb +13 -0
- data/spec/helper/server.rb +36 -0
- data/spec/helper/tolerance_matcher.rb +40 -0
- data/spec/spec_helper.rb +3 -5
- data/spec/unit/rpc/client_spec.rb +174 -0
- data/spec/unit/rpc/connector_spec.rb +36 -0
- data/spec/unit/rpc/connectors/base_spec.rb +77 -0
- data/spec/unit/rpc/connectors/eventmachine/client_spec.rb +0 -0
- data/spec/unit/rpc/connectors/eventmachine_spec.rb +0 -0
- data/spec/unit/{server_spec.rb → rpc/server_spec.rb} +0 -0
- data/spec/unit/{service_spec.rb → rpc/service_spec.rb} +0 -0
- metadata +79 -63
- data/lib/protobuf/compiler/template/rpc_bin.erb +0 -4
- data/lib/protobuf/compiler/template/rpc_client.erb +0 -18
- data/lib/protobuf/compiler/template/rpc_service.erb +0 -25
- data/lib/protobuf/rpc/client_connection.rb +0 -225
- data/spec/unit/client_spec.rb +0 -128
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'protobuf/rpc/connectors/eventmachine'
|
2
|
+
require 'protobuf/rpc/connectors/socket'
|
3
|
+
|
4
|
+
module Protobuf
|
5
|
+
module Rpc
|
6
|
+
class Connector
|
7
|
+
|
8
|
+
def self.connector_for_platform platform=RUBY_ENGINE
|
9
|
+
case platform
|
10
|
+
when /jruby/i
|
11
|
+
Connectors::Socket
|
12
|
+
else
|
13
|
+
Connectors::EventMachine
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'protobuf/common/logger'
|
2
|
+
|
3
|
+
module Protobuf
|
4
|
+
module Rpc
|
5
|
+
module Connectors
|
6
|
+
class Base
|
7
|
+
include Protobuf::Logger::LogMethods
|
8
|
+
|
9
|
+
attr_reader :options
|
10
|
+
attr_accessor :success_cb, :failure_cb
|
11
|
+
|
12
|
+
def initialize options
|
13
|
+
@options = options
|
14
|
+
@success_cb = nil
|
15
|
+
@failure_cb = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_request
|
19
|
+
raise 'not implemented'
|
20
|
+
end
|
21
|
+
|
22
|
+
def async?
|
23
|
+
!!@options[:async]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'protobuf/common/logger'
|
3
|
+
require 'protobuf/rpc/rpc.pb'
|
4
|
+
require 'protobuf/rpc/buffer'
|
5
|
+
require 'protobuf/rpc/error'
|
6
|
+
require 'protobuf/rpc/stat'
|
7
|
+
|
8
|
+
# Handles client connections to the server
|
9
|
+
module Protobuf
|
10
|
+
module Rpc
|
11
|
+
module Connectors
|
12
|
+
ClientError = Struct.new("ClientError", :code, :message)
|
13
|
+
|
14
|
+
class EMClient < EM::Connection
|
15
|
+
include Protobuf::Logger::LogMethods
|
16
|
+
|
17
|
+
attr_reader :options, :request, :response
|
18
|
+
attr_reader :error, :error_reason, :error_message
|
19
|
+
|
20
|
+
DEFAULT_OPTIONS = {
|
21
|
+
:service => nil, # Service to invoke
|
22
|
+
:method => nil, # Service method to call
|
23
|
+
:host => 'localhost', # A default host (usually overridden)
|
24
|
+
:port => '9938', # A default port (usually overridden)
|
25
|
+
:request => nil, # The request object sent by the client
|
26
|
+
:request_type => nil, # The request type expected by the client
|
27
|
+
:response_type => nil, # The response type expected by the client
|
28
|
+
:async => false, # Whether or not to block a client call, this is actually handled by client.rb
|
29
|
+
:timeout => 30 # The default timeout for the request, also handled by client.rb
|
30
|
+
}
|
31
|
+
|
32
|
+
# For state tracking
|
33
|
+
STATES = {
|
34
|
+
:pending => 0,
|
35
|
+
:succeeded => 1,
|
36
|
+
:failed => 2,
|
37
|
+
:completed => 3
|
38
|
+
}
|
39
|
+
|
40
|
+
def self.connect options={}
|
41
|
+
options = DEFAULT_OPTIONS.merge(options)
|
42
|
+
Protobuf::Logger.debug '[client-cnxn] Connecting to server: %s' % options.inspect
|
43
|
+
host = options[:host]
|
44
|
+
port = options[:port]
|
45
|
+
EM.connect host, port, self, options
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize options={}, &failure_cb
|
49
|
+
@failure_cb = failure_cb
|
50
|
+
|
51
|
+
# Verify the options that are necessary and merge them in
|
52
|
+
[:service, :method, :host, :port].each do |opt|
|
53
|
+
fail(:RPC_ERROR, "Invalid client connection configuration. #{opt} must be a defined option.") if !options[opt] || options[opt].nil?
|
54
|
+
end
|
55
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
56
|
+
log_debug '[client-cnxn] Client Initialized: %s' % options.inspect
|
57
|
+
|
58
|
+
@error = ClientError.new
|
59
|
+
@success_cb = nil
|
60
|
+
@state = STATES[:pending]
|
61
|
+
|
62
|
+
@stats = Protobuf::Rpc::Stat.new(:CLIENT, true)
|
63
|
+
@stats.server = [@options[:port], @options[:host]]
|
64
|
+
@stats.service = @options[:service].name
|
65
|
+
@stats.method = @options[:method]
|
66
|
+
rescue
|
67
|
+
fail(:RPC_ERROR, 'Failed to initialize connection: %s' % $!.message) unless failed?
|
68
|
+
end
|
69
|
+
|
70
|
+
# Success callback registration
|
71
|
+
def on_success &success_cb
|
72
|
+
@success_cb = success_cb
|
73
|
+
end
|
74
|
+
|
75
|
+
# Failure callback registration
|
76
|
+
def on_failure &failure_cb
|
77
|
+
@failure_cb = failure_cb
|
78
|
+
end
|
79
|
+
|
80
|
+
# Completion callback registration
|
81
|
+
def on_complete &complete_cb
|
82
|
+
@complete_cb = complete_cb
|
83
|
+
end
|
84
|
+
|
85
|
+
# Called after the EM.connect
|
86
|
+
def connection_completed
|
87
|
+
log_debug '[client-cnxn] Established server connection, sending request'
|
88
|
+
send_request unless error?
|
89
|
+
rescue
|
90
|
+
fail(:RPC_ERROR, 'Connection error: %s' % $!.message) unless failed?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Setup the read buffer for data coming back
|
94
|
+
def post_init
|
95
|
+
log_debug '[client-cnxn] Post init, new read buffer created'
|
96
|
+
@buffer = Protobuf::Rpc::Buffer.new :read
|
97
|
+
rescue
|
98
|
+
fail(:RPC_ERROR, 'Connection error: %s' % $!.message) unless failed?
|
99
|
+
end
|
100
|
+
|
101
|
+
def receive_data data
|
102
|
+
log_debug '[client-cnxn] receive_data: %s' % data
|
103
|
+
@buffer << data
|
104
|
+
parse_response if @buffer.flushed?
|
105
|
+
end
|
106
|
+
|
107
|
+
def pending?
|
108
|
+
@state == STATES[:pending]
|
109
|
+
end
|
110
|
+
|
111
|
+
def succeeded?
|
112
|
+
@state == STATES[:succeeded]
|
113
|
+
end
|
114
|
+
|
115
|
+
def failed?
|
116
|
+
@state == STATES[:failed]
|
117
|
+
end
|
118
|
+
|
119
|
+
def completed?
|
120
|
+
@state == STATES[:completed]
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# Sends the request to the server, invoked by the connection_completed event
|
126
|
+
def send_request
|
127
|
+
request_wrapper = Protobuf::Socketrpc::Request.new
|
128
|
+
request_wrapper.service_name = @options[:service].name
|
129
|
+
request_wrapper.method_name = @options[:method].to_s
|
130
|
+
|
131
|
+
if @options[:request].class == @options[:request_type]
|
132
|
+
request_wrapper.request_proto = @options[:request].serialize_to_string
|
133
|
+
else
|
134
|
+
expected = @options[:request_type].name
|
135
|
+
actual = @options[:request].class.name
|
136
|
+
fail :INVALID_REQUEST_PROTO, 'Expected request type to be type of %s, got %s instead' % [expected, actual]
|
137
|
+
end
|
138
|
+
|
139
|
+
log_debug '[client-cnxn] Sending Request: %s' % request_wrapper.inspect
|
140
|
+
request_buffer = Protobuf::Rpc::Buffer.new(:write, request_wrapper)
|
141
|
+
send_data(request_buffer.write)
|
142
|
+
@stats.request_size = request_buffer.size
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_response
|
146
|
+
# Close up the connection as we no longer need it
|
147
|
+
close_connection
|
148
|
+
|
149
|
+
log_debug '[client-cnxn] Parsing response from server (connection closed)'
|
150
|
+
@stats.response_size = @buffer.size
|
151
|
+
|
152
|
+
# Parse out the raw response
|
153
|
+
response_wrapper = Protobuf::Socketrpc::Response.new
|
154
|
+
response_wrapper.parse_from_string @buffer.data
|
155
|
+
|
156
|
+
# Determine success or failure based on parsed data
|
157
|
+
if response_wrapper.has_field? :error_reason
|
158
|
+
log_debug '[client-cnxn] Error response parsed'
|
159
|
+
|
160
|
+
# fail the call if we already know the client is failed
|
161
|
+
# (don't try to parse out the response payload)
|
162
|
+
fail response_wrapper.error_reason, response_wrapper.error
|
163
|
+
else
|
164
|
+
log_debug '[client-cnxn] Successful response parsed'
|
165
|
+
|
166
|
+
# Ensure client_response is an instance
|
167
|
+
response_type = @options[:response_type].new
|
168
|
+
parsed = response_type.parse_from_string(response_wrapper.response_proto.to_s)
|
169
|
+
|
170
|
+
if parsed.nil? and not response_wrapper.has_field?(:error_reason)
|
171
|
+
fail :BAD_RESPONSE_PROTO, 'Unable to parse response from server'
|
172
|
+
else
|
173
|
+
succeed parsed
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def fail code, message
|
179
|
+
@state = STATES[:failed]
|
180
|
+
@error.code = code.is_a?(Symbol) ? Protobuf::Socketrpc::ErrorReason.values[code] : code
|
181
|
+
@error.message = message
|
182
|
+
log_debug '[client-cnxn] Server failed request (invoking on_failure): %s' % @error.inspect
|
183
|
+
begin
|
184
|
+
@stats.end
|
185
|
+
@stats.log_stats
|
186
|
+
@failure_cb.call(@error) unless @failure_cb.nil?
|
187
|
+
rescue
|
188
|
+
log_error '[client-cnxn] Failure callback error encountered: %s' % $!.message
|
189
|
+
log_error '[client-cnxn] %s' % $!.backtrace.join("\n")
|
190
|
+
raise
|
191
|
+
ensure
|
192
|
+
complete
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def succeed response
|
197
|
+
@state = STATES[:succeeded]
|
198
|
+
begin
|
199
|
+
log_debug '[client-cnxn] Server succeeded request (invoking on_success)'
|
200
|
+
@stats.end
|
201
|
+
@stats.log_stats
|
202
|
+
@success_cb.call(response) unless @success_cb.nil?
|
203
|
+
complete
|
204
|
+
rescue
|
205
|
+
log_error '[client-cnxn] Success callback error encountered: %s' % $!.message
|
206
|
+
log_error '[client-cnxn] %s' % $!.backtrace.join("\n")
|
207
|
+
fail :RPC_ERROR, 'An exception occurred while calling on_success: %s' % $!.message
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def complete
|
212
|
+
@state = STATES[:completed]
|
213
|
+
begin
|
214
|
+
log_debug '[client-cnxn] Response proceessing complete'
|
215
|
+
@success_cb = @failure_cb = nil
|
216
|
+
@complete_cb.call(@state) unless @complete_cb.nil?
|
217
|
+
rescue
|
218
|
+
log_error '[client-cnxn] Complete callback error encountered: %s' % $!.message
|
219
|
+
log_error '[client-cnxn] %s' % $!.backtrace.join("\n")
|
220
|
+
raise
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'protobuf/rpc/connectors/base'
|
2
|
+
require 'protobuf/rpc/connectors/em_client'
|
3
|
+
|
4
|
+
module Protobuf
|
5
|
+
module Rpc
|
6
|
+
module Connectors
|
7
|
+
class EventMachine < Base
|
8
|
+
|
9
|
+
def initialize options
|
10
|
+
super(EMClient::DEFAULT_OPTIONS.merge(options))
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_request
|
14
|
+
Thread.new { EM.run } unless EM.reactor_running?
|
15
|
+
|
16
|
+
f = Fiber.current
|
17
|
+
|
18
|
+
EM.schedule do
|
19
|
+
log_debug '[client] Scheduling EventMachine client request to be created on next tick'
|
20
|
+
cnxn = EMClient.connect options, &ensure_cb
|
21
|
+
cnxn.on_success &success_cb if success_cb
|
22
|
+
cnxn.on_failure &ensure_cb
|
23
|
+
cnxn.on_complete { resume_fiber f } unless async?
|
24
|
+
log_debug '[client] Connection scheduled'
|
25
|
+
end
|
26
|
+
|
27
|
+
return set_timeout_and_validate_fiber unless async?
|
28
|
+
return true
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a proc that ensures any errors will be returned to the client
|
32
|
+
#
|
33
|
+
# If a failure callback was set, just use that as a direct assignment
|
34
|
+
# otherwise implement one here that simply throws an exception, since we
|
35
|
+
# don't want to swallow the black holes.
|
36
|
+
#
|
37
|
+
def ensure_cb
|
38
|
+
@ensure_cb ||= begin
|
39
|
+
cbk = nil
|
40
|
+
if @failure_cb
|
41
|
+
cbk = @failure_cb
|
42
|
+
else
|
43
|
+
cbk = proc do |error|
|
44
|
+
raise '%s: %s' % [error.code.name, error.message]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
cbk
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def set_timeout_and_validate_fiber
|
54
|
+
@timeout_timer = EM::add_timer(@options[:timeout]) do
|
55
|
+
message = 'Client timeout of %d seconds expired' % @options[:timeout]
|
56
|
+
err = ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
57
|
+
ensure_cb.call(err)
|
58
|
+
end
|
59
|
+
|
60
|
+
Fiber.yield
|
61
|
+
rescue FiberError
|
62
|
+
message = "Synchronous calls must be in 'EM.fiber_run' block"
|
63
|
+
err = ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
64
|
+
ensure_cb.call(err)
|
65
|
+
end
|
66
|
+
|
67
|
+
def resume_fiber(fib)
|
68
|
+
EM::cancel_timer(@timeout_timer)
|
69
|
+
fib.resume(true)
|
70
|
+
rescue => ex
|
71
|
+
log_error 'An exception occurred while waiting for server response:'
|
72
|
+
log_error ex.message
|
73
|
+
log_error ex.backtrace.join("\n")
|
74
|
+
|
75
|
+
message = 'Synchronous client failed: %s' % ex.message
|
76
|
+
err = ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
77
|
+
ensure_cb.call(err)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/protobuf/rpc/service.rb
CHANGED
@@ -141,8 +141,8 @@ module Protobuf
|
|
141
141
|
|
142
142
|
# Callback register for the server when a service
|
143
143
|
# method calls rpc_failed. Called by Service#rpc_failed.
|
144
|
-
def on_rpc_failed &
|
145
|
-
@
|
144
|
+
def on_rpc_failed &rpc_failure_cb
|
145
|
+
@rpc_failure_cb = rpc_failure_cb
|
146
146
|
end
|
147
147
|
|
148
148
|
# Automatically fail a service method.
|
@@ -150,14 +150,14 @@ module Protobuf
|
|
150
150
|
# not any way to get around this currently (and I'm not sure you should want to).
|
151
151
|
#
|
152
152
|
def rpc_failed message="RPC Failed while executing service method #{@current_method}"
|
153
|
-
if @
|
153
|
+
if @rpc_failure_cb.nil?
|
154
154
|
exc = RuntimeError.new 'Unable to invoke rpc_failed, no failure callback is setup.'
|
155
155
|
log_error exc.message
|
156
156
|
raise exc
|
157
157
|
end
|
158
158
|
error = message.is_a?(String) ? RpcFailed.new(message) : message
|
159
159
|
log_warn '[service] RPC Failed: %s' % error.message
|
160
|
-
@
|
160
|
+
@rpc_failure_cb.call(error)
|
161
161
|
end
|
162
162
|
|
163
163
|
# Callback register for the server to be notified
|
data/lib/protobuf/version.rb
CHANGED
data/protobuf.gemspec
CHANGED
@@ -5,10 +5,10 @@ require "protobuf/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = 'protobuf'
|
7
7
|
s.version = Protobuf::VERSION
|
8
|
-
s.date = %q{2011-
|
8
|
+
s.date = %q{2011-12-07}
|
9
9
|
|
10
|
-
s.authors = ['BJ Neilsen']
|
11
|
-
s.email = ["bj.neilsen@gmail.com"]
|
10
|
+
s.authors = ['BJ Neilsen', 'Brandon Dewitt']
|
11
|
+
s.email = ["bj.neilsen@gmail.com", "brandonsdewitt@gmail.com"]
|
12
12
|
s.homepage = %q{https://github.com/localshred/protobuf}
|
13
13
|
s.summary = 'Ruby implementation for Protocol Buffers. Works with other protobuf rpc implementations (e.g. Java, Python, C++).'
|
14
14
|
s.description = s.summary + "\n\nThis gem has diverged from https://github.com/macks/ruby-protobuf. All credit for serialization and rprotoc work most certainly goes to the original authors. All RPC implementation code (client/server/service) was written and is maintained by this author. Attempts to reconcile the original codebase with the current RPC implementation went unsuccessful."
|
data/spec/helper/all.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'lib/protobuf/rpc/server'
|
2
|
+
require 'spec/proto/test_service_impl'
|
3
|
+
|
4
|
+
module StubProtobufServerFactory
|
5
|
+
def self.build(delay)
|
6
|
+
new_server = Class.new(Protobuf::Rpc::Server) do
|
7
|
+
class << self
|
8
|
+
def sleep_interval
|
9
|
+
@sleep_interval
|
10
|
+
end
|
11
|
+
|
12
|
+
def sleep_interval=(si)
|
13
|
+
@sleep_interval = si
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def post_init
|
18
|
+
sleep self.class.sleep_interval
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
new_server.sleep_interval = delay
|
24
|
+
return new_server
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class StubServer
|
29
|
+
def initialize(delay = 0, port = 9191)
|
30
|
+
@server_handle = EventMachine::start_server("127.0.0.1", port, StubProtobufServerFactory.build(delay))
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
EventMachine.stop_server(@server_handle)
|
35
|
+
end
|
36
|
+
end
|