istox_gruf 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +246 -0
  3. data/CODE_OF_CONDUCT.md +46 -0
  4. data/README.md +544 -0
  5. data/bin/gruf +29 -0
  6. data/lib/gruf.rb +50 -0
  7. data/lib/gruf/cli/executor.rb +99 -0
  8. data/lib/gruf/client.rb +217 -0
  9. data/lib/gruf/client/error.rb +66 -0
  10. data/lib/gruf/client/error_factory.rb +105 -0
  11. data/lib/gruf/configuration.rb +137 -0
  12. data/lib/gruf/controllers/base.rb +102 -0
  13. data/lib/gruf/controllers/request.rb +121 -0
  14. data/lib/gruf/controllers/service_binder.rb +117 -0
  15. data/lib/gruf/error.rb +230 -0
  16. data/lib/gruf/errors/debug_info.rb +56 -0
  17. data/lib/gruf/errors/field.rb +56 -0
  18. data/lib/gruf/errors/helpers.rb +44 -0
  19. data/lib/gruf/hooks/base.rb +34 -0
  20. data/lib/gruf/hooks/executor.rb +47 -0
  21. data/lib/gruf/hooks/registry.rb +159 -0
  22. data/lib/gruf/instrumentable_grpc_server.rb +64 -0
  23. data/lib/gruf/integrations/rails/railtie.rb +10 -0
  24. data/lib/gruf/interceptors/active_record/connection_reset.rb +48 -0
  25. data/lib/gruf/interceptors/authentication/basic.rb +87 -0
  26. data/lib/gruf/interceptors/base.rb +53 -0
  27. data/lib/gruf/interceptors/client_interceptor.rb +125 -0
  28. data/lib/gruf/interceptors/context.rb +56 -0
  29. data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +61 -0
  30. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +41 -0
  31. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +43 -0
  32. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +48 -0
  33. data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +225 -0
  34. data/lib/gruf/interceptors/instrumentation/statsd.rb +82 -0
  35. data/lib/gruf/interceptors/registry.rb +161 -0
  36. data/lib/gruf/interceptors/server_interceptor.rb +34 -0
  37. data/lib/gruf/interceptors/timer.rb +85 -0
  38. data/lib/gruf/loggable.rb +30 -0
  39. data/lib/gruf/logging.rb +53 -0
  40. data/lib/gruf/outbound/request_context.rb +71 -0
  41. data/lib/gruf/response.rb +71 -0
  42. data/lib/gruf/serializers/errors/base.rb +57 -0
  43. data/lib/gruf/serializers/errors/json.rb +43 -0
  44. data/lib/gruf/server.rb +294 -0
  45. data/lib/gruf/synchronized_client.rb +97 -0
  46. data/lib/gruf/timer.rb +78 -0
  47. data/lib/gruf/version.rb +20 -0
  48. metadata +203 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f392b44e82edaeae1c11c6583ec5c67312f1cc9f0f1199d80316d636edff2795
4
+ data.tar.gz: e2f245a831f41d47df608ce54554099ba5e594cb90d5a792512b1b350df75574
5
+ SHA512:
6
+ metadata.gz: c7cd702f88a981b71c5095a49232bc3be67d8026ffbcae82a9c935c6aedb0649bd51f19cfe2fd27cfb3bdc2fa6499ae786c666a37bb90699bd5fa4e5246ed287
7
+ data.tar.gz: 73eabbaa8bdf9c0bfea9f4b4f046a5e3e3f0eaa7679531555499a97fd3c6425b2b48e34b6209d2f222eba8d39db63aefbf02a14955a04368d0a342dfd7d17641
@@ -0,0 +1,246 @@
1
+ Changelog for the gruf gem. This includes internal history before the gem was made.
2
+
3
+ ### Pending release
4
+
5
+ ### 2.7.0
6
+
7
+ - Add hook support for executing code paths before a server is started, and after a server stops
8
+
9
+ ### 2.6.1
10
+
11
+ - Add frozen_string_literal: true to files, update rubocop to 0.68
12
+
13
+ ### 2.6.0
14
+
15
+ - Drop Ruby 2.2 support
16
+ - Abstract gruf controller's send to make it usable in filters
17
+ - Adjusts configuration reset into a Railtie for Rails systems to ensure proper OOE
18
+ - Bump rubocop to 0.64, address violations, update activesupport/concurrent-ruby dependencies to have a min version
19
+
20
+ ### 2.5.2
21
+
22
+ - Log ok/validation responses at DEBUG levels to prevent log stampeding in high-volume environments
23
+
24
+ ### 2.5.1
25
+
26
+ - Ensure `timeout` is an int when passed as a client option to a gRPC client
27
+ - Add `bound_service` reader to `Gruf::Controllers::Base` for finding the service bound to the given controller
28
+
29
+ ### 2.5.0
30
+
31
+ - Client exceptions raised now contain mapped subclasses, such as `Gruf::Client::Errors::InvalidArgument`
32
+ - Client exceptions will also now catch StandardError and GRPC::Core errors, and handle them as Internal errors
33
+ - Added SynchronizedClient which prevents multiple calls to the same endpoint with the same params at
34
+ a given time. This is useful for mitigating thundering herds. To skip this behavior for certain endpoints,
35
+ pass the `options[:unsynchronized_methods]` param with a list of method names (as symbols).
36
+
37
+ ### 2.4.2
38
+
39
+ - Added error handling for GRPC::Core::CallError, a low-level error in the grpc library that does not inherit
40
+ from StandardError. [#59]
41
+ - Removed `Thread.abort\_on\_exception = true`. Exceptions should be handled by gruf or the application,
42
+ and should not cause the server process to crash. [#59]
43
+ - Added guard for size of trailing metadata attached to grpc call. The default max for http2 trailing metadata
44
+ in the gRPC C library is 8kb. If we go over that limit (either through custom metadata attached to the
45
+ error by the application, or via the error payload encoded by the error serializer), the gRPC library
46
+ will throw RESOURCE\_EXHAUSTED. Gruf now detects this case, and attempts to prevent it by logging the
47
+ original error and substituting it with an internal error indicating that the metadata was too large. [#60]
48
+ - Truncate stack trace in error payload to help avoid overflowing the trailing metadata. Added backtrace\_limit
49
+ configuration parameter, which defaults to 10.[#60]
50
+
51
+ ### 2.4.1
52
+
53
+ - Safer configuration of GRPC::RpcServer. From now on, use `Gruf.rpc_server_options` for the params
54
+ to be sent to GPRC::RpcServer. Also provide sane defaults for params for GRPC::RpcServer. [#55]
55
+ - Added ability to monitor `RESOURCE_EXHAUSTED` and `UNIMPLEMENTED`. By setting `event_listener_proc` in
56
+ the Gruf configuration, you will receive a callback when these events occur. The parameter to your
57
+ callback will be a symbol (`:thread_pool_exhausted` or `:unimplemented`). Others may be added in the future.
58
+
59
+ ### 2.4.0
60
+
61
+ - Added a hash of error log levels to RequestLogging interceptor, mapping error code to level of logging to use. To
62
+ override the level of logging per error response, provide a map of codes to log level in options, key :log_levels.
63
+ The default is :error log level.
64
+
65
+ ### 2.3.0
66
+
67
+ - Add Gruf::Interceptors::ClientInterceptor for intercepting outbound client calls
68
+ - Add command-line arguments to the gruf binstub
69
+ - Add ability to specify server hostname via CLI argument
70
+
71
+ ### 2.2.2
72
+
73
+ - Add ignore_methods option for RequestLogging interceptor [#45]
74
+
75
+ ### 2.2.1
76
+
77
+ - Now changes proc title once server is ready to process incoming requests [#44]
78
+ - Gruf now requires gRPC 1.10.x+ due to various fixes and improvements in the gRPC core libraries
79
+
80
+ ### 2.2.0
81
+
82
+ - Run server in a monitored thread to allow for trapped exits on sigints [#43]
83
+
84
+ ### 2.1.1
85
+
86
+ - Add ability to pass in client stub options into Gruf::Client
87
+
88
+ ### 2.1.0
89
+
90
+ - Add ability to list, clear, insert before, insert after, and remove to a server's interceptor
91
+ registry
92
+ - Ensure interceptors and services cannot be adjusted on the server after it starts to
93
+ prevent threading issues
94
+ - [#36], [#37] Adds `response_class`, `request_class`, and `service` accessors to controller request
95
+
96
+ ### 2.0.3
97
+
98
+ - Fix regression [#35] where gruf was not able to be loaded outside of a Rails application
99
+
100
+ ### 2.0.2
101
+
102
+ - Update Rubocop to 0.51
103
+ - Fix issue [#32] where server was not handling signals (\ht @Parad0X)
104
+
105
+ ### 2.0.1
106
+
107
+ - Handle ActiveRecord connection management more gracefully (Fixes #30)
108
+
109
+ ### 2.0.0
110
+
111
+ Gruf 2.0 is a major shift from Gruf 1.0. See [UPGRADING.md](UPGRADING.md) for details.
112
+
113
+ - New thread-safe controller-based model
114
+ - New controller request object
115
+ - Hooks deprecated in favor of interceptors
116
+ - New interceptor timer utility class
117
+ - Default logging to logstash formatter
118
+ - Various Gruf::Server improvements
119
+
120
+ ### 1.2.7
121
+
122
+ - Fix issues where field errors were persisted in between separate calls
123
+
124
+ ### 1.2.6
125
+
126
+ - Fix issues with arity and bidirectional streaming
127
+
128
+ ### 1.2.5
129
+
130
+ - Fix reference issue for client and bidirectional streaming calls
131
+
132
+ ### 1.2.4
133
+
134
+ - Loosen explicit Protobuf dependency now that 3.4.0.2 is released
135
+ - Guard against nil params in logger blacklist
136
+
137
+ ### 1.2.3
138
+
139
+ - Support nested blacklist parameters in path.to.key format
140
+
141
+ ### 1.2.2
142
+
143
+ - Pin Google Protobuf to 3.3.x due to failures in protobuf in Ruby at 3.4.x
144
+
145
+ ### 1.2.1
146
+
147
+ - Added ability to pass in server options via new `server_options` configuration
148
+ attribute. (\ht @kruczjak)
149
+
150
+ ### 1.2.0
151
+
152
+ - Instrumentation hooks now execute similarly to outer_around hooks; they can
153
+ now instrument failures
154
+ - Instrumentation hooks now pass a `RequestContext` object that contains information
155
+ about the incoming request, instead of relying on instance variables
156
+ - StatsD hook now sends success/failure metrics for endpoints
157
+ - Add ability to turn off sending exception message on uncaught exception.
158
+ - Add configuration to set the error message when an uncaught exception is
159
+ handled by gruf.
160
+ - Add a request logging hook for Rails-style request logging, with optional
161
+ parameter logging, blacklists, and formatter support
162
+ - Optimizations around Symbol casting within service calls
163
+
164
+ ### 1.1.0
165
+
166
+ - Add the ability for call options to the client, which enables deadline setting
167
+
168
+ ### 1.0.0
169
+
170
+ - Bump gRPC to 1.4
171
+
172
+ ### 0.14.2
173
+
174
+ - Added rubocop style-guide checks
175
+
176
+ ### 0.14.1
177
+
178
+ - Updated license to MIT
179
+
180
+ ### 0.14.0
181
+
182
+ - Send gRPC status 16 (Unauthenticated) instead of 7 (PermissionDenied) when authentication fails
183
+
184
+ ### 0.13.0
185
+
186
+ - Move to gRPC 1.3.4
187
+
188
+ ### 0.12.2
189
+
190
+ - Add outer_around hook for wrapping the entire call chain
191
+
192
+ ### 0.12.1
193
+
194
+ - Add ability to specify a separate gRPC logger from the Gruf logger
195
+
196
+ ### 0.12.0
197
+
198
+ - Add ability to run multiple around hooks
199
+ - Fix bug with error handling that caused error messages to repeat across streams
200
+
201
+ ### 0.11.5
202
+
203
+ - Fix issue with around hook
204
+
205
+ ### 0.11.4
206
+
207
+ - Add catchall rescue handler to capture uncaught exceptions and
208
+ raise a GRPC::Internal error.
209
+ - Add Gruf.backtrace_on_error configuration value. If set, Gruf
210
+ will call Service.set_debug_info with the exception backtrace
211
+ if an uncaught exception occurs.
212
+
213
+ ### 0.11.3
214
+
215
+ - Pass the service instance into hooks for reference
216
+
217
+ ### 0.11.2
218
+
219
+ - Ensure timer is measuring in milliseconds
220
+
221
+ ### 0.11.1
222
+
223
+ - Fix issue with interceptor and call signature
224
+
225
+ ### 0.11.0
226
+
227
+ - Add instrumentation layer and ability to register new instrumentors
228
+ - Add out-of-the-box statsd instrumentation support
229
+
230
+ ### 0.10.0
231
+
232
+ - Rename Gruf::Endpoint to Gruf::Service
233
+ - Make services auto-mount to server upon declaration
234
+
235
+ ### 0.9.2
236
+
237
+ - Support mount command on services to allow automatic setup on the server
238
+ - Cleanup and consolidate binstub to prevent need for custom binstub per-app
239
+
240
+ ### 0.9.1
241
+
242
+ - Relax licensing to a clean BSD license
243
+
244
+ ### 0.9.0
245
+
246
+ - Initial public release
@@ -0,0 +1,46 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ ## Our Standards
8
+
9
+ Examples of behavior that contributes to creating a positive environment include:
10
+
11
+ * Using welcoming and inclusive language
12
+ * Being respectful of differing viewpoints and experiences
13
+ * Gracefully accepting constructive criticism
14
+ * Focusing on what is best for the community
15
+ * Showing empathy towards other community members
16
+
17
+ Examples of unacceptable behavior by participants include:
18
+
19
+ * The use of sexualized language or imagery and unwelcome sexual attention or advances
20
+ * Trolling, insulting/derogatory comments, and personal or political attacks
21
+ * Public or private harassment
22
+ * Publishing others' private information, such as a physical or electronic address, without explicit permission
23
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
24
+
25
+ ## Our Responsibilities
26
+
27
+ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28
+
29
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ ## Scope
32
+
33
+ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34
+
35
+ ## Enforcement
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at engineering@bigcommerce.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38
+
39
+ Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40
+
41
+ ## Attribution
42
+
43
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44
+
45
+ [homepage]: http://contributor-covenant.org
46
+ [version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,544 @@
1
+ # gruf - gRPC Ruby Framework
2
+
3
+ [![CircleCI](https://circleci.com/gh/bigcommerce/gruf/tree/master.svg?style=svg)](https://circleci.com/gh/bigcommerce/gruf/tree/master) [![Gem Version](https://badge.fury.io/rb/gruf.svg)](https://badge.fury.io/rb/gruf) [![Documentation](https://inch-ci.org/github/bigcommerce/gruf.svg?branch=master)](https://inch-ci.org/github/bigcommerce/gruf?branch=master)
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 controllers with request context support
12
+ * Full interceptors with timing and unified request context support
13
+ * Robust client error handling and metadata transport abilities
14
+ * Server authentication via interceptors, with basic auth with multiple key support built in
15
+ * TLS support for client-server auth, though we recommend using [linkerd](https://linkerd.io/) instead
16
+ * Error data serialization in output metadata to allow fine-grained error handling in the transport while
17
+ still preserving gRPC BadStatus codes
18
+ * Server and client execution timings in responses
19
+
20
+ gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.5.
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).
23
+
24
+ ## Installation
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
509
+
510
+ ## Demo Rails App
511
+
512
+ There is a [demonstration Rails application here](https://github.com/bigcommerce/gruf-demo) you can
513
+ view and clone that shows how to integrate Gruf into an existing Rails application.
514
+
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
+ ## License
530
+
531
+ Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
532
+
533
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
534
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
535
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
536
+ persons to whom the Software is furnished to do so, subject to the following conditions:
537
+
538
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
539
+ Software.
540
+
541
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
542
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
543
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
544
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.