protobuf 1.0.0 → 1.0.1

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