gruf 1.1.0 → 1.2.0
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 +37 -19
- data/README.md +71 -21
- data/lib/gruf.rb +3 -0
- data/lib/gruf/authentication.rb +9 -1
- data/lib/gruf/authentication/base.rb +16 -8
- data/lib/gruf/authentication/basic.rb +6 -6
- data/lib/gruf/authentication/none.rb +2 -2
- data/lib/gruf/authentication/strategies.rb +20 -7
- data/lib/gruf/client.rb +73 -11
- data/lib/gruf/configuration.rb +9 -6
- data/lib/gruf/error.rb +49 -15
- data/lib/gruf/errors/debug_info.rb +10 -5
- data/lib/gruf/errors/field.rb +12 -5
- data/lib/gruf/hooks/active_record/connection_reset.rb +15 -1
- data/lib/gruf/hooks/base.rb +26 -4
- data/lib/gruf/hooks/registry.rb +15 -9
- data/lib/gruf/instrumentation/base.rb +66 -11
- data/lib/gruf/instrumentation/output_metadata_timer.rb +6 -3
- data/lib/gruf/instrumentation/registry.rb +22 -13
- data/lib/gruf/instrumentation/request_context.rb +66 -0
- data/lib/gruf/instrumentation/request_logging/formatters/base.rb +38 -0
- data/lib/gruf/instrumentation/request_logging/formatters/logstash.rb +40 -0
- data/lib/gruf/instrumentation/request_logging/formatters/plain.rb +45 -0
- data/lib/gruf/instrumentation/request_logging/hook.rb +145 -0
- data/lib/gruf/instrumentation/statsd.rb +19 -13
- data/lib/gruf/loggable.rb +4 -1
- data/lib/gruf/logging.rb +19 -0
- data/lib/gruf/response.rb +19 -4
- data/lib/gruf/serializers/errors/base.rb +10 -3
- data/lib/gruf/serializers/errors/json.rb +5 -2
- data/lib/gruf/server.rb +10 -2
- data/lib/gruf/service.rb +32 -22
- data/lib/gruf/timer.rb +26 -5
- data/lib/gruf/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bf92528e6ff160d8d3a6997b8b00a44d86c46ac
|
4
|
+
data.tar.gz: 52bc1ac08fbf89488719d85c00bab0e741359c87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9caf811ac34255710d42a13a1a7f9066279d6c747a0e9b86fb3fff100aea31dc9889deb161acdfaa4b80907810f689c367851497ecd5399cde16a417df2bcf61
|
7
|
+
data.tar.gz: 86151ce87a7c7598e9a2a132b2d21fee05c10e621456766b78c3d11b94df942fb5d7d3e478333a0e84c8d6d7ebf0a223f1272be819dafbf2fd530bb007fd7983
|
data/CHANGELOG.md
CHANGED
@@ -1,47 +1,65 @@
|
|
1
1
|
Changelog for the gruf gem. This includes internal history before the gem was made.
|
2
2
|
|
3
|
-
|
3
|
+
### Pending release
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
### 1.2.0
|
8
|
+
|
9
|
+
- Instrumentation hooks now execute similarly to outer_around hooks; they can
|
10
|
+
now instrument failures
|
11
|
+
- Instrumentation hooks now pass a `RequestContext` object that contains information
|
12
|
+
about the incoming request, instead of relying on instance variables
|
13
|
+
- StatsD hook now sends success/failure metrics for endpoints
|
14
|
+
- Add ability to turn off sending exception message on uncaught exception.
|
15
|
+
- Add configuration to set the error message when an uncaught exception is
|
16
|
+
handled by gruf.
|
17
|
+
- Add a request logging hook for Rails-style request logging, with optional
|
18
|
+
parameter logging, blacklists, and formatter support
|
19
|
+
- Optimizations around Symbol casting within service calls
|
20
|
+
|
21
|
+
### 1.1.0
|
4
22
|
|
5
23
|
- Add the ability for call options to the client, which enables deadline setting
|
6
24
|
|
7
|
-
|
25
|
+
### 1.0.0
|
8
26
|
|
9
27
|
- Bump gRPC to 1.4
|
10
28
|
|
11
|
-
|
29
|
+
### 0.14.2
|
12
30
|
|
13
31
|
- Added rubocop style-guide checks
|
14
32
|
|
15
|
-
|
33
|
+
### 0.14.1
|
16
34
|
|
17
35
|
- Updated license to MIT
|
18
36
|
|
19
|
-
|
37
|
+
### 0.14.0
|
20
38
|
|
21
39
|
- Send gRPC status 16 (Unauthenticated) instead of 7 (PermissionDenied) when authentication fails
|
22
40
|
|
23
|
-
|
41
|
+
### 0.13.0
|
24
42
|
|
25
43
|
- Move to gRPC 1.3.4
|
26
44
|
|
27
|
-
|
45
|
+
### 0.12.2
|
28
46
|
|
29
47
|
- Add outer_around hook for wrapping the entire call chain
|
30
48
|
|
31
|
-
|
49
|
+
### 0.12.1
|
32
50
|
|
33
51
|
- Add ability to specify a separate gRPC logger from the Gruf logger
|
34
52
|
|
35
|
-
|
53
|
+
### 0.12.0
|
36
54
|
|
37
55
|
- Add ability to run multiple around hooks
|
38
56
|
- Fix bug with error handling that caused error messages to repeat across streams
|
39
57
|
|
40
|
-
|
58
|
+
### 0.11.5
|
41
59
|
|
42
60
|
- Fix issue with around hook
|
43
61
|
|
44
|
-
|
62
|
+
### 0.11.4
|
45
63
|
|
46
64
|
- Add catchall rescue handler to capture uncaught exceptions and
|
47
65
|
raise a GRPC::Internal error.
|
@@ -49,37 +67,37 @@ h3. 0.11.4
|
|
49
67
|
will call Service.set_debug_info with the exception backtrace
|
50
68
|
if an uncaught exception occurs.
|
51
69
|
|
52
|
-
|
70
|
+
### 0.11.3
|
53
71
|
|
54
72
|
- Pass the service instance into hooks for reference
|
55
73
|
|
56
|
-
|
74
|
+
### 0.11.2
|
57
75
|
|
58
76
|
- Ensure timer is measuring in milliseconds
|
59
77
|
|
60
|
-
|
78
|
+
### 0.11.1
|
61
79
|
|
62
80
|
- Fix issue with interceptor and call signature
|
63
81
|
|
64
|
-
|
82
|
+
### 0.11.0
|
65
83
|
|
66
84
|
- Add instrumentation layer and ability to register new instrumentors
|
67
85
|
- Add out-of-the-box statsd instrumentation support
|
68
86
|
|
69
|
-
|
87
|
+
### 0.10.0
|
70
88
|
|
71
89
|
- Rename Gruf::Endpoint to Gruf::Service
|
72
90
|
- Make services auto-mount to server upon declaration
|
73
91
|
|
74
|
-
|
92
|
+
### 0.9.2
|
75
93
|
|
76
94
|
- Support mount command on services to allow automatic setup on the server
|
77
95
|
- Cleanup and consolidate binstub to prevent need for custom binstub per-app
|
78
96
|
|
79
|
-
|
97
|
+
### 0.9.1
|
80
98
|
|
81
99
|
- Relax licensing to a clean BSD license
|
82
100
|
|
83
|
-
|
101
|
+
### 0.9.0
|
84
102
|
|
85
103
|
- Initial public release
|
data/README.md
CHANGED
@@ -32,6 +32,9 @@ Then in an initializer or before use:
|
|
32
32
|
require 'gruf'
|
33
33
|
```
|
34
34
|
|
35
|
+
Make sure to review [UPGRADING.md](https://github.com/bigcommerce/gruf/blob/master/UPGRADING.md)
|
36
|
+
if you are upgrading gruf between minor or major versions.
|
37
|
+
|
35
38
|
### Client
|
36
39
|
|
37
40
|
From there, you can instantiate a client given a stub service (say on an SslCertificates proto with a GetSslCertificate call):
|
@@ -42,7 +45,7 @@ require 'gruf'
|
|
42
45
|
id = args[:id].to_i.presence || 1
|
43
46
|
|
44
47
|
begin
|
45
|
-
client = ::Gruf::Client.new(service:
|
48
|
+
client = ::Gruf::Client.new(service: ::Demo::ThingService)
|
46
49
|
response = client.call(:GetMyThing, id: id)
|
47
50
|
puts response.message.inspect
|
48
51
|
rescue Gruf::Client::Error => e
|
@@ -72,40 +75,40 @@ syntax = "proto3";
|
|
72
75
|
|
73
76
|
package demo;
|
74
77
|
|
75
|
-
service
|
76
|
-
rpc
|
78
|
+
service Jobs {
|
79
|
+
rpc GetJob(GetJobReq) returns (GetJobResp) { }
|
77
80
|
}
|
78
81
|
|
79
|
-
message
|
82
|
+
message GetJobReq {
|
80
83
|
uint64 id = 1;
|
81
84
|
}
|
82
85
|
|
83
|
-
message
|
86
|
+
message GetJobResp {
|
84
87
|
uint64 id = 1;
|
85
88
|
string name = 2;
|
86
89
|
}
|
87
90
|
```
|
88
91
|
|
89
|
-
You'd have this handler in `/app/rpc/demo/
|
92
|
+
You'd have this handler in `/app/rpc/demo/job_server.rb`
|
90
93
|
|
91
94
|
```ruby
|
92
95
|
module Demo
|
93
|
-
class
|
96
|
+
class JobServer < ::Demo::Jobs::Service
|
94
97
|
include Gruf::Service
|
95
98
|
|
96
99
|
##
|
97
|
-
# @param [Demo::
|
98
|
-
# @param [GRPC::ActiveCall] call
|
99
|
-
# @return [Demo::
|
100
|
+
# @param [Demo::GetJobReq] req The incoming gRPC request object
|
101
|
+
# @param [GRPC::ActiveCall] call The gRPC active call instance
|
102
|
+
# @return [Demo::GetJobResp] The job response
|
100
103
|
#
|
101
|
-
def
|
102
|
-
|
104
|
+
def get_job(req, call)
|
105
|
+
thing = Job.find(req.id)
|
103
106
|
|
104
|
-
Demo::
|
105
|
-
id:
|
107
|
+
Demo::GetJobResp.new(
|
108
|
+
id: thing.id
|
106
109
|
)
|
107
110
|
rescue
|
108
|
-
fail!(req, call, :not_found, :
|
111
|
+
fail!(req, call, :not_found, :job_not_found, "Failed to find Job with ID: #{req.id}")
|
109
112
|
end
|
110
113
|
end
|
111
114
|
end
|
@@ -277,23 +280,70 @@ The StatsD support is not enabled by default. To enable it, you'll want to do:
|
|
277
280
|
|
278
281
|
```ruby
|
279
282
|
Gruf.configure do |c|
|
280
|
-
c.instrumentation_options = {
|
281
|
-
|
282
|
-
|
283
|
-
prefix: 'my_application_prefix.rpc'
|
284
|
-
}
|
283
|
+
c.instrumentation_options[:statsd] = {
|
284
|
+
client: ::Statsd.new('my.statsd.host', 8125),
|
285
|
+
prefix: 'my_application_prefix.rpc'
|
285
286
|
}
|
286
287
|
end
|
287
288
|
Gruf::Instrumentation::Registry.add(:statsd, Gruf::Instrumentation::Statsd)
|
288
289
|
```
|
289
290
|
|
290
|
-
This will measure counts and timings for each endpoint.
|
291
|
+
This will measure counts and timings for each endpoint. Note: instrumentation hooks happen in LIFO order; they also
|
292
|
+
run similarly to an outer_around hook, executing _before_ authorization happens. Note: It's important that in your
|
293
|
+
instrumentors, you pass-through exceptions (such as `GRPC::BadStatus`); catching them in instrumentors will cause errors
|
294
|
+
upstream.
|
295
|
+
|
296
|
+
### Request Logging
|
297
|
+
|
298
|
+
Gruf 1.2+ comes built with request logging out of the box; you'll get Rails-style logs with your gRPC calls:
|
299
|
+
|
300
|
+
```
|
301
|
+
# plain
|
302
|
+
I, [2017-07-14T09:50:54.200506 #70571] INFO -- : [GRPC::Ok] (thing_service.get_thing) [0.348ms]
|
303
|
+
# logstash
|
304
|
+
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"}
|
305
|
+
```
|
306
|
+
|
307
|
+
It supports formatters (including custom ones) that you can use to specify the formatting of the logging:
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
Gruf.configure do |c|
|
311
|
+
c.instrumentation_options[:request_logging] = {
|
312
|
+
formatter: :logstash
|
313
|
+
}
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
317
|
+
It comes with a few more options as well:
|
318
|
+
|
319
|
+
| Option | Description | Default |
|
320
|
+
| ------ | ----------- | ------- |
|
321
|
+
| formatter | The formatter to use. By default `:plain` and `:logstash` are supported. | `:plain` |
|
322
|
+
| log_parameters | If set to true, will log parameters in the response | `false` |
|
323
|
+
| blacklist | An array of parameter key names to redact from logging | `[]` |
|
324
|
+
| redacted_string | The string to use for redacted parameters. | `REDACTED` |
|
325
|
+
|
326
|
+
It's important to maintain a safe blacklist should you decide to log parameters; gruf does no
|
327
|
+
parameter sanitization on its own. We also recommend blacklisting parameters that may contain
|
328
|
+
very large values (such as binary or json data).
|
291
329
|
|
292
330
|
### Custom Instrumentors
|
293
331
|
|
294
332
|
Similar to hooks, simply extend the `Gruf::Instrumentation::Base` class, and implement the `call` method. See the StatsD
|
295
333
|
instrumentor for an example.
|
296
334
|
|
335
|
+
## Plugins
|
336
|
+
|
337
|
+
You can build your own hooks and middleware for gruf; here's a list of known open source gems for
|
338
|
+
gruf that you can use today:
|
339
|
+
|
340
|
+
* [gruf-zipkin](https://github.com/bigcommerce/gruf-zipkin) - Provides a [Zipkin](https://zipkin.io)
|
341
|
+
integration for gruf
|
342
|
+
* [gruf-circuit-breaker](https://github.com/bigcommerce/gruf-circuit-breaker) - Provides circuit breaker
|
343
|
+
support for gruf services
|
344
|
+
* [gruf-profiler](https://github.com/bigcommerce/gruf-profiler) - Profiles and provides memory usage
|
345
|
+
reports for gruf services
|
346
|
+
|
297
347
|
## License
|
298
348
|
|
299
349
|
Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
data/lib/gruf.rb
CHANGED
data/lib/gruf/authentication.rb
CHANGED
@@ -20,11 +20,19 @@ require_relative 'authentication/basic'
|
|
20
20
|
require_relative 'authentication/none'
|
21
21
|
|
22
22
|
module Gruf
|
23
|
+
##
|
24
|
+
# Handles authentication for gruf services
|
25
|
+
#
|
23
26
|
module Authentication
|
27
|
+
##
|
28
|
+
# Custom error class for handling unauthorized requests
|
29
|
+
#
|
24
30
|
class UnauthorizedError < StandardError; end
|
25
31
|
|
26
32
|
##
|
27
|
-
#
|
33
|
+
# Verify a given gruf request
|
34
|
+
#
|
35
|
+
# @param [GRPC::ActiveCall] call The gRPC active call with marshalled data that is being executed
|
28
36
|
# @param [Symbol] strategy The authentication strategy to use
|
29
37
|
# @return [Boolean]
|
30
38
|
#
|
@@ -17,16 +17,21 @@
|
|
17
17
|
module Gruf
|
18
18
|
module Authentication
|
19
19
|
##
|
20
|
-
# Base interface for Authentication strategies
|
20
|
+
# Base interface for Authentication strategies. All derived strategies must define the `valid?` method.
|
21
21
|
#
|
22
22
|
class Base
|
23
23
|
include Gruf::Loggable
|
24
24
|
|
25
|
-
|
25
|
+
# @return [String] The credentials sent in the request
|
26
|
+
attr_reader :credentials
|
27
|
+
# @return [Hash] A hash of authentication options
|
28
|
+
attr_reader :options
|
26
29
|
|
27
30
|
##
|
31
|
+
# Initialize the authentication middleware
|
28
32
|
#
|
29
|
-
# @param [String] credentials
|
33
|
+
# @param [String] credentials The credentials sent in the request
|
34
|
+
# @param [Hash] options A hash of authentication options
|
30
35
|
#
|
31
36
|
def initialize(credentials, options = {})
|
32
37
|
opts = Gruf.authentication_options || {}
|
@@ -37,17 +42,20 @@ module Gruf
|
|
37
42
|
##
|
38
43
|
# Verify the credentials. Helper class method.
|
39
44
|
#
|
40
|
-
# @param [GRPC::ActiveCall] call
|
41
|
-
# @param [String] credentials
|
42
|
-
# @param [Hash] options
|
45
|
+
# @param [GRPC::ActiveCall] call The gRPC active call for the given operation
|
46
|
+
# @param [String] credentials The credentials sent in the request
|
47
|
+
# @param [Hash] options A hash of authentication options
|
43
48
|
#
|
44
49
|
def self.verify(call, credentials = '', options = {})
|
45
50
|
new(credentials, options).valid?(call)
|
46
51
|
end
|
47
52
|
|
48
53
|
##
|
49
|
-
#
|
50
|
-
#
|
54
|
+
# Abstract method that is required to be implemented in every derivative class.
|
55
|
+
# Return true if the call is authenticated, false if a permission denied error is to be sent.
|
56
|
+
#
|
57
|
+
# @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
|
58
|
+
# @return [Boolean] True if the call was authenticated
|
51
59
|
#
|
52
60
|
def valid?(_call)
|
53
61
|
raise NotImplementedError
|
@@ -23,8 +23,8 @@ module Gruf
|
|
23
23
|
#
|
24
24
|
class Basic < Base
|
25
25
|
##
|
26
|
-
# @param [GRPC::ActiveCall] _call
|
27
|
-
# @return [Boolean]
|
26
|
+
# @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
|
27
|
+
# @return [Boolean] True if the basic authentication was valid
|
28
28
|
#
|
29
29
|
def valid?(_call)
|
30
30
|
server_credentials.any? do |cred|
|
@@ -41,21 +41,21 @@ module Gruf
|
|
41
41
|
private
|
42
42
|
|
43
43
|
##
|
44
|
-
# @return [Array<Hash>]
|
44
|
+
# @return [Array<Hash>] An array of valid server credentials for this service
|
45
45
|
#
|
46
46
|
def server_credentials
|
47
47
|
options.fetch(:credentials, [])
|
48
48
|
end
|
49
49
|
|
50
50
|
##
|
51
|
-
# @return [String]
|
51
|
+
# @return [String] The decoded request credentials
|
52
52
|
#
|
53
53
|
def request_credentials
|
54
54
|
Base64.decode64(credentials.to_s.gsub('Basic ', '').strip)
|
55
55
|
end
|
56
56
|
|
57
57
|
##
|
58
|
-
# @return [String]
|
58
|
+
# @return [String] The decoded request username
|
59
59
|
# @deprecated
|
60
60
|
# :nocov:
|
61
61
|
def request_username
|
@@ -64,7 +64,7 @@ module Gruf
|
|
64
64
|
# :nocov:
|
65
65
|
|
66
66
|
##
|
67
|
-
# @return [String]
|
67
|
+
# @return [String] The decoded request password
|
68
68
|
#
|
69
69
|
def request_password
|
70
70
|
@request_password ||= request_credentials.split(':').last
|
@@ -21,8 +21,8 @@ module Gruf
|
|
21
21
|
#
|
22
22
|
class None < Base
|
23
23
|
##
|
24
|
-
# @param [GRPC::ActiveCall] _call
|
25
|
-
# @return [TrueClass]
|
24
|
+
# @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
|
25
|
+
# @return [TrueClass] Always true for this passthrough strategy.
|
26
26
|
#
|
27
27
|
def valid?(_call)
|
28
28
|
true
|
@@ -20,15 +20,19 @@ module Gruf
|
|
20
20
|
# Provides a modifiable repository of strategies for authentication
|
21
21
|
#
|
22
22
|
class Strategies
|
23
|
+
##
|
24
|
+
# Error class that represents when a strategy does not extend the base class
|
25
|
+
#
|
23
26
|
class StrategyDescendantError < StandardError; end
|
24
27
|
|
25
28
|
class << self
|
26
29
|
##
|
27
30
|
# Add an authentication strategy, either through a class or a block
|
28
31
|
#
|
29
|
-
# @param [String] name
|
30
|
-
# @param [Class|NilClass] strategy
|
31
|
-
# @
|
32
|
+
# @param [String] name The vanity name of the strategy being loaded
|
33
|
+
# @param [Class|NilClass] strategy (Optional) The class that represents the strategy
|
34
|
+
# @param [Proc] &block If given, will attempt to build a strategy class from the base class with this block
|
35
|
+
# @return [Class] The loaded strategy
|
32
36
|
#
|
33
37
|
def add(name, strategy = nil, &block)
|
34
38
|
base = Gruf::Authentication::Base
|
@@ -46,12 +50,15 @@ module Gruf
|
|
46
50
|
##
|
47
51
|
# Return a strategy via a hash accessor syntax
|
48
52
|
#
|
53
|
+
# @param [Symbol] label The name of the strategy
|
54
|
+
# @return [Gruf::Authentication::Base] The requested strategy
|
55
|
+
#
|
49
56
|
def [](label)
|
50
57
|
_strategies[label.to_sym]
|
51
58
|
end
|
52
59
|
|
53
60
|
##
|
54
|
-
# Iterate over each
|
61
|
+
# Iterate over each strategy and yield it to the caller
|
55
62
|
#
|
56
63
|
def each
|
57
64
|
_strategies.each do |s|
|
@@ -60,21 +67,27 @@ module Gruf
|
|
60
67
|
end
|
61
68
|
|
62
69
|
##
|
63
|
-
#
|
70
|
+
# Return the loaded strategies as a hash
|
71
|
+
#
|
72
|
+
# @return [Hash<Class>] A name/strategy pair of loaded strategies
|
64
73
|
#
|
65
74
|
def to_h
|
66
75
|
_strategies
|
67
76
|
end
|
68
77
|
|
69
78
|
##
|
70
|
-
#
|
79
|
+
# Return if there are any loaded strategies
|
80
|
+
#
|
81
|
+
# @return [Boolean] True if there are loaded strategies
|
71
82
|
#
|
72
83
|
def any?
|
73
84
|
to_h.keys.count > 0
|
74
85
|
end
|
75
86
|
|
76
87
|
##
|
77
|
-
#
|
88
|
+
# Clear all given strategies
|
89
|
+
#
|
90
|
+
# @return [Hash] The newly empty hash
|
78
91
|
#
|
79
92
|
def clear
|
80
93
|
@strategies = {}
|