gruf 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 = {}
|