gruf 2.4.0 → 2.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +89 -6
- data/gruf.gemspec +1 -1
- data/lib/gruf.rb +1 -0
- data/lib/gruf/configuration.rb +10 -1
- data/lib/gruf/error.rb +2 -1
- data/lib/gruf/instrumentable_grpc_server.rb +61 -0
- data/lib/gruf/server.rb +30 -12
- data/lib/gruf/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1289ff979cec29b1c475fc7fb9292d3eab752229e5147b59f18c107fb34531d
|
4
|
+
data.tar.gz: 55acd4dee980cd8274edbbc7308edc63e09b6a4b19e4a87a6d1d1e72b11dd46f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99959b15a10222393883ac16b80c8550ba35190aa9a9858764687e4349d04db9f696d37fe9ed7fd7bf4db0df8e1841791760eb49de7633392720fe9f8175d733
|
7
|
+
data.tar.gz: 22f6f6f2b9db9bea6768b1f3089c6502d02323e012efdad113becd430a94af4136d8d6f216d4e73079e66d59490ee379c6ba962d1c3f9f9649243fa0b382b57d
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
|
|
2
2
|
|
3
3
|
### Pending release
|
4
4
|
|
5
|
+
### 2.4.1
|
6
|
+
|
7
|
+
- Safer configuration of GRPC::RpcServer. From now on, use `Gruf.rpc_server_options` for the params
|
8
|
+
to be sent to GPRC::RpcServer. Also provide sane defaults for params for GRPC::RpcServer. [#55]
|
9
|
+
- Added ability to monitor `RESOURCE_EXHAUSTED` and `UNIMPLEMENTED`. By setting `event_listener_proc` in
|
10
|
+
the Gruf configuration, you will receive a callback when these events occur. The parameter to your
|
11
|
+
callback will be a symbol (`:thread_pool_exhausted` or `:unimplemented`). Others may be added in the future.
|
12
|
+
|
5
13
|
### 2.4.0
|
6
14
|
|
7
15
|
- Added a hash of error log levels to RequestLogging interceptor, mapping error code to level of logging to use. To
|
data/README.md
CHANGED
@@ -17,8 +17,8 @@ up fast and efficiently at scale. Some of its features include:
|
|
17
17
|
still preserving gRPC BadStatus codes
|
18
18
|
* Server and client execution timings in responses
|
19
19
|
|
20
|
-
gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.5.
|
21
|
-
gruf is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework
|
20
|
+
gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.5.
|
21
|
+
gruf is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework
|
22
22
|
(such as [Grape](https://github.com/ruby-grape/grape), for instance).
|
23
23
|
|
24
24
|
## Installation
|
@@ -86,13 +86,13 @@ class MyInterceptor < Gruf::Interceptors::ClientInterceptor
|
|
86
86
|
end
|
87
87
|
|
88
88
|
::Gruf::Client.new(
|
89
|
-
service: ::Demo::ThingService,
|
89
|
+
service: ::Demo::ThingService,
|
90
90
|
client_options: [
|
91
91
|
interceptors: [MyInterceptor.new]
|
92
92
|
])
|
93
93
|
```
|
94
94
|
|
95
|
-
The `interceptors` option in `client_options` can accept either a `GRPC::ClientInterceptor` class or a
|
95
|
+
The `interceptors` option in `client_options` can accept either a `GRPC::ClientInterceptor` class or a
|
96
96
|
`Gruf::Interceptors::ClientInterceptor`, since the latter just extends the former. The gruf client interceptors
|
97
97
|
take an optional alternative approach: rather than having separate methods for each request type, it provides a default
|
98
98
|
`call` method that passes in a `RequestContext` object, which has the following attributes:
|
@@ -178,8 +178,8 @@ Gruf comes baked in with a few command-line options for the binstub:
|
|
178
178
|
| --suppress-default-interceptors | Do not use the default interceptors for the server |
|
179
179
|
| --backtrace-on-error | Push backtraces on exceptions to the error serializer |
|
180
180
|
|
181
|
-
These options will override whatever is passed in the Gruf configure block or
|
182
|
-
initializer.
|
181
|
+
These options will override whatever is passed in the Gruf configure block or
|
182
|
+
initializer.
|
183
183
|
|
184
184
|
### Basic Authentication
|
185
185
|
|
@@ -255,6 +255,18 @@ Gruf.configure do |c|
|
|
255
255
|
end
|
256
256
|
```
|
257
257
|
|
258
|
+
### GRPC::RpcServer configuration
|
259
|
+
To customize parameters for the underlying GRPC::RpcServer, such as the size of the gRPC thread pool,
|
260
|
+
you can pass them in via Gruf.rpc\_server\_options.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
Gruf.configure do |c|
|
264
|
+
# The size of the underlying thread pool. No more concurrent requests can be made
|
265
|
+
# than the size of the thread pool.
|
266
|
+
c.rpc_server_options[:pool_size] = 100
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
258
270
|
## Server Interceptors
|
259
271
|
|
260
272
|
gruf supports interceptors around the grpc server calls, allowing you to perform actions around your service
|
@@ -395,6 +407,77 @@ It's important to maintain a safe blacklist should you decide to log parameters;
|
|
395
407
|
parameter sanitization on its own. We also recommend blacklisting parameters that may contain
|
396
408
|
very large values (such as binary or json data).
|
397
409
|
|
410
|
+
## Testing with RSpec
|
411
|
+
|
412
|
+
### Controllers
|
413
|
+
|
414
|
+
In order to test your controller, you first need to mock a GRPC ActiveCall. You can create the following file under `/spec/support/` path of your project:
|
415
|
+
```
|
416
|
+
require 'grpc'
|
417
|
+
|
418
|
+
module Rpc
|
419
|
+
module Test
|
420
|
+
class Call
|
421
|
+
attr_reader :metadata
|
422
|
+
|
423
|
+
def initialize(md = nil)
|
424
|
+
@metadata = md || { 'authorization' => "Basic #{Base64.encode64('grpc:magic')}" }
|
425
|
+
end
|
426
|
+
|
427
|
+
def output_metadata
|
428
|
+
@output_metadata ||= {}
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
Imagine you have the following controller to test:
|
436
|
+
```
|
437
|
+
class ThingController < ::Gruf::Controllers::Base
|
438
|
+
bind ::Rpc::ThingService::Service
|
439
|
+
|
440
|
+
def get_thing
|
441
|
+
thing = Rpc::Thing.new(id: message_id, name: 'Foo')
|
442
|
+
Rpc::GetThingResponse.new(thing: thing)
|
443
|
+
end
|
444
|
+
|
445
|
+
private
|
446
|
+
|
447
|
+
def message_id
|
448
|
+
request.message.id
|
449
|
+
end
|
450
|
+
end
|
451
|
+
```
|
452
|
+
|
453
|
+
You can stub it in the specs this way:
|
454
|
+
```
|
455
|
+
describe ThingController do
|
456
|
+
let(:rpc_service) { ::Rpc::ThingService::Service }
|
457
|
+
let(:rpc_desc) { Rpc::ThingService::Service.rpc_descs.values.first }
|
458
|
+
let(:message) { Rpc::GetThingRequest.new(id: 1) }
|
459
|
+
let(:controller) do
|
460
|
+
described_class.new(
|
461
|
+
method_key: :get_thing,
|
462
|
+
service: rpc_service,
|
463
|
+
active_call: Rpc::Test::Call.new,
|
464
|
+
message: message,
|
465
|
+
rpc_desc: rpc_desc
|
466
|
+
)
|
467
|
+
end
|
468
|
+
|
469
|
+
describe '.call' do
|
470
|
+
context 'with :get_thing as an argument' do
|
471
|
+
let(:result) { controller.call(:get_thing) }
|
472
|
+
|
473
|
+
it 'returns an instance of Rpc::GetThingResponse' do
|
474
|
+
expect(result).to be_instance_of(Rpc::GetThingResponse)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
```
|
480
|
+
|
398
481
|
## Plugins
|
399
482
|
|
400
483
|
You can build your own hooks and middleware for gruf; here's a list of known open source gems for
|
data/gruf.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.licenses = ['MIT']
|
26
26
|
|
27
27
|
spec.summary = 'gRPC Ruby Framework'
|
28
|
-
spec.description =
|
28
|
+
spec.description = 'gRPC Ruby Framework for building complex gRPC applications at scale'
|
29
29
|
spec.homepage = 'https://github.com/bigcommerce/gruf'
|
30
30
|
|
31
31
|
spec.files = Dir['README.md', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'lib/**/*', 'gruf.gemspec']
|
data/lib/gruf.rb
CHANGED
data/lib/gruf/configuration.rb
CHANGED
@@ -37,7 +37,16 @@ module Gruf
|
|
37
37
|
use_default_interceptors: true,
|
38
38
|
backtrace_on_error: false,
|
39
39
|
use_exception_message: true,
|
40
|
-
internal_error_message: 'Internal Server Error'
|
40
|
+
internal_error_message: 'Internal Server Error',
|
41
|
+
event_listener_proc: nil,
|
42
|
+
rpc_server_options: {
|
43
|
+
pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
|
44
|
+
max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
|
45
|
+
poll_period: GRPC::RpcServer::DEFAULT_POLL_PERIOD,
|
46
|
+
pool_keep_alive: GRPC::Pool::DEFAULT_KEEP_ALIVE,
|
47
|
+
connect_md_proc: nil,
|
48
|
+
server_args: {}
|
49
|
+
}.freeze
|
41
50
|
}.freeze
|
42
51
|
|
43
52
|
attr_accessor *VALID_CONFIG_KEYS.keys
|
data/lib/gruf/error.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Gruf
|
2
|
+
##
|
3
|
+
# A subclass of GRPC::RpcServer that can be used for enhanced monitoring
|
4
|
+
# of thread pool. Note that since we are reaching into the internals of
|
5
|
+
# GRPC::RpcServer, we need to watch the evolution of that class.
|
6
|
+
#
|
7
|
+
class InstrumentableGrpcServer < GRPC::RpcServer
|
8
|
+
##
|
9
|
+
# Add an event_listener_proc that, if supplied, will be called
|
10
|
+
# when interesting events happen in the server.
|
11
|
+
#
|
12
|
+
def initialize(pool_size: DEFAULT_POOL_SIZE,
|
13
|
+
max_waiting_requests: DEFAULT_MAX_WAITING_REQUESTS,
|
14
|
+
poll_period: DEFAULT_POLL_PERIOD,
|
15
|
+
pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
|
16
|
+
connect_md_proc: nil,
|
17
|
+
server_args: {},
|
18
|
+
interceptors: [],
|
19
|
+
event_listener_proc: nil)
|
20
|
+
# Call the base class initializer
|
21
|
+
super(
|
22
|
+
pool_size: pool_size,
|
23
|
+
max_waiting_requests: max_waiting_requests,
|
24
|
+
poll_period: poll_period,
|
25
|
+
pool_keep_alive: pool_keep_alive,
|
26
|
+
connect_md_proc: connect_md_proc,
|
27
|
+
server_args: server_args,
|
28
|
+
interceptors: interceptors
|
29
|
+
)
|
30
|
+
|
31
|
+
# Save event listener for later
|
32
|
+
@event_listener_proc = event_listener_proc
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Notify the event listener of something interesting
|
37
|
+
#
|
38
|
+
def notify(event)
|
39
|
+
return unless @event_listener_proc && @event_listener_proc.respond_to?(:call)
|
40
|
+
@event_listener_proc.call(event)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Hook into the thread pool availability check for monitoring
|
45
|
+
#
|
46
|
+
def available?(an_rpc)
|
47
|
+
super.tap do |obj|
|
48
|
+
notify(:thread_pool_exhausted) unless obj
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Hook into the method implementation check for monitoring
|
54
|
+
#
|
55
|
+
def implemented?(an_rpc)
|
56
|
+
super.tap do |obj|
|
57
|
+
notify(:unimplemented) unless obj
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/gruf/server.rb
CHANGED
@@ -31,11 +31,11 @@ module Gruf
|
|
31
31
|
##
|
32
32
|
# Initialize the server and load and setup the services
|
33
33
|
#
|
34
|
-
# @param [Hash]
|
34
|
+
# @param [Hash] opts
|
35
35
|
#
|
36
|
-
def initialize(
|
37
|
-
@options =
|
38
|
-
@interceptors =
|
36
|
+
def initialize(opts = {})
|
37
|
+
@options = opts || {}
|
38
|
+
@interceptors = opts.fetch(:interceptor_registry, Gruf.interceptors)
|
39
39
|
@interceptors = Gruf::Interceptors::Registry.new unless @interceptors.is_a?(Gruf::Interceptors::Registry)
|
40
40
|
@services = []
|
41
41
|
@started = false
|
@@ -43,7 +43,8 @@ module Gruf
|
|
43
43
|
@stop_server_cv = ConditionVariable.new
|
44
44
|
@stop_server_mu = Monitor.new
|
45
45
|
@server_mu = Monitor.new
|
46
|
-
@hostname =
|
46
|
+
@hostname = opts.fetch(:hostname, Gruf.server_binding_url)
|
47
|
+
@event_listener_proc = opts.fetch(:event_listener_proc, Gruf.event_listener_proc)
|
47
48
|
setup
|
48
49
|
end
|
49
50
|
|
@@ -53,7 +54,24 @@ module Gruf
|
|
53
54
|
def server
|
54
55
|
@server_mu.synchronize do
|
55
56
|
@server ||= begin
|
56
|
-
|
57
|
+
# For backward compatibility, we allow these options to be passed directly
|
58
|
+
# in the Gruf::Server options, or via Gruf.rpc_server_options.
|
59
|
+
server_options = {
|
60
|
+
pool_size: options.fetch(:pool_size, Gruf.rpc_server_options[:pool_size]),
|
61
|
+
max_waiting_requests: options.fetch(:max_waiting_requests, Gruf.rpc_server_options[:max_waiting_requests]),
|
62
|
+
poll_period: options.fetch(:poll_period, Gruf.rpc_server_options[:poll_period]),
|
63
|
+
pool_keep_alive: options.fetch(:pool_keep_alive, Gruf.rpc_server_options[:pool_keep_alive]),
|
64
|
+
connect_md_proc: options.fetch(:connect_md_proc, Gruf.rpc_server_options[:connect_md_proc]),
|
65
|
+
server_args: options.fetch(:server_args, Gruf.rpc_server_options[:server_args])
|
66
|
+
}
|
67
|
+
|
68
|
+
server = if @event_listener_proc
|
69
|
+
server_options[:event_listener_proc] = @event_listener_proc
|
70
|
+
Gruf::InstrumentableGrpcServer.new(server_options)
|
71
|
+
else
|
72
|
+
GRPC::RpcServer.new(server_options)
|
73
|
+
end
|
74
|
+
|
57
75
|
@port = server.add_http2_port(@hostname, ssl_credentials)
|
58
76
|
@services.each { |s| server.handle(s) }
|
59
77
|
server
|
@@ -122,11 +140,11 @@ module Gruf
|
|
122
140
|
#
|
123
141
|
# @param [Class] before_class The interceptor that you want to add the new interceptor before
|
124
142
|
# @param [Class] interceptor_class The Interceptor to add to the registry
|
125
|
-
# @param [Hash]
|
143
|
+
# @param [Hash] opts A hash of options for the interceptor
|
126
144
|
#
|
127
|
-
def insert_interceptor_before(before_class, interceptor_class,
|
145
|
+
def insert_interceptor_before(before_class, interceptor_class, opts = {})
|
128
146
|
raise ServerAlreadyStartedError if @started
|
129
|
-
@interceptors.insert_before(before_class, interceptor_class,
|
147
|
+
@interceptors.insert_before(before_class, interceptor_class, opts)
|
130
148
|
end
|
131
149
|
|
132
150
|
##
|
@@ -134,11 +152,11 @@ module Gruf
|
|
134
152
|
#
|
135
153
|
# @param [Class] after_class The interceptor that you want to add the new interceptor after
|
136
154
|
# @param [Class] interceptor_class The Interceptor to add to the registry
|
137
|
-
# @param [Hash]
|
155
|
+
# @param [Hash] opts A hash of options for the interceptor
|
138
156
|
#
|
139
|
-
def insert_interceptor_after(after_class, interceptor_class,
|
157
|
+
def insert_interceptor_after(after_class, interceptor_class, opts = {})
|
140
158
|
raise ServerAlreadyStartedError if @started
|
141
|
-
@interceptors.insert_after(after_class, interceptor_class,
|
159
|
+
@interceptors.insert_after(after_class, interceptor_class, opts)
|
142
160
|
end
|
143
161
|
|
144
162
|
##
|
data/lib/gruf/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gruf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.4.
|
4
|
+
version: 2.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shaun McCormick
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,7 +108,7 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '4.6'
|
111
|
-
description: gRPC Ruby Framework
|
111
|
+
description: gRPC Ruby Framework for building complex gRPC applications at scale
|
112
112
|
email:
|
113
113
|
- splittingred@gmail.com
|
114
114
|
executables:
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- lib/gruf/errors/debug_info.rb
|
133
133
|
- lib/gruf/errors/field.rb
|
134
134
|
- lib/gruf/errors/helpers.rb
|
135
|
+
- lib/gruf/instrumentable_grpc_server.rb
|
135
136
|
- lib/gruf/interceptors/active_record/connection_reset.rb
|
136
137
|
- lib/gruf/interceptors/authentication/basic.rb
|
137
138
|
- lib/gruf/interceptors/base.rb
|