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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- protobuf (1.0.0)
4
+ protobuf (1.0.1)
5
5
  eventmachine (~> 0.12.10)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- protobuf
2
- ========
1
+ # protobuf
3
2
 
4
3
  Protobuf is an implementation of [Google's protocol buffers][google-pb] in ruby. It's a gem for managing 3 things:
5
4
 
@@ -9,85 +8,93 @@ Protobuf is an implementation of [Google's protocol buffers][google-pb] in ruby.
9
8
 
10
9
  So let's dive in and see how to work with all three.
11
10
 
12
- 1. Compile `.proto` definitions to ruby
13
- =======================================
11
+ ## 1. Compile `.proto` definitions to ruby
14
12
 
15
13
  Protocol Buffers are great because they allow you to clearly define data storage or data transfer packets. Google officially supports Java, C++, and Python for compilation and usage. Let's make it ruby aware!
16
14
 
17
15
  Let's say you have a `defs.proto` file that defines a User message.
18
16
 
19
- package mycompany;
20
- message User {
21
- required string first_name = 1;
22
- required string last_name = 1;
23
- }
17
+ ```
18
+ package mycompany;
19
+ message User {
20
+ required string first_name = 1;
21
+ required string last_name = 1;
22
+ }
24
23
 
25
24
  Now let's compile that definition to ruby:
26
25
 
27
- $ rprotoc defs.proto -o ./lib
26
+ $ rprotoc defs.proto -o ./lib
27
+ ```
28
28
 
29
29
  The previous line will take whatever is defined in defs.proto and output ruby classes to the `./lib` directory, obeying the package directive. Assuming that's all defs.proto had defined, `./lib` should now look like this:
30
30
 
31
- - lib
32
- |- mycompany
33
- |- defs.pb.rb
31
+ ```
32
+ - lib
33
+ |- mycompany
34
+ |- defs.pb.rb
35
+ ```
34
36
 
35
37
  And `defs.pb.rb` should look like this:
36
38
 
37
- module Mycompany
38
- class User
39
- optional :string, :first_name, 1
40
- optional :string, :last_name, 2
41
- end
42
- end
39
+ ```ruby
40
+ module Mycompany
41
+ class User
42
+ optional :string, :first_name, 1
43
+ optional :string, :last_name, 2
44
+ end
45
+ end
46
+ ```
43
47
 
44
48
  You can then use that class just like normal:
45
49
 
46
- require 'lib/mycompany/user.pb'
47
-
48
- # dot notation reading/writing fields
49
- user = Mycompany::User.new
50
- user.first_name = "Lloyd"
51
- user.last_name = "Christmas"
52
- user.first_name # => "Lloyd"
53
-
54
- # or pass in the fields as a hash to the initializer
55
- user = Mycompany::User.new :first_name => "Lloyd", :last_name => "Christmas"
56
- user.first_name # => Lloyd
57
- user.last_name # => Christmas
50
+ ```ruby
51
+ require 'lib/mycompany/user.pb'
58
52
 
59
- ------------------
53
+ # dot notation reading/writing fields
54
+ user = Mycompany::User.new
55
+ user.first_name = "Lloyd"
56
+ user.last_name = "Christmas"
57
+ user.first_name # => "Lloyd"
60
58
 
61
- 2. RPC
62
- ======
59
+ # or pass in the fields as a hash to the initializer
60
+ user = Mycompany::User.new :first_name => "Lloyd", :last_name => "Christmas"
61
+ user.first_name # => Lloyd
62
+ user.last_name # => Christmas
63
+ ```
64
+
65
+ ## 2. RPC
63
66
 
64
67
  RPC is one of many technologies that tries to solve the problem of getting smaller pieces of data from one place to another. Many will argue for or against RPC and its usefulness, but I'm not going to do that here. Google's Protocol Buffers relies on RPC and that's why you're here.
65
68
 
66
69
  Any discussion about RPC leads to a discussion about clients and servers and the remote procedures themselves. For our purposes, we'll talk about a `Client` (process that is calling the server/service), a `Service` (the remote procedure), and a `Server` (the process that manages one or more services). We'll start with the Service first.
67
70
 
68
- **Services**
71
+ ### Services
69
72
 
70
73
  Services are simply classes that have endpoint methods defined. Here's what one looks like in protobuf:
71
74
 
72
- message UserRequest {
73
- optional string email = 1;
74
- }
75
- message UserList {
76
- repeated User users = 1;
77
- }
78
- service UserService {
79
- rpc Find (UserRequest) returns (UserList);
80
- }
75
+ ```
76
+ message UserRequest {
77
+ optional string email = 1;
78
+ }
79
+ message UserList {
80
+ repeated User users = 1;
81
+ }
82
+ service UserService {
83
+ rpc Find (UserRequest) returns (UserList);
84
+ }
85
+ ```
81
86
 
82
87
  And the equivalent ruby stub for the service (generated with `rprotoc`):
83
88
 
84
- # lib/mycompany/user_service.rb
85
- module Mycompany
86
- class UserService < Protobuf::Rpc::Service
87
- rpc :find, UserRequest, UserList
88
- end
89
- end
90
-
89
+ ```ruby
90
+ # lib/mycompany/user_service.rb
91
+ module Mycompany
92
+ class UserService < Protobuf::Rpc::Service
93
+ rpc :find, UserRequest, UserList
94
+ end
95
+ end
96
+ ```
97
+
91
98
  Recognize that the extra messages would actually have gone into the `defs.pb.rb` file while the service stub would receive it's own file at `user_service.rb`.
92
99
 
93
100
  **Important Note: The *stubbed* class here is a *stub*. You should not alter it directly in any way as it will break your definition. Read on to learn how to use this stub.**
@@ -96,23 +103,25 @@ Did you read the note above? Go read it. I'll wait.
96
103
 
97
104
  Ok, now that you have a compiled service stub, you'll want to require it from `lib` and implement the methods. You'll notice when you compile the stub there is a large comment at the top of the file. You can use this code comment to start your real implementation. Go ahead and copy it to your services directory (probably `app/services` if we're in rails).
98
105
 
99
- # app/services/user_service.rb
100
- require 'lib/mycompany/user_service'
101
- module Mycompany
102
- class UserService
103
-
104
- # request -> Mycompany::UserRequest
105
- # response -> Mycompany::UserResponse
106
- def find
107
- # request.email will be the unpacked string that was sent by the client request
108
- User.find_by_email(request.email).each do |user|
109
- # must only use a proto instance of Mycompany::User when appending to the `users` field
110
- response.users << user.to_proto
111
- end
112
- end
113
-
106
+ ```ruby
107
+ # app/services/user_service.rb
108
+ require 'lib/mycompany/user_service'
109
+ module Mycompany
110
+ class UserService
111
+
112
+ # request -> Mycompany::UserRequest
113
+ # response -> Mycompany::UserResponse
114
+ def find
115
+ # request.email will be the unpacked string that was sent by the client request
116
+ User.find_by_email(request.email).each do |user|
117
+ # must only use a proto instance of Mycompany::User when appending to the `users` field
118
+ response.users << user.to_proto
114
119
  end
115
120
  end
121
+
122
+ end
123
+ end
124
+ ```
116
125
 
117
126
  Simply implement the instance method for the defined rpc. No other methods will be allowed in the class (even helpers or private methods). An implicit `request` and `response` object are provided for you, pre-instantiated, and in the case of the request, already are populated with the data that was sent by the client.
118
127
 
@@ -120,24 +129,29 @@ If you need to create your own response object (a valid case), be sure to assign
120
129
 
121
130
  Triggering an error from the service is simple:
122
131
 
123
- #...
124
- def find
125
- if request.email.blank?
126
- rpc_failed 'Unable to find user without an email'
127
- else
128
- # query/populate response
129
- end
130
- end
131
-
132
+ ```ruby
133
+ #...
134
+ def find
135
+ if request.email.blank?
136
+ rpc_failed 'Unable to find user without an email'
137
+ else
138
+ # query/populate response
139
+ end
140
+ end
141
+ #...
142
+ ```
143
+
132
144
  This means that the client's `on_failure` callback will be invoked instead of the `on_success` callback. Read more below on client callbacks.
133
145
 
134
146
  I find it very convenient to use a CRUD-style interface when defining certain data services, though this is certainly not always the case.
135
147
 
136
- **Servers**
148
+ ### Servers
137
149
 
138
150
  A service is nothing without being hooked up to a socket. It's the nerdy kid waiting by the telephone for someone to call without knowing that the phone company disconnected their house. Sad and pathetic. So hook the phone lines!
139
151
 
140
- $ rpc_server -o myserver.com -p 9939 -e production -l ./log/protobuf.log config/environment.rb
152
+ ```
153
+ $ rpc_server -o myserver.com -p 9939 -e production -l ./log/protobuf.log config/environment.rb
154
+ ```
141
155
 
142
156
  The previous call will start an EventMachine server running on the given host and port which will load your application into memory. You certainly don't have to run rails or any other framework, just make sure you have some kind of file that will load your services all into memory. The server doesn't know where you put your code, so tell it.
143
157
 
@@ -145,63 +159,61 @@ Be aware that server needs to be able to translate the socket stream of bytes in
145
159
 
146
160
  Once the server starts, you should see it as a running process with `ps`. Sending a KILL, QUIT, or TERM signal to the pid will result in shutting the server down gracefully.
147
161
 
148
- $ ps aux | grep rpc_server
149
- 1234 ... rpc_server myservice.com:9939
150
-
151
- $ kill -QUIT 1234
152
- rpc_server shutdown
162
+ ```
163
+ $ ps aux | grep rpc_server
164
+ 1234 ... rpc_server myservice.com:9939
153
165
 
154
- **Clients**
166
+ $ kill -QUIT 1234
167
+ rpc_server shutdown
168
+ ```
169
+
170
+ ### Clients
155
171
 
156
172
  A lot of work has gone into making the client calls simple and easy to use yet still powerful. Clients have a DSL that feels very ajax-ish, mostly because of the nature of EventMachine, but I also think it works quite well.
157
173
 
158
- # require the defs from the shared gem/repo
159
- require 'sharedgem/mycompany/user.pb'
160
- require 'sharedgem/mycompany/user_service'
161
-
162
- # Create a request object for the method we are invoking
163
- req = Mycompany::UserRequest.new(:email => 'jeff@gmail.com')
164
-
165
- # Use the UserService class to generate a client, invoke the rpc method
166
- # while passing the request object
167
- Mycompany::UserService.client.find(req) do |c|
168
- # This block will be executed (registering the callbacks)
169
- # before the request actualy occurs.
170
- # the `c` param in this block is the `.client` object
171
- # that is generated from the call above
172
-
173
- # Register a block for execution when the response
174
- # is deemed successful from the service. Accepts
175
- # the unpacked response as its only parameter
176
- c.on_success do |response|
177
- response.users.each do |u|
178
- puts u.inspect
179
- end
180
- end
181
-
182
- # Register a block for execution when the response
183
- # is deemed a failure. This can be either a client-side
184
- # or server-side failure. The object passed the to the
185
- # block has a `message` and a `code` attribute
186
- # to aid in logging/diagnosing the failure.
187
- c.on_failure do |err|
188
- puts 'It failed: ' + err.message
189
- end
174
+ ```ruby
175
+ # require the defs from the shared gem/repo
176
+ require 'sharedgem/mycompany/user.pb'
177
+ require 'sharedgem/mycompany/user_service'
178
+
179
+ # Create a request object for the method we are invoking
180
+ req = Mycompany::UserRequest.new(:email => 'jeff@gmail.com')
181
+
182
+ # Use the UserService class to generate a client, invoke the rpc method
183
+ # while passing the request object
184
+ Mycompany::UserService.client.find(req) do |c|
185
+ # This block will be executed (registering the callbacks)
186
+ # before the request actualy occurs.
187
+ # the `c` param in this block is the `.client` object
188
+ # that is generated from the call above
189
+
190
+ # Register a block for execution when the response
191
+ # is deemed successful from the service. Accepts
192
+ # the unpacked response as its only parameter
193
+ c.on_success do |response|
194
+ response.users.each do |u|
195
+ puts u.inspect
190
196
  end
191
-
192
- Many different options can be passed to the `.client` call above (such as `:async => true` or `:timeout => 600`). See the `lib/protobuf/rpc/client.rb` and `lib/protobuf/rpc/service.rb` files for more documentation. It hsould be noted that the default behavior of `UserService.client` is to return a blocking client. The nature of using Client calls within an framework like Rails demands a blocking call if the response of a web request is dependent on data returned from the service.
193
-
194
- ---
195
-
196
- 3. RPC Interop
197
- ==============
197
+ end
198
+
199
+ # Register a block for execution when the response
200
+ # is deemed a failure. This can be either a client-side
201
+ # or server-side failure. The object passed the to the
202
+ # block has a `message` and a `code` attribute
203
+ # to aid in logging/diagnosing the failure.
204
+ c.on_failure do |err|
205
+ puts 'It failed: ' + err.message
206
+ end
207
+ end
208
+ ```
209
+
210
+ Many different options can be passed to the `.client` call above (such as `:async => true` or `:timeout => 600`). See the `lib/protobuf/rpc/client.rb` and `lib/protobuf/rpc/service.rb` files for more documentation. It should be noted that the default behavior of `UserService.client` is to return a blocking client. The nature of using Client calls within a framework like Rails demands a blocking call if the response of a web request is dependent on data returned from the service.
211
+
212
+ ## 3. RPC Interop
198
213
 
199
214
  The main reason I wrote this gem was to provide a ruby implementation to google's protobuf that worked on the RPC layer with a Java Service layer that was already running [protobuf-socket-rpc][], the supported socket rpc library for protobuf from Google. The [old gem][] did not provide a very robust RPC implementation and it most certainly did not work with the Java stack.
200
215
 
201
- ---
202
-
203
- Accreditation & Caveats
204
- =======================
216
+ ## Accreditation & Caveats
205
217
 
206
218
  It must be noted a large amount of the code in this library was taken from the [ruby-protobuf][old gem] gem. Its authors and I were unable to reach a communication point to be able to merge all of my RPC updates in with their master. Unfortunately I just simply couldn't use their RPC code and so I've decided to diverge from their codeset. I take no credit whatsoever for the (de)serialization and `rprotoc` code generation original work, though I have modified it slightly to be more compliant with my understanding of the pb spec. I want to say thanks to the original devs for the good work they did to get me most of the way there. The code was initially diverged at their 0.4.0 version.
207
219
 
@@ -95,8 +95,8 @@ begin
95
95
  end
96
96
 
97
97
  # Set the name of the process
98
- $0 = 'rpc_server %s:%d' % [server.host, server.port]
99
-
98
+ $0 = 'rpc_server %s:%d %s' % [server.host, server.port, server.app]
99
+
100
100
  # Require the given application file
101
101
  require server.app
102
102
 
@@ -9,8 +9,10 @@ else
9
9
  gem 'protobuf'
10
10
  end
11
11
  require 'protobuf'
12
+ require 'protobuf/version'
12
13
  require 'protobuf/compiler/compiler'
13
14
 
15
+ ::Version = Protobuf::VERSION
14
16
 
15
17
  options = {
16
18
  :proto_path => '.',
@@ -22,8 +24,6 @@ opts.on('-o', '--out <OUT_DIR>', 'Specify the directory in which Ruby source fil
22
24
  opts.on_tail('-v', '--version', 'Show version.'){ puts(opts.ver); exit }
23
25
  opts.on_tail('-h', '--help', 'Show this message.'){ puts(opts.help); exit }
24
26
 
25
- ::Version = Protobuf::VERSION
26
-
27
27
  begin
28
28
  opts.order!
29
29
  rescue OptionParser::ParseError
@@ -9,11 +9,11 @@ def list_people(address_book)
9
9
  puts " E-mail: #{person.email}" unless person.email.empty?
10
10
  person.phone.each do |phone_number|
11
11
  print(case phone_number.type
12
- when Tutorial::Person::PhoneType::MOBILE
12
+ when Tutorial::Person::PhoneType::MOBILE then
13
13
  ' Mobile phone #: '
14
- when Tutorial::Person::PhoneType::HOME
14
+ when Tutorial::Person::PhoneType::HOME then
15
15
  ' Home phone #: '
16
- when Tutorial::Person::PhoneType::WORK
16
+ when Tutorial::Person::PhoneType::WORK then
17
17
  ' Work phone #: '
18
18
  end)
19
19
  puts phone_number.number
@@ -21,11 +21,11 @@ def prompt_for_address(person)
21
21
  print 'Is this a mobile, home, or work phone? '
22
22
  person.phone.last.type =
23
23
  case type = STDIN.gets.strip
24
- when 'mobile'
24
+ when 'mobile' then
25
25
  Tutorial::Person::PhoneType::MOBILE
26
- when 'home'
26
+ when 'home' then
27
27
  Tutorial::Person::PhoneType::HOME
28
- when 'work'
28
+ when 'work' then
29
29
  Tutorial::Person::PhoneType::WORK
30
30
  else
31
31
  puts 'Unknown phone type; leaving as default value.'
@@ -1,3 +1,6 @@
1
+ require 'eventmachine'
2
+ require 'protobuf/ext/eventmachine'
3
+
1
4
  module Protobuf
2
5
  end
3
6
 
@@ -264,7 +264,7 @@ require 'protobuf/message/extend'
264
264
  descriptor.type_name = @type.is_a?(Array) ? @type.join : @type.to_s
265
265
  @opts.each do |key, val|
266
266
  case key.to_sym
267
- when :default
267
+ when :default then
268
268
  descriptor.default_value = val.to_s
269
269
  end
270
270
  end
@@ -256,10 +256,10 @@ module Racc
256
256
  }
257
257
  if code
258
258
  case code
259
- when 1 # yyerror
259
+ when 1 then # yyerror
260
260
  @racc_user_yyerror = true # user_yyerror
261
261
  return -reduce_n
262
- when 2 # yyaccept
262
+ when 2 then # yyaccept
263
263
  return shift_n
264
264
  else
265
265
  raise '[Racc Bug] unknown jump code'
@@ -278,12 +278,12 @@ module Racc
278
278
  # error
279
279
  #
280
280
  case @racc_error_status
281
- when 0
281
+ when 0 then
282
282
  unless arg[21] # user_yyerror
283
283
  nerr += 1
284
284
  on_error @racc_t, @racc_val, @racc_vstack
285
285
  end
286
- when 3
286
+ when 3 then
287
287
  if @racc_t == 0 # is $
288
288
  throw :racc_end_parse, nil
289
289
  end
@@ -482,30 +482,30 @@ module_eval <<'..end lib/protobuf/compiler/proto.y modeval..id110d2bf917', 'lib/
482
482
  def scan
483
483
  until @scanner.eos?
484
484
  case
485
- when match(/\s+/, /\/\/.*/)
485
+ when match(/\s+/, /\/\/.*/) then
486
486
  # skip
487
- when match(/\/\*/)
487
+ when match(/\/\*/) then
488
488
  # C-like comment
489
489
  raise 'EOF inside block comment' until @scanner.scan_until(/\*\//)
490
- when match(/(?:required|optional|repeated|import|package|option|message|extend|enum|service|rpc|returns|group|default|extensions|to|max|double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)\b/)
490
+ when match(/(?:required|optional|repeated|import|package|option|message|extend|enum|service|rpc|returns|group|default|extensions|to|max|double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)\b/) then
491
491
  yield [@token, @token.to_sym]
492
- when match(/[+-]?\d*\.\d+([Ee][\+-]?\d+)?/)
492
+ when match(/[+-]?\d*\.\d+([Ee][\+-]?\d+)?/) then
493
493
  yield [:FLOAT_LITERAL, @token.to_f]
494
- when match(/[+-]?[1-9]\d*(?!\.)/, /0(?![.xX0-9])/)
494
+ when match(/[+-]?[1-9]\d*(?!\.)/, /0(?![.xX0-9])/) then
495
495
  yield [:DEC_INTEGER, @token.to_i]
496
- when match(/0[xX]([A-Fa-f0-9])+/)
496
+ when match(/0[xX]([A-Fa-f0-9])+/) then
497
497
  yield [:HEX_INTEGER, @token.to_i(0)]
498
- when match(/0[0-7]+/)
498
+ when match(/0[0-7]+/) then
499
499
  yield [:OCT_INTEGER, @token.to_i(0)]
500
- when match(/(true|false)\b/)
500
+ when match(/(true|false)\b/) then
501
501
  yield [:BOOLEAN_LITERAL, @token == 'true']
502
- when match(/"(?:[^"\\]+|\\.)*"/, /'(?:[^'\\]+|\\.)*'/)
502
+ when match(/"(?:[^"\\]+|\\.)*"/, /'(?:[^'\\]+|\\.)*'/) then
503
503
  yield [:STRING_LITERAL, eval(@token)]
504
- when match(/[a-zA-Z_]\w*/)
504
+ when match(/[a-zA-Z_]\w*/) then
505
505
  yield [:IDENT, @token.to_sym]
506
- when match(/[A-Z]\w*/)
506
+ when match(/[A-Z]\w*/) then
507
507
  yield [:CAMEL_IDENT, @token.to_sym]
508
- when match(/./)
508
+ when match(/./) then
509
509
  yield [@token, @token]
510
510
  else
511
511
  raise "parse error around #{@scanner.string[@scanner.pos, 32].inspect}"