gruf 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.com/bigcommerce/gruf.svg?token=D3Cc4LCF9BgpUx4dpPpv&branch=master)](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.
|