gruf 2.7.0 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fb4c36a5bd87553944bb4056fcca8875620cffe1d623f8757db6fa0e88f9aee
4
- data.tar.gz: b3903a27dd46302ac99db4cbfa512b7ffc6b3ad9a4faafe83ac22fe83469ae29
3
+ metadata.gz: 406a8f82efb12ee3d64829d9888301899a2268caf34c5ce9f4ada8c50e32700c
4
+ data.tar.gz: 7b2f3d645e99fc49e53a97e75b5c59c6c9c1d35c2544642f52bc7607beb7aab0
5
5
  SHA512:
6
- metadata.gz: ba2ccb4ffe9915044fa8026eefa6e078b746d392af52401a779ad62bcd12ae2ae92cdfce73dc8680564d635bad0f2412b2ff3c740dd0fbfb5291a6a3c3fe70f6
7
- data.tar.gz: c3db4263ae977cede3b9f3adb9cd90984e7906f72c1d005d3b61f977bb64eb3b442e8b4a092475f6fdd9af4ad5510d7e316255bbe59613c4f19d0d8231a48edf
6
+ metadata.gz: 93c5133334095c2319e555d902b8f597c01ec5cdc1ecd48d670fcb247ee45df4304c7623f91f50142cd10277175f22ec7c6a6ea428f70834afeee0bfc3b1373d
7
+ data.tar.gz: 538bee3d11ee7a1fa45b375304857cf4f8daa18f0ef545e36a69a6d147a0b8cd911b759a2bdc7b9247e882829c95ae9ebc29a72852d126985555e8970b24694e
@@ -2,6 +2,10 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
2
2
 
3
3
  ### Pending release
4
4
 
5
+ ### 2.7.1
6
+
7
+ - Add `channel_credentials` option to `Gruf::Client` and `default_channel_credentials` option to `Gruf::Configuration` [#85] [#87]
8
+
5
9
  ### 2.7.0
6
10
 
7
11
  - Add hook support for executing code paths before a server is started, and after a server stops
data/README.md CHANGED
@@ -17,515 +17,20 @@ 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.
20
+ gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.6.
21
21
  gruf is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework
22
- (such as [Grape](https://github.com/ruby-grape/grape), for instance).
22
+ (such as [Grape](https://github.com/ruby-grape/grape) or [dry-rb](https://dry-rb.org/), for instance).
23
23
 
24
- ## Installation
24
+ ### Getting Started
25
25
 
26
- ```ruby
27
- gem 'gruf'
28
- ```
29
-
30
- Then in an initializer or before use:
31
-
32
- ```ruby
33
- require 'gruf'
34
- ```
35
-
36
- Make sure to review [UPGRADING.md](https://github.com/bigcommerce/gruf/blob/master/UPGRADING.md)
37
- if you are upgrading gruf between minor or major versions.
38
-
39
- ### Client
40
-
41
- Add an initializer:
42
-
43
- ```ruby
44
- require 'gruf'
45
-
46
- Gruf.configure do |c|
47
- c.default_client_host = 'grpc.service.com:9003'
48
- end
49
- ```
50
-
51
- If you don't explicitly set `default_client_host`, you will need to pass it into the options, like so:
52
-
53
- ```ruby
54
- client = ::Gruf::Client.new(service: ::Demo::ThingService, options: {hostname: 'grpc.service.com:9003'})
55
- ```
56
-
57
- From there, you can instantiate a client given a stub service (say on an SslCertificates proto with a GetSslCertificate call):
58
-
59
- ```ruby
60
- require 'gruf'
61
-
62
- id = args[:id].to_i.presence || 1
63
-
64
- begin
65
- client = ::Gruf::Client.new(service: ::Demo::ThingService)
66
- response = client.call(:GetMyThing, id: id)
67
- puts response.message.inspect
68
- rescue Gruf::Client::Error => e
69
- puts e.error.inspect
70
- end
71
- ```
72
-
73
- Note this returns a response object. The response object can provide `trailing_metadata` as well as a `execution_time`.
74
-
75
- ### SynchronizedClient
76
-
77
- SynchronizedClient wraps Client with some additional behavior to help prevent generating spikes
78
- of redundant requests. If multiple calls to the same endpoint with the same parameters are made,
79
- the first one will be executed and the following ones will block, waiting for the first result.
80
-
81
- ```ruby
82
- require 'gruf'
83
- require 'thwait'
84
-
85
- id = args[:id].to_i.presence || 1
86
- client = ::Gruf::SynchronizedClient.new(service: ::Demo::ThingService)
87
- thread1 = Thread.new { client.call(:GetMyThing, id: id) }
88
- thread2 = Thread.new { client.call(:GetMyThing, id: id) }
89
- ThreadsWait.all_waits(thread1, thread2)
90
- ```
91
-
92
- In the above example, thread1 will make the rpc call, thread2 will block until the call is complete, and then
93
- will get the same value without making a second rpc call.
94
-
95
- You can also skip this behavior for certain methods if desired.
96
-
97
- ```ruby
98
- require 'gruf'
99
- require 'thwait'
100
-
101
- id = args[:id].to_i.presence || 1
102
- client = ::Gruf::SynchronizedClient.new(service: ::Demo::ThingService, options: { unsynchronized_methods: [:GetMyThing] })
103
- thread1 = Thread.new { client.call(:GetMyThing, id: id) }
104
- thread2 = Thread.new { client.call(:GetMyThing, id: id) }
105
- ThreadsWait.all_waits(thread1, thread2)
106
- ```
107
-
108
- In the above example, thread1 and thread2 will make rpc calls in parallel, in the same way as if you had used
109
- `Gruf::Client`.
110
-
111
- ### Client Interceptors
112
-
113
- Gruf comes with an assistance class for client interceptors that you can use - or you can use the native gRPC core
114
- interceptors. Either way, you pass them into the `client_options` when creating a client:
115
-
116
- ```ruby
117
- class MyInterceptor < Gruf::Interceptors::ClientInterceptor
118
- def call(request_context:)
119
- logger.info "Got method #{request_context.method}!"
120
- yield
121
- end
122
- end
123
-
124
- ::Gruf::Client.new(
125
- service: ::Demo::ThingService,
126
- client_options: {
127
- interceptors: [MyInterceptor.new]
128
- })
129
- ```
130
-
131
- The `interceptors` option in `client_options` can accept either a `GRPC::ClientInterceptor` class or a
132
- `Gruf::Interceptors::ClientInterceptor`, since the latter just extends the former. The gruf client interceptors
133
- take an optional alternative approach: rather than having separate methods for each request type, it provides a default
134
- `call` method that passes in a `RequestContext` object, which has the following attributes:
135
-
136
- * *type* - A Symbol of the type of request (`request_response`, `server_streamer`, etc)
137
- * *requests* An enumerable of requests being sent. For unary requests, this is a single request in an array
138
- * *call* - The `GRPC::ActiveCall` object
139
- * *method* - The Method being called
140
- * *metadata* - The hash of outgoing metadata
141
-
142
- Note that you _must_ yield back the block when building a client interceptor, so that the call can be executed.
143
-
144
- ### Server
145
-
146
- Add an initializer:
147
-
148
- ```ruby
149
- require 'gruf'
150
-
151
- Gruf.configure do |c|
152
- c.server_binding_url = 'grpc.service.com:9003'
153
- end
154
- ```
155
-
156
- Next, setup some handlers based on your proto configurations in `/app/rpc/`. For example, for the Thing service, with a
157
- GetThingReq/GetThingResp call based on this proto:
158
-
159
- ```proto
160
- syntax = "proto3";
161
-
162
- package demo;
163
-
164
- service Jobs {
165
- rpc GetJob(GetJobReq) returns (GetJobResp) { }
166
- }
167
-
168
- message GetJobReq {
169
- uint64 id = 1;
170
- }
171
-
172
- message GetJobResp {
173
- uint64 id = 1;
174
- string name = 2;
175
- }
176
- ```
177
-
178
- You'd have this handler in `/app/rpc/demo/job_controller.rb`
179
-
180
- ```ruby
181
- module Demo
182
- class JobController < ::Gruf::Controllers::Base
183
- bind ::Demo::Jobs::Service
184
-
185
- ##
186
- # @return [Demo::GetJobResp] The job response
187
- #
188
- def get_job
189
- thing = Job.find(request.message.id)
190
-
191
- Demo::GetJobResp.new(id: thing.id)
192
- rescue
193
- fail!(:not_found, :job_not_found, "Failed to find Job with ID: #{request.message.id}")
194
- end
195
- end
196
- end
197
- ```
198
-
199
- Finally, you can start the server by running:
200
-
201
- ```bash
202
- bundle exec gruf
203
- ```
204
-
205
- ### Command-Line Options
206
-
207
- Gruf comes baked in with a few command-line options for the binstub:
208
-
209
- | Option | Description |
210
- | ------ | ----------- |
211
- | -h, --help | Displays the help message |
212
- | -v, --version | Displays the gruf version |
213
- | --host | Specify the server binding host |
214
- | --suppress-default-interceptors | Do not use the default interceptors for the server |
215
- | --backtrace-on-error | Push backtraces on exceptions to the error serializer |
216
-
217
- These options will override whatever is passed in the Gruf configure block or
218
- initializer.
219
-
220
- ### Basic Authentication
221
-
222
- Gruf comes packaged in with a Basic Authentication interceptor. It takes in an array of supported
223
- username and password pairs (or password-only credentials).
224
-
225
- In Server:
226
-
227
- ```ruby
228
- Gruf.configure do |c|
229
- c.interceptors.use(
230
- Gruf::Interceptors::Authentication::Basic,
231
- credentials: [{
232
- username: 'my-username-here',
233
- password: 'my-password-here',
234
- },{
235
- username: 'another-username',
236
- password: 'another-password',
237
- },{
238
- password: 'a-password-only'
239
- }]
240
- )
241
- end
242
- ```
243
-
244
- In Client:
245
-
246
- ```ruby
247
- require 'gruf'
248
-
249
- id = args[:id].to_i.presence || 1
250
-
251
- options = {
252
- username: ENV.fetch('DEMO_THING_SERVICE_USERNAME'),
253
- password: ENV.fetch('DEMO_THING_SERVICE_PASSWORD')
254
- }
255
-
256
- begin
257
- client = ::Gruf::Client.new(service: ::Demo::ThingService, options: options)
258
- response = client.call(:GetMyThing, id: id)
259
- puts response.message.inspect
260
- rescue Gruf::Client::Error => e
261
- puts e.error.inspect
262
- end
263
- ```
264
-
265
- Supporting an array of credentials allow for unique credentials per service, or for easy credential
266
- rotation with zero downtime.
267
-
268
- ### SSL Configuration
269
-
270
- We don't recommend using TLS for gRPC, but instead using something like [linkerd](https://linkerd.io) for TLS
271
- encryption between services. If you need it, however, this library supports TLS.
272
-
273
- For the client, you'll need to point to the public certificate:
274
-
275
- ```ruby
276
- ::Gruf::Client.new(
277
- service: Demo::ThingService,
278
- options: {
279
- ssl_certificate: 'x509 public certificate here',
280
- # OR
281
- ssl_certificate_file: '/path/to/my.crt'
282
- }
283
- )
284
- ```
285
-
286
- If you want to run a server you'll need both the CRT and the key file if you want to do credentialed auth:
287
-
288
- ```ruby
289
- Gruf.configure do |c|
290
- c.use_ssl = true
291
- c.ssl_crt_file = "#{Rails.root}/config/ssl/#{Rails.env}.crt"
292
- c.ssl_key_file = "#{Rails.root}/config/ssl/#{Rails.env}.key"
293
- end
294
- ```
295
-
296
- ### GRPC::RpcServer configuration
297
- To customize parameters for the underlying GRPC::RpcServer, such as the size of the gRPC thread pool,
298
- you can pass them in via Gruf.rpc\_server\_options.
299
-
300
- ```ruby
301
- Gruf.configure do |c|
302
- # The size of the underlying thread pool. No more concurrent requests can be made
303
- # than the size of the thread pool.
304
- c.rpc_server_options[:pool_size] = 100
305
- end
306
- ```
307
-
308
- ## Server Interceptors
309
-
310
- gruf supports interceptors around the grpc server calls, allowing you to perform actions around your service
311
- method calls. This can be used to add tracing data, connection resets in the grpc thread pool, further
312
- instrumentation, and other things.
313
-
314
- Adding a hook is as simple as creating a class that extends `Gruf::Interceptor::ServerInterceptor`,
315
- and a `call` method that yields control to get the method result:
316
-
317
- ```ruby
318
- class MyInterceptor < ::Gruf::Interceptors::ServerInterceptor
319
- def call
320
- yield
321
- end
322
- end
323
- ```
324
-
325
- Interceptors have access to the `request` object, which is the `Gruf::Controller::Request` object
326
- described above.
327
-
328
- ### Failing in an Interceptor
329
-
330
- Interceptors can fail requests with the same method calls as a controller:
331
-
332
- ```ruby
333
- class MyFailingInterceptor < ::Gruf::Interceptors::ServerInterceptor
334
- def call
335
- result = yield # this returns the protobuf message
336
- unless result.dont_hijack
337
- # we'll assume this "dont_hijack" attribute exists on the message for this example
338
- fail!(:internal, :hijacked, 'Hijack all the things!')
339
- end
340
- result
341
- end
342
- end
343
- ```
344
-
345
- Similarly, you can raise `GRPC::BadStatus` calls to trigger similar errors without accompanying metadata.
346
-
347
- ### Configuring Interceptors
348
-
349
- From there, the interceptor can be added to the server manually (if not executing via `bundle exec gruf`):
350
-
351
- ```ruby
352
- server = Gruf::Server.new
353
- server.add_interceptor(MyInterceptor, option_foo: 'value 123')
354
- ```
355
-
356
- Or, alternatively, the more common method of passing them into the `interceptors` configuration hash:
357
-
358
- ```ruby
359
- Gruf.configure do |c|
360
- c.interceptors.use(MyInterceptor, option_foo: 'value 123')
361
- end
362
- ```
363
-
364
- Interceptors each wrap the call and are run recursively within each other. This means that if you have
365
- three interceptors - `Interceptor1`, `Interceptor2`, and `Interceptor3` - they will run in FIFO
366
- (first in, first out) order. `Interceptor1` will run, yielding to `Interceptor2`,
367
- which will then yield to `Interceptor3`, which will then yield to your service method call,
368
- ending the chain.
369
-
370
- You can utilize the `insert_before` and `insert_after` methods to maintain order:
371
-
372
- ```ruby
373
- Gruf.configure do |c|
374
- c.interceptors.use(Interceptor1)
375
- c.interceptors.use(Interceptor2)
376
- c.interceptors.insert_before(Interceptor2, Interceptor3) # 3 will now happen before 2
377
- c.interceptors.insert_after(Interceptor1, Interceptor4) # 4 will now happen after 1
378
- end
379
- ```
380
-
381
- By default, the ActiveRecord Connection Reset interceptor and Output Metadata Timing interceptor
382
- are loaded into gruf unless explicitly told not to via the `use_default_interceptors` configuration
383
- parameter.
384
-
385
- ## Hooks
386
-
387
- Hooks, unlike interceptors, are executed outside of the request chain, such as when a server starts
388
- or stops. They run in FIFO order sequentially and do not wrap one another. They can be used to provide
389
- custom boot sequences, external instrumentation support, or shutdown alerting.
390
-
391
- You can create a hook by extending the `Gruf::Hooks::Base` class and defining the methods
392
- on the hook you wish to implement:
393
-
394
- ```ruby
395
- class MyHook < Gruf::Hooks::Base
396
- def before_server_start(server:)
397
- # do my thing before the server starts
398
- end
399
-
400
- def after_server_stop(server:)
401
- # do my thing after the server stops
402
- end
403
- end
404
-
405
- # Then in an initializer:
406
-
407
- Gruf.configure do |c|
408
- c.hooks.use(MyHook, option_foo: 'value 123')
409
- end
410
- ```
411
-
412
- Exceptions raised in hooks will halt the execution chain and bubble up the stack appropriately.
413
-
414
- ### Available Hook Insertion Points
415
-
416
- Current hook insertion points are:
417
-
418
- * `before_server_start` - Right before the gRPC server starts
419
- * `after_server_stop` - Right after the gRPC server is shutdown
420
-
421
- Note that exceptions raised in `before_server_start` will halt the execution chain for the remaining
422
- `before_server_start` hooks, but will still execute the `after_server_stop` hooks as expected. Exceptions raised
423
- in `after_server_stop` will prevent further `after_server_stop` hooks from running.
424
-
425
- ## Instrumentation
426
-
427
- gruf comes out of the box with a couple of instrumentation interceptors packed in:
428
- output metadata timings and StatsD support.
429
-
430
- ### Output Metadata Timing
431
-
432
- Enabled by default, this will push timings for _successful responses_ through the response output
433
- metadata back to the client.
434
-
435
- ### StatsD
436
-
437
- The StatsD support is not enabled by default. To enable it, you'll want to do:
438
-
439
- ```ruby
440
- Gruf.configure do |c|
441
- c.interceptors.use(
442
- Gruf::Interceptors::Instrumentation::Statsd,
443
- client: ::Statsd.new('my.statsd.host', 8125),
444
- prefix: 'my_application_prefix.rpc'
445
- )
446
- end
447
- ```
448
-
449
- This will measure counts and timings for each endpoint.
450
-
451
- ### Request Logging
452
-
453
- Gruf 1.2+ comes built with request logging out of the box; you'll get Rails-style logs with your
454
- gRPC calls:
455
-
456
- ```
457
- # plain
458
- I, [2017-07-14T09:50:54.200506 #70571] INFO -- : [GRPC::Ok] (thing_service.get_thing) [0.348ms]
459
- # logstash
460
- I, [2017-07-14T09:51:03.299050 #70595] INFO -- : {"message":"[GRPC::Ok] (thing_service.get_thing) [0.372ms]","service":"thing_service","method":"thing_service.get_thing","grpc_status":"GRPC::Ok"}
461
- ```
462
-
463
- It supports formatters (including custom ones) that you can use to specify the formatting of the logging:
464
-
465
- ```ruby
466
- Gruf.configure do |c|
467
- c.interceptors.use(
468
- Gruf::Interceptors::Instrumentation::RequestLogging::Interceptor,
469
- formatter: :logstash
470
- )
471
- end
472
- ```
473
-
474
- It comes with a few more options as well:
475
-
476
- | Option | Description | Default |
477
- | ------ | ----------- | ------- |
478
- | formatter | The formatter to use. By default `:plain` and `:logstash` are supported. | `:logstash` |
479
- | log_parameters | If set to true, will log parameters in the response | `false` |
480
- | blacklist | An array of parameter key names to redact from logging, in path.to.key format | `[]` |
481
- | redacted_string | The string to use for redacted parameters. | `REDACTED` |
482
- | ignore_methods | An array of method names to ignore from logging. E.g. `['namespace.health.check']` | `[]` |
483
-
484
- It's important to maintain a safe blacklist should you decide to log parameters; gruf does no
485
- parameter sanitization on its own. We also recommend blacklisting parameters that may contain
486
- very large values (such as binary or json data).
487
-
488
- ## Testing with RSpec
489
-
490
- There is a gem specifically for easy testing with RSpec: [gruf-rspec](https://github.com/bigcommerce/gruf-rspec). Take
491
- a look at its README for more information.
492
-
493
- ## Plugins
494
-
495
- You can build your own hooks and middleware for gruf; here's a list of known open source gems for
496
- gruf that you can use today:
497
-
498
- * [gruf-lightstep](https://github.com/bigcommerce/gruf-lightstep) - Provides a seamless
499
- [LightStep](https://lightstep.com) integration
500
- * [gruf-zipkin](https://github.com/bigcommerce/gruf-zipkin) - Provides a [Zipkin](https://zipkin.io)
501
- integration
502
- * [gruf-newrelic](https://github.com/bigcommerce/gruf-newrelic) - Easy [New Relic](https://newrelic.com/) integration
503
- * [gruf-commander](https://github.com/bigcommerce/gruf-commander) - Request/command-style validation and
504
- execution patterns for services
505
- * [gruf-profiler](https://github.com/bigcommerce/gruf-profiler) - Profiles and provides memory usage
506
- reports for clients and services
507
- * [gruf-circuit-breaker](https://github.com/bigcommerce/gruf-circuit-breaker) - Circuit breaker
508
- support for services
26
+ Please see the [gruf wiki](https://github.com/bigcommerce/gruf/wiki) for detailed information on getting started
27
+ using gruf.
509
28
 
510
29
  ## Demo Rails App
511
30
 
512
31
  There is a [demonstration Rails application here](https://github.com/bigcommerce/gruf-demo) you can
513
32
  view and clone that shows how to integrate Gruf into an existing Rails application.
514
33
 
515
- ## Roadmap
516
-
517
- ### Gruf 3.0
518
-
519
- * Change configuration to an injectable object to ensure thread safety on chained server/client interactions
520
- * Move all references to `Gruf.` configuration into injectable parameters
521
- * Redo server configuration to be fully injectable
522
-
523
- ## Companies Using Gruf
524
-
525
- Using gruf and want to show your support? Let us know and we'll add your name here.
526
-
527
- * [BigCommerce](https://www.bigcommerce.com/)
528
-
529
34
  ## License
530
35
 
531
36
  Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
@@ -60,6 +60,7 @@ module Gruf
60
60
  @opts = options || {}
61
61
  @opts[:password] = @opts.fetch(:password, '').to_s
62
62
  @opts[:hostname] = @opts.fetch(:hostname, Gruf.default_client_host)
63
+ @opts[:channel_credentials] = @opts.fetch(:channel_credentials, Gruf.default_channel_credentials)
63
64
  @error_factory = Gruf::Client::ErrorFactory.new
64
65
  client_options[:timeout] = client_options[:timeout].to_i if client_options.key?(:timeout)
65
66
  client = "#{service}::Stub".constantize.new(@opts[:hostname], build_ssl_credentials, client_options)
@@ -190,6 +191,8 @@ module Gruf
190
191
  #
191
192
  # :nocov:
192
193
  def build_ssl_credentials
194
+ return opts[:channel_credentials] if opts[:channel_credentials]
195
+
193
196
  cert = nil
194
197
  if opts[:ssl_certificate_file]
195
198
  cert = File.read(opts[:ssl_certificate_file]).to_s.strip
@@ -26,6 +26,7 @@ module Gruf
26
26
  server_options: {},
27
27
  interceptors: nil,
28
28
  hooks: nil,
29
+ default_channel_credentials: nil,
29
30
  default_client_host: '',
30
31
  use_ssl: false,
31
32
  ssl_crt_file: '',
@@ -16,5 +16,5 @@
16
16
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
17
  #
18
18
  module Gruf
19
- VERSION = '2.7.0'
19
+ VERSION = '2.7.1'
20
20
  end
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.7.0
4
+ version: 2.7.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: 2019-05-15 00:00:00.000000000 Z
11
+ date: 2019-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -196,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
196
  - !ruby/object:Gem::Version
197
197
  version: '0'
198
198
  requirements: []
199
- rubygems_version: 3.0.2
199
+ rubygems_version: 3.0.6
200
200
  signing_key:
201
201
  specification_version: 4
202
202
  summary: gRPC Ruby Framework