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,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Controllers
20
+ ##
21
+ # Binds gRPC services to a gruf controller
22
+ #
23
+ class ServiceBinder
24
+ ##
25
+ # Represents a bound RPC descriptor for future-proofing internal helpers
26
+ #
27
+ class BoundDesc < SimpleDelegator; end
28
+
29
+ ##
30
+ # Initialize a service binder instance with the given service
31
+ #
32
+ # @param [GRPC::GenericService] service The gRPC service stub to bind
33
+ #
34
+ def initialize(service)
35
+ @service = service
36
+ end
37
+
38
+ ##
39
+ # Bind all methods on the service to the passed controller
40
+ #
41
+ # @param [Gruf::Controllers::Base] controller
42
+ #
43
+ def bind!(controller)
44
+ rpc_methods.each { |name, desc| bind_method(controller, name, desc) }
45
+ end
46
+
47
+ private
48
+
49
+ ##
50
+ # Bind the grpc methods to the service, allowing for server interception and execution control
51
+ #
52
+ # @param [Gruf::Controllers::Base] controller
53
+ # @param [Symbol] method_name
54
+ # @param [BoundDesc] desc
55
+ #
56
+ def bind_method(controller, method_name, desc)
57
+ method_key = method_name.to_s.underscore.to_sym
58
+ service_ref = @service
59
+
60
+ @service.class_eval do
61
+ if desc.request_response?
62
+ define_method(method_key) do |message, active_call|
63
+ c = controller.new(
64
+ method_key: method_key,
65
+ service: service_ref,
66
+ message: message,
67
+ active_call: active_call,
68
+ rpc_desc: desc
69
+ )
70
+ c.call(method_key)
71
+ end
72
+ elsif desc.client_streamer?
73
+ define_method(method_key) do |active_call|
74
+ c = controller.new(
75
+ method_key: method_key,
76
+ service: service_ref,
77
+ message: proc { |&block| active_call.each_remote_read(&block) },
78
+ active_call: active_call,
79
+ rpc_desc: desc
80
+ )
81
+ c.call(method_key)
82
+ end
83
+ elsif desc.server_streamer?
84
+ define_method(method_key) do |message, active_call, &block|
85
+ c = controller.new(
86
+ method_key: method_key,
87
+ service: service_ref,
88
+ message: message,
89
+ active_call: active_call,
90
+ rpc_desc: desc
91
+ )
92
+ c.call(method_key, &block)
93
+ end
94
+ else # bidi
95
+ define_method(method_key) do |messages, active_call, &block|
96
+ c = controller.new(
97
+ method_key: method_key,
98
+ service: service_ref,
99
+ message: messages,
100
+ active_call: active_call,
101
+ rpc_desc: desc
102
+ )
103
+ c.call(method_key, &block)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ ##
110
+ # @return Array<Gruf::Controllers::ServiceBinder::BoundDesc>
111
+ #
112
+ def rpc_methods
113
+ @service.rpc_descs.map { |rd| BoundDesc.new(rd) }
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ require_relative 'errors/field'
19
+ require_relative 'errors/debug_info'
20
+ require_relative 'serializers/errors/base'
21
+ require_relative 'serializers/errors/json'
22
+
23
+ module Gruf
24
+ ##
25
+ # Represents a error that can be transformed into a gRPC error and have metadata attached to the trailing headers.
26
+ # This layer acts as an middle layer that can have metadata injection, tracing support, and other functionality
27
+ # not present in the gRPC core.
28
+ #
29
+ class Error
30
+ include Gruf::Loggable
31
+
32
+ # @return [Hash<GRPC::BadStatus>] A hash mapping of gRPC BadStatus codes to error symbols
33
+ TYPES = {
34
+ ok: GRPC::Ok,
35
+ cancelled: GRPC::Cancelled,
36
+ unknown: GRPC::Unknown,
37
+ invalid_argument: GRPC::InvalidArgument,
38
+ bad_request: GRPC::InvalidArgument,
39
+ deadline_exceeded: GRPC::DeadlineExceeded,
40
+ not_found: GRPC::NotFound,
41
+ already_exists: GRPC::AlreadyExists,
42
+ unauthorized: GRPC::PermissionDenied,
43
+ permission_denied: GRPC::PermissionDenied,
44
+ unauthenticated: GRPC::Unauthenticated,
45
+ resource_exhausted: GRPC::ResourceExhausted,
46
+ failed_precondition: GRPC::FailedPrecondition,
47
+ aborted: GRPC::Aborted,
48
+ out_of_range: GRPC::OutOfRange,
49
+ unimplemented: GRPC::Unimplemented,
50
+ internal: GRPC::Internal,
51
+ unavailable: GRPC::Unavailable,
52
+ data_loss: GRPC::DataLoss
53
+ }.freeze
54
+
55
+ # Default limit on trailing metadata is 8KB. We need to be careful
56
+ # not to overflow this limit, or the response message will never
57
+ # be sent. Instead, resource_exhausted will be thrown.
58
+ MAX_METADATA_SIZE = 7.5 * 1_024
59
+ METADATA_SIZE_EXCEEDED_CODE = 'metadata_size_exceeded'
60
+ METADATA_SIZE_EXCEEDED_MSG = 'Metadata too long, risks exceeding http2 trailing metadata limit.'
61
+
62
+ # @return [Symbol] The given internal gRPC code for the error
63
+ attr_accessor :code
64
+ # @return [Symbol] An arbitrary application code that can be used for logical processing of the error by the client
65
+ attr_accessor :app_code
66
+ # @return [String] The error message returned by the server
67
+ attr_accessor :message
68
+ # @return [Array] An array of field errors that can be returned by the server
69
+ attr_accessor :field_errors
70
+ # @return [Errors::DebugInfo] A object containing debugging information, such as a stack trace and exception name,
71
+ # that can be used to debug an given error response. This is sent by the server over the trailing metadata.
72
+ attr_accessor :debug_info
73
+ # @return [GRPC::BadStatus] The gRPC BadStatus error object that was generated
74
+ attr_writer :grpc_error
75
+ # @return [Hash] The trailing metadata that was attached to the error
76
+ attr_reader :metadata
77
+
78
+ ##
79
+ # Initialize the error, setting default values
80
+ #
81
+ # @param [Hash] args (Optional) An optional hash of arguments that will set fields on the error object
82
+ #
83
+ def initialize(args = {})
84
+ @field_errors = []
85
+ @metadata = {}
86
+ args.each do |k, v|
87
+ send("#{k}=", v) if respond_to?(k)
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Add a field error to this error package
93
+ #
94
+ # @param [Symbol] field_name The field name for the error
95
+ # @param [Symbol] error_code The application error code for the error; e.g. :job_not_found
96
+ # @param [String] message The application error message for the error; e.g. "Job not found with ID 123"
97
+ #
98
+ def add_field_error(field_name, error_code, message = '')
99
+ @field_errors << Errors::Field.new(field_name, error_code, message)
100
+ end
101
+
102
+ ##
103
+ # Return true if there are any present field errors
104
+ #
105
+ # @return [Boolean] True if the service has any field errors
106
+ #
107
+ def has_field_errors?
108
+ @field_errors.any?
109
+ end
110
+
111
+ ##
112
+ # Set the debugging information for the error message
113
+ #
114
+ # @param [String] detail The detailed message generated by the exception
115
+ # @param [Array<String>] stack_trace An array of strings that represents the exception backtrace generated by the
116
+ # service
117
+ #
118
+ def set_debug_info(detail, stack_trace = [])
119
+ @debug_info = Errors::DebugInfo.new(detail, stack_trace)
120
+ end
121
+
122
+ ##
123
+ # Ensure all metadata values are strings as HTTP/2 requires string values for transport
124
+ #
125
+ # @param [Hash] metadata The existing metadata hash
126
+ # @return [Hash] The newly set metadata
127
+ #
128
+ def metadata=(metadata)
129
+ @metadata = metadata.map { |k, str| [k, str.to_s] }.to_h
130
+ end
131
+
132
+ ##
133
+ # Serialize the error for transport
134
+ #
135
+ # @return [String] The serialized error message
136
+ #
137
+ def serialize
138
+ serializer = serializer_class.new(self)
139
+ serializer.serialize.to_s
140
+ end
141
+
142
+ ##
143
+ # Update the trailing metadata on the given gRPC call, including the error payload if configured
144
+ # to do so.
145
+ #
146
+ # @param [GRPC::ActiveCall] active_call The marshalled gRPC call
147
+ # @return [Error] Return the error itself after updating metadata on the given gRPC call.
148
+ # In the case of a metadata overflow error, we replace the current error with
149
+ # a new one that won't cause a low-level http2 error.
150
+ #
151
+ def attach_to_call(active_call)
152
+ metadata[Gruf.error_metadata_key.to_sym] = serialize if Gruf.append_server_errors_to_trailing_metadata
153
+ return self if metadata.empty? || !active_call || !active_call.respond_to?(:output_metadata)
154
+
155
+ # Check if we've overflown the maximum size of output metadata. If so,
156
+ # log a warning and replace the metadata with something smaller to avoid
157
+ # resource exhausted errors.
158
+ if metadata.inspect.size > MAX_METADATA_SIZE
159
+ code = METADATA_SIZE_EXCEEDED_CODE
160
+ msg = METADATA_SIZE_EXCEEDED_MSG
161
+ logger.warn "#{code}: #{msg} Original error: #{to_h.inspect}"
162
+ err = Gruf::Error.new(code: :internal, app_code: code, message: msg)
163
+ return err.attach_to_call(active_call)
164
+ end
165
+
166
+ active_call.output_metadata.update(metadata)
167
+ self
168
+ end
169
+
170
+ ##
171
+ # Fail the current gRPC call with the given error, properly attaching it to the call and raising the appropriate
172
+ # gRPC BadStatus code.
173
+ #
174
+ # @param [GRPC::ActiveCall] active_call The marshalled gRPC call
175
+ # @return [GRPC::BadStatus] The gRPC BadStatus code this error is mapped to
176
+ #
177
+ def fail!(active_call)
178
+ raise attach_to_call(active_call).grpc_error
179
+ end
180
+
181
+ ##
182
+ # Return the error represented in Hash form
183
+ #
184
+ # @return [Hash] The error as a hash
185
+ #
186
+ def to_h
187
+ {
188
+ code: code,
189
+ app_code: app_code,
190
+ message: message,
191
+ field_errors: field_errors.map(&:to_h),
192
+ debug_info: debug_info.to_h
193
+ }
194
+ end
195
+
196
+ ##
197
+ # Return the appropriately mapped GRPC::BadStatus error object for this error
198
+ #
199
+ # @return [GRPC::BadStatus]
200
+ #
201
+ def grpc_error
202
+ md = @metadata || {}
203
+ @grpc_error = grpc_class.new(message, **md)
204
+ end
205
+
206
+ private
207
+
208
+ ##
209
+ # Return the error serializer being used for gruf
210
+ #
211
+ # @return [Gruf::Serializers::Errors::Base]
212
+ #
213
+ def serializer_class
214
+ if Gruf.error_serializer
215
+ Gruf.error_serializer.is_a?(Class) ? Gruf.error_serializer : Gruf.error_serializer.to_s.constantize
216
+ else
217
+ Gruf::Serializers::Errors::Json
218
+ end
219
+ end
220
+
221
+ ##
222
+ # Return the appropriate gRPC class for the given error code
223
+ #
224
+ # @return [Class] The gRPC error class
225
+ #
226
+ def grpc_class
227
+ TYPES[code]
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Errors
20
+ ##
21
+ # Represents debugging information for an exception that occurred in a gRPC service
22
+ #
23
+ class DebugInfo
24
+ # @return [String] The detail message of the exception
25
+ attr_reader :detail
26
+ # @return [Array<String>] The stack trace generated by the exception as an array of strings
27
+ attr_reader :stack_trace
28
+
29
+ ##
30
+ # @param [String] detail The detail message of the exception
31
+ # @param [Array<String>] stack_trace The stack trace generated by the exception as an array of strings
32
+ #
33
+ def initialize(detail, stack_trace = [])
34
+ @detail = detail
35
+ @stack_trace = (stack_trace.is_a?(String) ? stack_trace.split("\n") : stack_trace)
36
+
37
+ # Limit the size of the stack trace to reduce risk of overflowing metadata
38
+ stack_trace_limit = Gruf.backtrace_limit.to_i
39
+ stack_trace_limit = 10 if stack_trace_limit.negative?
40
+ @stack_trace = @stack_trace[0..stack_trace_limit] if stack_trace_limit.positive?
41
+ end
42
+
43
+ ##
44
+ # Return this object marshalled into a hash
45
+ #
46
+ # @return [Hash] The debug info represented as a hash
47
+ #
48
+ def to_h
49
+ {
50
+ detail: detail,
51
+ stack_trace: stack_trace
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Errors
20
+ ##
21
+ # Represents a field-specific error
22
+ #
23
+ class Field
24
+ # @return [Symbol] The name of the field as a Symbol
25
+ attr_reader :field_name
26
+ # @return [Symbol] The application error code for the field, e.g. :job_not_found
27
+ attr_reader :error_code
28
+ # @return [String] The error message for the field, e.g. "Job with ID 123 not found"
29
+ attr_reader :message
30
+
31
+ ##
32
+ # @param [Symbol] field_name The name of the field as a Symbol
33
+ # @param [Symbol] error_code The application error code for the field, e.g. :job_not_found
34
+ # @param [String] message (Optional) The error message for the field, e.g. "Job with ID 123 not found"
35
+ #
36
+ def initialize(field_name, error_code, message = '')
37
+ @field_name = field_name
38
+ @error_code = error_code
39
+ @message = message
40
+ end
41
+
42
+ ##
43
+ # Return the field error represented as a hash
44
+ #
45
+ # @return [Hash] The error represented as a hash
46
+ #
47
+ def to_h
48
+ {
49
+ field_name: field_name,
50
+ error_code: error_code,
51
+ message: message
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end