gruf 1.0.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 +7 -0
- data/CHANGELOG.md +81 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/README.md +312 -0
- data/bin/gruf +29 -0
- data/gruf.gemspec +41 -0
- data/lib/gruf.rb +38 -0
- data/lib/gruf/authentication.rb +57 -0
- data/lib/gruf/authentication/base.rb +57 -0
- data/lib/gruf/authentication/basic.rb +74 -0
- data/lib/gruf/authentication/none.rb +32 -0
- data/lib/gruf/authentication/strategies.rb +94 -0
- data/lib/gruf/client.rb +141 -0
- data/lib/gruf/configuration.rb +124 -0
- data/lib/gruf/error.rb +162 -0
- data/lib/gruf/errors/debug_info.rb +45 -0
- data/lib/gruf/errors/field.rb +48 -0
- data/lib/gruf/hooks/active_record/connection_reset.rb +36 -0
- data/lib/gruf/hooks/base.rb +44 -0
- data/lib/gruf/hooks/registry.rb +104 -0
- data/lib/gruf/instrumentation/base.rb +59 -0
- data/lib/gruf/instrumentation/output_metadata_timer.rb +40 -0
- data/lib/gruf/instrumentation/registry.rb +95 -0
- data/lib/gruf/instrumentation/statsd.rb +74 -0
- data/lib/gruf/loggable.rb +26 -0
- data/lib/gruf/logging.rb +33 -0
- data/lib/gruf/response.rb +55 -0
- data/lib/gruf/serializers/errors/base.rb +49 -0
- data/lib/gruf/serializers/errors/json.rb +39 -0
- data/lib/gruf/server.rb +92 -0
- data/lib/gruf/service.rb +271 -0
- data/lib/gruf/timer.rb +56 -0
- data/lib/gruf/version.rb +19 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8db09af385df9c2a441b0bf6ece236e8d3a0735a
|
4
|
+
data.tar.gz: dff8c04b4439335743edb8c6cbd7e74d7cb64f43
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 040cfcf2678a142635796b35706dc1599e4b0364a4cc6a90fbc9628e02d3b273427452b3325d6e08988b53e80d2a08482879072c90927de16f697ad7f7937c3f
|
7
|
+
data.tar.gz: 3a67f98f29b8be1b9216a0c609e040308721afe822719d591cd74a82cb3d3c791b796d96eee750e096b2de7c837607377e01101b760d1fd4fb82dcc2e98ca904
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
Changelog for the gruf gem. This includes internal history before the gem was made.
|
2
|
+
|
3
|
+
h3. 1.0.0
|
4
|
+
|
5
|
+
- Bump gRPC to 1.4
|
6
|
+
|
7
|
+
h3. 0.14.2
|
8
|
+
|
9
|
+
- Added rubocop style-guide checks
|
10
|
+
|
11
|
+
h3. 0.14.1
|
12
|
+
|
13
|
+
- Updated license to MIT
|
14
|
+
|
15
|
+
h3. 0.14.0
|
16
|
+
|
17
|
+
- Send gRPC status 16 (Unauthenticated) instead of 7 (PermissionDenied) when authentication fails
|
18
|
+
|
19
|
+
h3. 0.13.0
|
20
|
+
|
21
|
+
- Move to gRPC 1.3.4
|
22
|
+
|
23
|
+
h4. 0.12.2
|
24
|
+
|
25
|
+
- Add outer_around hook for wrapping the entire call chain
|
26
|
+
|
27
|
+
h4. 0.12.1
|
28
|
+
|
29
|
+
- Add ability to specify a separate gRPC logger from the Gruf logger
|
30
|
+
|
31
|
+
h3. 0.12.0
|
32
|
+
|
33
|
+
- Add ability to run multiple around hooks
|
34
|
+
- Fix bug with error handling that caused error messages to repeat across streams
|
35
|
+
|
36
|
+
h3. 0.11.5
|
37
|
+
|
38
|
+
- Fix issue with around hook
|
39
|
+
|
40
|
+
h3. 0.11.4
|
41
|
+
|
42
|
+
- Add catchall rescue handler to capture uncaught exceptions and
|
43
|
+
raise a GRPC::Internal error.
|
44
|
+
- Add Gruf.backtrace_on_error configuration value. If set, Gruf
|
45
|
+
will call Service.set_debug_info with the exception backtrace
|
46
|
+
if an uncaught exception occurs.
|
47
|
+
|
48
|
+
h3. 0.11.3
|
49
|
+
|
50
|
+
- Pass the service instance into hooks for reference
|
51
|
+
|
52
|
+
h3. 0.11.2
|
53
|
+
|
54
|
+
- Ensure timer is measuring in milliseconds
|
55
|
+
|
56
|
+
h3. 0.11.1
|
57
|
+
|
58
|
+
- Fix issue with interceptor and call signature
|
59
|
+
|
60
|
+
h3. 0.11.0
|
61
|
+
|
62
|
+
- Add instrumentation layer and ability to register new instrumentors
|
63
|
+
- Add out-of-the-box statsd instrumentation support
|
64
|
+
|
65
|
+
h3. 0.10.0
|
66
|
+
|
67
|
+
- Rename Gruf::Endpoint to Gruf::Service
|
68
|
+
- Make services auto-mount to server upon declaration
|
69
|
+
|
70
|
+
h3. 0.9.2
|
71
|
+
|
72
|
+
- Support mount command on services to allow automatic setup on the server
|
73
|
+
- Cleanup and consolidate binstub to prevent need for custom binstub per-app
|
74
|
+
|
75
|
+
h3. 0.9.1
|
76
|
+
|
77
|
+
- Relax licensing to a clean BSD license
|
78
|
+
|
79
|
+
h3. 0.9.0
|
80
|
+
|
81
|
+
- Initial public release
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at splittingred@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/README.md
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
# gruf - gRPC Ruby Framework
|
2
|
+
|
3
|
+
[](https://travis-ci.com/bigcommerce/gruf)
|
4
|
+
|
5
|
+
gruf is a Ruby framework that wraps the [gRPC Ruby library](https://github.com/grpc/grpc/tree/master/src/ruby) to
|
6
|
+
provide a more streamlined integration into Ruby and Ruby on Rails applications.
|
7
|
+
|
8
|
+
It provides an abstracted server and client for gRPC services, along with other tools to help get gRPC services in Ruby
|
9
|
+
up fast and efficiently at scale. Some of its features include:
|
10
|
+
|
11
|
+
* Abstracted server endpoints with before, around, outer around, and after hooks during an endpoint call
|
12
|
+
* Robust client error handling and metadata transport abilities
|
13
|
+
* Server authentication strategy support, with basic auth with multiple key support built in
|
14
|
+
* TLS support for client-server auth, though we recommend using [linkerd](https://linkerd.io/) instead
|
15
|
+
* Error data serialization in output metadata to allow fine-grained error handling in the transport while still
|
16
|
+
preserving gRPC BadStatus codes
|
17
|
+
* Server and client execution timings in responses
|
18
|
+
|
19
|
+
gruf currently has active support for gRPC 1.4.x. gruf is compatible and tested with with Ruby 2.2, 2.3, and 2.4. gruf
|
20
|
+
is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework (such as
|
21
|
+
[Grape](https://github.com/ruby-grape/grape), for instance).
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'gruf'
|
27
|
+
```
|
28
|
+
|
29
|
+
Then in an initializer or before use:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'gruf'
|
33
|
+
```
|
34
|
+
|
35
|
+
### Client
|
36
|
+
|
37
|
+
From there, you can instantiate a client given a stub service (say on an SslCertificates proto with a GetSslCertificate call):
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'gruf'
|
41
|
+
|
42
|
+
id = args[:id].to_i.presence || 1
|
43
|
+
|
44
|
+
begin
|
45
|
+
client = ::Gruf::Client.new(service: MyPackage::MyService)
|
46
|
+
response = client.call(:GetMyThing, id: id)
|
47
|
+
puts response.message.inspect
|
48
|
+
rescue Gruf::Client::Error => e
|
49
|
+
puts e.error.inspect
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Note this returns a response object. The response object can provide `trailing_metadata` as well as a `execution_time`.
|
54
|
+
|
55
|
+
### Server
|
56
|
+
|
57
|
+
Add an initializer:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
require 'gruf'
|
61
|
+
|
62
|
+
Gruf.configure do |c|
|
63
|
+
c.server_binding_url = 'grpc.service.com:9003'
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Next, setup some handlers based on your proto configurations in `/app/rpc/`. For example, for the Thing service, with a
|
68
|
+
GetThingReq/GetThingResp call based on this proto:
|
69
|
+
|
70
|
+
```
|
71
|
+
syntax = "proto3";
|
72
|
+
|
73
|
+
package demo;
|
74
|
+
|
75
|
+
service Thing {
|
76
|
+
rpc GetThing(GetThingReq) returns (GetSslCertificateResp) { }
|
77
|
+
}
|
78
|
+
|
79
|
+
message ThingReq {
|
80
|
+
uint64 id = 1;
|
81
|
+
}
|
82
|
+
|
83
|
+
message ThingResp {
|
84
|
+
uint64 id = 1;
|
85
|
+
string name = 2;
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
You'd have this handler in `/app/rpc/demo/thing_server.rb`
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
module Demo
|
93
|
+
class ThingServer < ::Demo::ThingService::Service
|
94
|
+
include Gruf::Service
|
95
|
+
|
96
|
+
##
|
97
|
+
# @param [Demo::GetThingReq] req
|
98
|
+
# @param [GRPC::ActiveCall] call
|
99
|
+
# @return [Demo::GetThingResp]
|
100
|
+
#
|
101
|
+
def get_thing(req, call)
|
102
|
+
ssl = Thing.find(req.id)
|
103
|
+
|
104
|
+
Demo::Things::GetThingResp.new(
|
105
|
+
id: ssl.id
|
106
|
+
)
|
107
|
+
rescue
|
108
|
+
fail!(req, call, :not_found, :thing_not_found, "Failed to find Thing with ID: #{req.id}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
Finally, you can start the server by running:
|
115
|
+
|
116
|
+
bundle exec gruf
|
117
|
+
|
118
|
+
### Authentication
|
119
|
+
|
120
|
+
Authentication is done via a strategy pattern and are injectable via middleware. If any of the strategies return `true`,
|
121
|
+
it will proceed the request as successful. For example, to add basic auth, you can do:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
Gruf::Authentication::Strategies.add(:basic, Gruf::Authentication::Basic)
|
125
|
+
```
|
126
|
+
|
127
|
+
Options to the middleware libraries can be passed through the `authentication_options` configuration option.
|
128
|
+
|
129
|
+
To add a custom authentication pattern, your class must extend the `Gruf::Authentication::Base` class, and implement
|
130
|
+
the `valid?(call)` method. For example, this class allows everyone in:
|
131
|
+
|
132
|
+
```
|
133
|
+
class NoAuth < Gruf::Authentication::Base
|
134
|
+
def valid?(_call)
|
135
|
+
true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
#### Basic Auth
|
141
|
+
|
142
|
+
gruf supports simple basic authentication with an array of accepted credentials:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
Gruf.configure do |c|
|
146
|
+
c.authentication_options = {
|
147
|
+
credentials: [{
|
148
|
+
username: 'my-username-here',
|
149
|
+
password: 'my-password-here',
|
150
|
+
},{
|
151
|
+
username: 'another-username',
|
152
|
+
password: 'another-password',
|
153
|
+
},{
|
154
|
+
password: 'a-password-only'
|
155
|
+
}]
|
156
|
+
}
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
Supporting an array of credentials allow for unique credentials per service, or for easy credential rotation with
|
161
|
+
zero downtime.
|
162
|
+
|
163
|
+
### SSL Configuration
|
164
|
+
|
165
|
+
We don't recommend using TLS for gRPC, but instead using something like [linkerd](https://linkerd.io) for TLS
|
166
|
+
encryption between services. If you need it, however, this library supports TLS.
|
167
|
+
|
168
|
+
For the client, you'll need to point to the public certificate:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
::Gruf::Client.new(
|
172
|
+
service: Demo::ThingService,
|
173
|
+
ssl_certificate: 'x509 public certificate here',
|
174
|
+
# OR
|
175
|
+
ssl_certificate_file: '/path/to/my.crt'
|
176
|
+
)
|
177
|
+
```
|
178
|
+
|
179
|
+
If you want to run a server you'll need both the CRT and the key file if you want to do credentialed auth:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
Gruf.configure do |c|
|
183
|
+
c.use_ssl = true
|
184
|
+
c.ssl_crt_file = "#{Rails.root}/config/ssl/#{Rails.env}.crt"
|
185
|
+
c.ssl_key_file = "#{Rails.root}/config/ssl/#{Rails.env}.key"
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
## Hooks
|
190
|
+
|
191
|
+
gruf supports hooks that act as interceptors around the grpc server calls, allowing you to perform actions before,
|
192
|
+
after, and even around your server endpoints. This can be used to add tracing data, connection resets in the grpc thread
|
193
|
+
pool, further instrumentation, and other things.
|
194
|
+
|
195
|
+
Adding a hook is as simple as creating a class that extends `Gruf::Hooks::Base`, and implementing it via the registry.
|
196
|
+
|
197
|
+
### Before
|
198
|
+
|
199
|
+
A before hook passes in the method call signature, request object, and `GRPC::ActiveCall` object:
|
200
|
+
```ruby
|
201
|
+
class MyBeforeHook < Gruf::Hooks::Base
|
202
|
+
def before(call_signature, request, active_call)
|
203
|
+
# do my thing before the call. Calling `fail!` here will prevent the call from happening.
|
204
|
+
end
|
205
|
+
end
|
206
|
+
Gruf::Hooks::Registry.add(:my_before_hook, MyBeforeHook)
|
207
|
+
```
|
208
|
+
|
209
|
+
### After
|
210
|
+
|
211
|
+
An after hook passes in the response object, method call signature, request object, and `GRPC::ActiveCall` object:
|
212
|
+
```ruby
|
213
|
+
class MyAfterHook < Gruf::Hooks::Base
|
214
|
+
def after(success, response, call_signature, request, active_call)
|
215
|
+
# You can modify the response object
|
216
|
+
end
|
217
|
+
end
|
218
|
+
Gruf::Hooks::Registry.add(:my_after_hook, MyAfterHook)
|
219
|
+
```
|
220
|
+
|
221
|
+
### Around
|
222
|
+
|
223
|
+
An around hook passes in the method call signature, request object, `GRPC::ActiveCall` object, and the block
|
224
|
+
being executed:
|
225
|
+
```ruby
|
226
|
+
class MyAroundHook < Gruf::Hooks::Base
|
227
|
+
def around(call_signature, request, active_call, &block)
|
228
|
+
# do my thing here
|
229
|
+
resp = yield
|
230
|
+
# do my thing there
|
231
|
+
resp
|
232
|
+
end
|
233
|
+
end
|
234
|
+
Gruf::Hooks::Registry.add(:my_around_hook, MyAroundHook)
|
235
|
+
```
|
236
|
+
|
237
|
+
Around hooks are a special case - because each needs to wrap the call, they are run recursively within each other.
|
238
|
+
This means that if you have three hooks - `Hook1`, `Hook2`, and `Hook3` - they will run in LIFO (last in, first out)
|
239
|
+
order. `Hook3` will run, calling `Hook2`, which will then call `Hook1`, ending the chain.
|
240
|
+
|
241
|
+
### Outer Around
|
242
|
+
|
243
|
+
And finally, an "outer" around hook passes in the method call signature, request object, `GRPC::ActiveCall`
|
244
|
+
object, and the block being executed, and executes around the _entire_ call chain (before, around, request, after):
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
class MyOuterAroundHook < Gruf::Hooks::Base
|
248
|
+
def outer_around(call_signature, request, active_call, &block)
|
249
|
+
# do my thing here
|
250
|
+
resp = yield
|
251
|
+
# do my thing there
|
252
|
+
resp
|
253
|
+
end
|
254
|
+
end
|
255
|
+
Gruf::Hooks::Registry.add(:my_outer_around_hook, MyOuterAroundHook)
|
256
|
+
```
|
257
|
+
|
258
|
+
Outer around hooks behave similarly in execution order to around hooks.
|
259
|
+
|
260
|
+
Note: It's important to note that the authentication step happens immediately before the first _before_ hook is called,
|
261
|
+
so don't perform any actions that you want behind authentication in outer around hooks, as they are not called with
|
262
|
+
authentication.
|
263
|
+
|
264
|
+
## Instrumentation
|
265
|
+
|
266
|
+
gruf comes out of the box with a couple of instrumentors packed in: output metadata timings, and StatsD
|
267
|
+
support.
|
268
|
+
|
269
|
+
### Output Metadata Timing
|
270
|
+
|
271
|
+
Enabled by default, this will push timings for _successful responses_ through the response output metadata back to the
|
272
|
+
client.
|
273
|
+
|
274
|
+
### StatsD
|
275
|
+
|
276
|
+
The StatsD support is not enabled by default. To enable it, you'll want to do:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
Gruf.configure do |c|
|
280
|
+
c.instrumentation_options = {
|
281
|
+
statsd: {
|
282
|
+
client: ::Statsd.new('my.statsd.host', 8125),
|
283
|
+
prefix: 'my_application_prefix.rpc'
|
284
|
+
}
|
285
|
+
}
|
286
|
+
end
|
287
|
+
Gruf::Instrumentation::Registry.add(:statsd, Gruf::Instrumentation::Statsd)
|
288
|
+
```
|
289
|
+
|
290
|
+
This will measure counts and timings for each endpoint.
|
291
|
+
|
292
|
+
### Custom Instrumentors
|
293
|
+
|
294
|
+
Similar to hooks, simply extend the `Gruf::Instrumentation::Base` class, and implement the `call` method. See the StatsD
|
295
|
+
instrumentor for an example.
|
296
|
+
|
297
|
+
## License
|
298
|
+
|
299
|
+
Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
300
|
+
|
301
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
302
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
303
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
304
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
305
|
+
|
306
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
307
|
+
Software.
|
308
|
+
|
309
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
310
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
311
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
312
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|