istox_gruf 2.7.1

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.
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,105 @@
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
+ class Client < SimpleDelegator
20
+ ##
21
+ # Translates exceptions into Gruf::Client::Errors
22
+ #
23
+ class ErrorFactory
24
+ ##
25
+ # @param [Class] default_class
26
+ # @param [Class] deserializer_class
27
+ # @param [String|Symbol] metadata_key
28
+ #
29
+ def initialize(
30
+ default_class: nil,
31
+ deserializer_class: nil,
32
+ metadata_key: nil
33
+ )
34
+ @default_class = default_class || Gruf::Client::Errors::Internal
35
+ @metadata_key = (metadata_key || Gruf.error_metadata_key).to_s
36
+ @deserializer_class = deserializer_class || default_serializer
37
+ end
38
+
39
+ ##
40
+ # Determine the proper error class to raise given the incoming exception. This will attempt to coalesce the
41
+ # exception object into the appropriate Gruf::Client::Errors subclass, or fallback to the default class if none
42
+ # is found (or it is a StandardError or higher-level error). It will leave alone Signals instead of attempting to
43
+ # coalesce them.
44
+ #
45
+ # @param [Exception] exception
46
+ # @return [Gruf::Client::Errors::Base|SignalException]
47
+ #
48
+ def from_exception(exception)
49
+ # passthrough on Signals, we don't want to mess with these
50
+ return exception if exception.is_a?(SignalException)
51
+
52
+ exception_class = determine_class(exception)
53
+ if exception.is_a?(GRPC::BadStatus)
54
+ # if it's a GRPC::BadStatus code, let's check for any trailing error metadata and decode it
55
+ exception_class.new(deserialize(exception))
56
+ else
57
+ # otherwise, let's just capture the error and build the wrapper class
58
+ exception_class.new(exception)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ ##
65
+ # Deserialize any trailing metadata error payload from the exception
66
+ #
67
+ # @param [Gruf::Client::Errors::Base]
68
+ # @return [String]
69
+ #
70
+ def deserialize(exception)
71
+ if exception.respond_to?(:metadata)
72
+ key = exception.metadata.key?(@metadata_key.to_s) ? @metadata_key.to_s : @metadata_key.to_sym
73
+ return @deserializer_class.new(exception.metadata[key]).deserialize if exception.metadata.key?(key)
74
+ end
75
+
76
+ exception
77
+ end
78
+
79
+ ##
80
+ # @param [Exception] exception
81
+ # @return [Gruf::Client::Errors::Base]
82
+ #
83
+ def determine_class(exception)
84
+ error_class = Gruf::Client::Errors.const_get(exception.class.name.demodulize)
85
+
86
+ # Ruby module inheritance will have StandardError, ScriptError, etc still get to this point
87
+ # So we need to explicitly check for ancestry here
88
+ return @default_class unless error_class.ancestors.include?(Gruf::Client::Errors::Base)
89
+
90
+ error_class
91
+ rescue NameError => _e
92
+ @default_class
93
+ end
94
+
95
+ ##
96
+ # @return [Gruf::Serializers::Errors::Base]
97
+ #
98
+ def default_serializer
99
+ return Gruf::Serializers::Errors::Json unless Gruf.error_serializer
100
+
101
+ Gruf.error_serializer.is_a?(Class) ? Gruf.error_serializer : Gruf.error_serializer.to_s.constantize
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,137 @@
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
+ ##
20
+ # Represents configuration settings for the system
21
+ #
22
+ module Configuration
23
+ VALID_CONFIG_KEYS = {
24
+ root_path: '',
25
+ server_binding_url: '0.0.0.0:9001',
26
+ server_options: {},
27
+ interceptors: nil,
28
+ hooks: nil,
29
+ default_client_host: '',
30
+ use_ssl: false,
31
+ ssl_crt_file: '',
32
+ ssl_key_file: '',
33
+ controllers_path: '',
34
+ services: [],
35
+ logger: nil,
36
+ grpc_logger: nil,
37
+ error_metadata_key: :'error-internal-bin',
38
+ error_serializer: nil,
39
+ append_server_errors_to_trailing_metadata: true,
40
+ use_default_interceptors: true,
41
+ backtrace_on_error: false,
42
+ backtrace_limit: 10,
43
+ use_exception_message: true,
44
+ internal_error_message: 'Internal Server Error',
45
+ event_listener_proc: nil,
46
+ synchronized_client_internal_cache_expiry: 60,
47
+ rpc_server_options: {
48
+ pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
49
+ max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
50
+ poll_period: GRPC::RpcServer::DEFAULT_POLL_PERIOD,
51
+ pool_keep_alive: GRPC::Pool::DEFAULT_KEEP_ALIVE,
52
+ connect_md_proc: nil,
53
+ server_args: {}
54
+ }.freeze
55
+ }.freeze
56
+
57
+ attr_accessor(* VALID_CONFIG_KEYS.keys)
58
+
59
+ ##
60
+ # Whenever this is extended into a class, setup the defaults
61
+ #
62
+ def self.extended(base)
63
+ if defined?(Rails)
64
+ Gruf::Integrations::Rails::Railtie.config.before_initialize { base.reset }
65
+ else
66
+ base.reset
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Yield self for ruby-style initialization
72
+ #
73
+ # @yields [Gruf::Configuration] The configuration object for gruf
74
+ # @return [Gruf::Configuration] The configuration object for gruf
75
+ #
76
+ def configure
77
+ yield self
78
+ end
79
+
80
+ ##
81
+ # Return the current configuration options as a Hash
82
+ #
83
+ # @return [Hash] The configuration for gruf, represented as a Hash
84
+ #
85
+ def options
86
+ opts = {}
87
+ VALID_CONFIG_KEYS.each_key do |k|
88
+ opts.merge!(k => send(k))
89
+ end
90
+ opts
91
+ end
92
+
93
+ ##
94
+ # Set the default configuration onto the extended class
95
+ #
96
+ # @return [Hash] options The reset options hash
97
+ #
98
+ def reset
99
+ VALID_CONFIG_KEYS.each do |k, v|
100
+ send((k.to_s + '='), v)
101
+ end
102
+ self.interceptors = Gruf::Interceptors::Registry.new
103
+ self.hooks = Gruf::Hooks::Registry.new
104
+ self.root_path = Rails.root.to_s.chomp('/') if defined?(Rails)
105
+ if defined?(Rails) && Rails.logger
106
+ self.logger = Rails.logger
107
+ else
108
+ require 'logger'
109
+ self.logger = ::Logger.new(STDOUT)
110
+ end
111
+ self.grpc_logger = logger if grpc_logger.nil?
112
+ self.ssl_crt_file = "#{root_path}config/ssl/#{environment}.crt"
113
+ self.ssl_key_file = "#{root_path}config/ssl/#{environment}.key"
114
+ self.controllers_path = root_path.to_s.empty? ? 'app/rpc' : "#{root_path}/app/rpc"
115
+ if use_default_interceptors
116
+ interceptors.use(Gruf::Interceptors::ActiveRecord::ConnectionReset)
117
+ interceptors.use(Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
118
+ end
119
+ options
120
+ end
121
+
122
+ private
123
+
124
+ ##
125
+ # Automatically determine environment
126
+ #
127
+ # @return [String] The current Ruby environment
128
+ #
129
+ def environment
130
+ if defined?(Rails)
131
+ Rails.env.to_s
132
+ else
133
+ (ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development').to_s
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,102 @@
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 'request'
19
+ require_relative 'service_binder'
20
+
21
+ module Gruf
22
+ module Controllers
23
+ ##
24
+ # Base controller object for Gruf gRPC requests
25
+ #
26
+ class Base
27
+ include Gruf::Errors::Helpers
28
+
29
+ # @var [Gruf::Controller::Request] request
30
+ attr_reader :request
31
+ # @var [Gruf::Error] error
32
+ attr_reader :error
33
+
34
+ class << self
35
+ # @var [GRPC::GenericService] bound_service
36
+ attr_reader :bound_service
37
+ end
38
+
39
+ ##
40
+ # Initialize the controller within the given request context
41
+ #
42
+ # @param [Symbol] method_key The gRPC method that this controller relates to
43
+ # @param [GRPC::GenericService] service The gRPC service stub for this controller
44
+ # @param [GRPC::RpcDesc] rpc_desc The RPC descriptor for this service method
45
+ # @param [GRPC::ActiveCall] active_call The gRPC ActiveCall object
46
+ # @param [Google::Protobuf::MessageExts] message The incoming protobuf request message
47
+ #
48
+ def initialize(method_key:, service:, rpc_desc:, active_call:, message:)
49
+ @request = Request.new(
50
+ method_key: method_key,
51
+ service: service,
52
+ rpc_desc: rpc_desc,
53
+ active_call: active_call,
54
+ message: message
55
+ )
56
+ @error = Gruf::Error.new
57
+ @interceptors = Gruf.interceptors.prepare(@request, @error)
58
+ end
59
+
60
+ ##
61
+ # Bind the controller to the given service and add it to the service registry
62
+ #
63
+ # @param [GRPC::GenericService] service The name of the service to bind this controller to
64
+ #
65
+ def self.bind(service)
66
+ service_class = service.name.constantize
67
+ Gruf.services << service_class
68
+ @bound_service = service_class
69
+ ServiceBinder.new(service_class).bind!(self)
70
+ end
71
+
72
+ ##
73
+ # Call a method on this controller.
74
+ # Override this in a subclass to modify the behavior around processing a method
75
+ #
76
+ # @param [Symbol] method_key The name of the gRPC service method being called as a Symbol
77
+ # @param [block] &block The passed block for executing the method
78
+ #
79
+ def process_action(method_key, &block)
80
+ send(method_key, &block)
81
+ end
82
+
83
+ ##
84
+ # Call a method on this controller
85
+ #
86
+ # @param [Symbol] method_key The name of the gRPC service method being called as a Symbol
87
+ # @param [block] &block The passed block for executing the method
88
+ #
89
+ def call(method_key, &block)
90
+ Interceptors::Context.new(@interceptors).intercept! do
91
+ process_action(method_key, &block)
92
+ end
93
+ rescue GRPC::BadStatus
94
+ raise # passthrough, to be caught by Gruf::Interceptors::Timer
95
+ rescue GRPC::Core::CallError, StandardError => e # CallError is not a StandardError
96
+ set_debug_info(e.message, e.backtrace) if Gruf.backtrace_on_error
97
+ error_message = Gruf.use_exception_message ? e.message : Gruf.internal_error_message
98
+ fail!(:internal, :unknown, error_message)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,121 @@
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
+ # Encapsulates a request for a controller
22
+ #
23
+ class Request
24
+ # @var [Object] message
25
+ attr_reader :message
26
+ # @var [GRPC::ActiveCall] active_call
27
+ attr_reader :active_call
28
+ # @var [Symbol] method_key
29
+ attr_reader :method_key
30
+ # @var [Gruf::Controllers::Request::Type] type
31
+ attr_reader :type
32
+ # @var [Class] service
33
+ attr_reader :service
34
+
35
+ delegate :metadata, to: :active_call
36
+ delegate :messages, :client_streamer?, :server_streamer?, :bidi_streamer?, :request_response?, to: :type
37
+
38
+ ##
39
+ # Abstract representation of a gRPC request type
40
+ #
41
+ class Type
42
+ delegate :client_streamer?, :server_streamer?, :bidi_streamer?, :request_response?, to: :@rpc_desc
43
+
44
+ ##
45
+ # Initialize a new request type object
46
+ #
47
+ # @param [GRPC::RpcDesc] rpc_desc The RPC descriptor for the request type
48
+ #
49
+ def initialize(rpc_desc)
50
+ @rpc_desc = rpc_desc
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Initialize an inbound controller request object
56
+ #
57
+ # @param [Symbol] method_key The method symbol of the RPC method being executed
58
+ # @param [Class] service The class of the service being executed against
59
+ # @param [GRPC::RpcDesc] rpc_desc The RPC descriptor of the call
60
+ # @param [GRPC::ActiveCall] active_call The restricted view of the call
61
+ # @param [Object] message The protobuf message (or messages) of the request
62
+ #
63
+ def initialize(method_key:, service:, rpc_desc:, active_call:, message:)
64
+ @method_key = method_key
65
+ @service = service
66
+ @active_call = active_call
67
+ @message = message
68
+ @rpc_desc = rpc_desc
69
+ @type = Type.new(rpc_desc)
70
+ end
71
+
72
+ ##
73
+ # Returns the service name as a translated name separated by periods. Strips
74
+ # the superfluous "Service" suffix from the name
75
+ #
76
+ # @return [String] The mapped service key
77
+ #
78
+ def service_key
79
+ @service.name.underscore.tr('/', '.').gsub('.service', '')
80
+ end
81
+
82
+ ##
83
+ # @return [Class] The class of the response message
84
+ #
85
+ def response_class
86
+ @rpc_desc.output
87
+ end
88
+
89
+ ##
90
+ # @return [Class] The class of the request message
91
+ #
92
+ def request_class
93
+ @rpc_desc.input
94
+ end
95
+
96
+ ##
97
+ # Parse the method signature into a service.method name format
98
+ #
99
+ # @return [String] The parsed service method name
100
+ #
101
+ def method_name
102
+ "#{service_key}.#{method_key}"
103
+ end
104
+
105
+ ##
106
+ # Return all messages for this request, properly handling different request types
107
+ #
108
+ # @return Enumerable<Object> All messages for this request
109
+ #
110
+ def messages
111
+ if client_streamer?
112
+ message.call { |msg| yield msg }
113
+ elsif bidi_streamer?
114
+ message
115
+ else
116
+ [message]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end