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.
Files changed (42) hide show
  1. data/Gemfile.lock +1 -1
  2. data/README.md +138 -126
  3. data/bin/rpc_server +2 -2
  4. data/bin/rprotoc +2 -2
  5. data/examples/reading_a_message.rb +3 -3
  6. data/examples/writing_a_message.rb +3 -3
  7. data/lib/protobuf.rb +3 -0
  8. data/lib/protobuf/compiler/nodes.rb +1 -1
  9. data/lib/protobuf/compiler/proto_parser.rb +16 -16
  10. data/lib/protobuf/compiler/visitors.rb +11 -25
  11. data/lib/protobuf/descriptor/descriptor_builder.rb +58 -58
  12. data/lib/protobuf/descriptor/field_descriptor.rb +2 -2
  13. data/lib/protobuf/ext/eventmachine.rb +16 -0
  14. data/lib/protobuf/message/decoder.rb +6 -6
  15. data/lib/protobuf/message/field.rb +14 -14
  16. data/lib/protobuf/message/message.rb +4 -4
  17. data/lib/protobuf/rpc/client.rb +42 -183
  18. data/lib/protobuf/rpc/connector.rb +19 -0
  19. data/lib/protobuf/rpc/connectors/base.rb +29 -0
  20. data/lib/protobuf/rpc/connectors/em_client.rb +227 -0
  21. data/lib/protobuf/rpc/connectors/eventmachine.rb +84 -0
  22. data/lib/protobuf/rpc/connectors/socket.rb +14 -0
  23. data/lib/protobuf/rpc/service.rb +4 -4
  24. data/lib/protobuf/version.rb +1 -1
  25. data/protobuf.gemspec +3 -3
  26. data/spec/helper/all.rb +13 -0
  27. data/spec/helper/server.rb +36 -0
  28. data/spec/helper/tolerance_matcher.rb +40 -0
  29. data/spec/spec_helper.rb +3 -5
  30. data/spec/unit/rpc/client_spec.rb +174 -0
  31. data/spec/unit/rpc/connector_spec.rb +36 -0
  32. data/spec/unit/rpc/connectors/base_spec.rb +77 -0
  33. data/spec/unit/rpc/connectors/eventmachine/client_spec.rb +0 -0
  34. data/spec/unit/rpc/connectors/eventmachine_spec.rb +0 -0
  35. data/spec/unit/{server_spec.rb → rpc/server_spec.rb} +0 -0
  36. data/spec/unit/{service_spec.rb → rpc/service_spec.rb} +0 -0
  37. metadata +79 -63
  38. data/lib/protobuf/compiler/template/rpc_bin.erb +0 -4
  39. data/lib/protobuf/compiler/template/rpc_client.erb +0 -18
  40. data/lib/protobuf/compiler/template/rpc_service.erb +0 -25
  41. data/lib/protobuf/rpc/client_connection.rb +0 -225
  42. 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
@@ -0,0 +1,14 @@
1
+ require 'protobuf/rpc/connectors/base'
2
+
3
+ module Protobuf
4
+ module Rpc
5
+ module Connectors
6
+ class Socket < Base
7
+
8
+ def send_request
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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 &rpc_failure_callback
145
- @rpc_failure_callback = rpc_failure_callback
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 @rpc_failure_callback.nil?
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
- @rpc_failure_callback.call(error)
160
+ @rpc_failure_cb.call(error)
161
161
  end
162
162
 
163
163
  # Callback register for the server to be notified
@@ -1,3 +1,3 @@
1
1
  module Protobuf
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.1'
3
3
  end
@@ -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-11-06}
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."
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ require 'helper/tolerance_matcher'
5
+ require 'helper/server'
6
+
7
+ def now
8
+ Time.new.to_f
9
+ end
10
+
11
+ RSpec.configure do |con|
12
+ con.include(Sander6::CustomMatchers)
13
+ end
@@ -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