gruf 2.2.2 → 2.9.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +107 -12
- data/CODE_OF_CONDUCT.md +38 -41
- data/README.md +8 -360
- data/bin/gruf +2 -12
- data/gruf.gemspec +31 -7
- data/lib/gruf/cli/executor.rb +102 -0
- data/lib/gruf/client/error.rb +68 -0
- data/lib/gruf/client/error_factory.rb +105 -0
- data/lib/gruf/client.rb +52 -38
- data/lib/gruf/configuration.rb +25 -5
- data/lib/gruf/controllers/base.rb +35 -12
- data/lib/gruf/controllers/request.rb +21 -10
- data/lib/gruf/controllers/service_binder.rb +38 -6
- data/lib/gruf/error.rb +34 -9
- data/lib/gruf/errors/debug_info.rb +9 -2
- data/lib/gruf/errors/field.rb +2 -0
- data/lib/gruf/errors/helpers.rb +5 -0
- data/lib/gruf/hooks/base.rb +34 -0
- data/lib/gruf/hooks/executor.rb +47 -0
- data/lib/gruf/hooks/registry.rb +159 -0
- data/lib/gruf/instrumentable_grpc_server.rb +64 -0
- data/lib/gruf/integrations/rails/railtie.rb +10 -0
- data/lib/gruf/interceptors/active_record/connection_reset.rb +4 -3
- data/lib/gruf/interceptors/authentication/basic.rb +10 -2
- data/lib/gruf/interceptors/base.rb +3 -0
- data/lib/gruf/interceptors/client_interceptor.rb +117 -0
- data/lib/gruf/interceptors/context.rb +6 -4
- data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +5 -4
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +5 -1
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +5 -1
- data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +5 -1
- data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +60 -29
- data/lib/gruf/interceptors/instrumentation/statsd.rb +5 -4
- data/lib/gruf/interceptors/registry.rb +6 -1
- data/lib/gruf/interceptors/server_interceptor.rb +2 -0
- data/lib/gruf/interceptors/timer.rb +12 -2
- data/lib/gruf/loggable.rb +2 -0
- data/lib/gruf/logging.rb +2 -0
- data/lib/gruf/outbound/request_context.rb +71 -0
- data/lib/gruf/response.rb +5 -2
- data/lib/gruf/serializers/errors/base.rb +2 -0
- data/lib/gruf/serializers/errors/json.rb +2 -0
- data/lib/gruf/server.rb +57 -23
- data/lib/gruf/synchronized_client.rb +97 -0
- data/lib/gruf/timer.rb +9 -6
- data/lib/gruf/version.rb +3 -1
- data/lib/gruf.rb +10 -0
- metadata +254 -17
data/gruf.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
3
4
|
#
|
4
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
@@ -14,7 +15,7 @@
|
|
14
15
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
15
16
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
17
|
#
|
17
|
-
|
18
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
18
19
|
require 'gruf/version'
|
19
20
|
|
20
21
|
Gem::Specification.new do |spec|
|
@@ -25,18 +26,41 @@ Gem::Specification.new do |spec|
|
|
25
26
|
spec.licenses = ['MIT']
|
26
27
|
|
27
28
|
spec.summary = 'gRPC Ruby Framework'
|
28
|
-
spec.description =
|
29
|
+
spec.description = 'gRPC Ruby Framework for building complex gRPC applications at scale'
|
29
30
|
spec.homepage = 'https://github.com/bigcommerce/gruf'
|
30
31
|
|
31
32
|
spec.files = Dir['README.md', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'lib/**/*', 'gruf.gemspec']
|
32
|
-
spec.executables
|
33
|
+
spec.executables << 'gruf'
|
33
34
|
spec.require_paths = ['lib']
|
34
35
|
|
36
|
+
spec.required_ruby_version = '>= 2.4', '< 3.1'
|
37
|
+
|
35
38
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
36
|
-
spec.add_development_dependency '
|
37
|
-
|
39
|
+
spec.add_development_dependency 'bundler-audit', '>= 0.6'
|
40
|
+
# rubocop:disable Gemspec/RubyVersionGlobalsUsage
|
41
|
+
spec.add_development_dependency(
|
42
|
+
'factory_bot',
|
43
|
+
(Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5') ? '>= 6.1' : '~> 5.2')
|
44
|
+
)
|
45
|
+
# rubocop:enable Gemspec/RubyVersionGlobalsUsage
|
46
|
+
spec.add_development_dependency 'ffaker', '>= 2.15'
|
47
|
+
spec.add_development_dependency 'pry', '~> 0.12'
|
48
|
+
spec.add_development_dependency 'pry-byebug', '>= 3.9'
|
49
|
+
spec.add_development_dependency 'rake', '>= 10.0'
|
50
|
+
spec.add_development_dependency 'rspec', '>= 3.8'
|
51
|
+
spec.add_development_dependency 'rspec_junit_formatter', '>= 0.4'
|
52
|
+
spec.add_development_dependency 'rubocop', '>= 1.0'
|
53
|
+
spec.add_development_dependency 'rubocop-performance', '>= 0.0.1'
|
54
|
+
spec.add_development_dependency 'rubocop-rspec', '>= 2.0'
|
55
|
+
spec.add_development_dependency 'rubocop-thread_safety', '>= 0.3'
|
56
|
+
spec.add_development_dependency 'simplecov', '>= 0.16'
|
38
57
|
|
58
|
+
spec.add_runtime_dependency 'activesupport', '> 4'
|
59
|
+
spec.add_runtime_dependency 'concurrent-ruby', '> 1'
|
60
|
+
spec.add_runtime_dependency 'e2mmap', '~> 0.1'
|
39
61
|
spec.add_runtime_dependency 'grpc', '~> 1.10'
|
40
62
|
spec.add_runtime_dependency 'grpc-tools', '~> 1.10'
|
41
|
-
spec.add_runtime_dependency '
|
63
|
+
spec.add_runtime_dependency 'json', '>= 2.3'
|
64
|
+
spec.add_runtime_dependency 'slop', '~> 4.6'
|
65
|
+
spec.add_runtime_dependency 'thwait', '~> 0.1'
|
42
66
|
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 'slop'
|
19
|
+
|
20
|
+
module Gruf
|
21
|
+
module Cli
|
22
|
+
##
|
23
|
+
# Handles execution of the gruf binstub, along with command-line arguments
|
24
|
+
#
|
25
|
+
class Executor
|
26
|
+
##
|
27
|
+
# @param [Hash|ARGV]
|
28
|
+
#
|
29
|
+
def initialize(
|
30
|
+
args = ARGV,
|
31
|
+
server: nil,
|
32
|
+
services: nil,
|
33
|
+
hook_executor: nil,
|
34
|
+
logger: nil
|
35
|
+
)
|
36
|
+
@args = args
|
37
|
+
setup! # ensure we set some defaults from CLI here so we can allow configuration
|
38
|
+
@services = services || Gruf.services
|
39
|
+
@hook_executor = hook_executor || Gruf::Hooks::Executor.new(hooks: Gruf.hooks&.prepare)
|
40
|
+
@server = server || Gruf::Server.new(Gruf.server_options)
|
41
|
+
@logger = logger || Gruf.logger || ::Logger.new($stderr)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Run the server
|
46
|
+
#
|
47
|
+
def run
|
48
|
+
exception = nil
|
49
|
+
begin
|
50
|
+
@services.each { |s| @server.add_service(s) }
|
51
|
+
@hook_executor.call(:before_server_start, server: @server)
|
52
|
+
@server.start!
|
53
|
+
rescue StandardError => e
|
54
|
+
exception = e
|
55
|
+
# Catch the exception here so that we always ensure the post hook runs
|
56
|
+
# This allows systems wanting to provide external server instrumentation
|
57
|
+
# the ability to properly handle server failures
|
58
|
+
@logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
|
59
|
+
end
|
60
|
+
@hook_executor.call(:after_server_stop, server: @server)
|
61
|
+
raise exception if exception
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
##
|
67
|
+
# Setup options for CLI execution and configure Gruf based on inputs
|
68
|
+
#
|
69
|
+
def setup!
|
70
|
+
opts = parse_options
|
71
|
+
|
72
|
+
Gruf.server_binding_url = opts[:host] if opts[:host]
|
73
|
+
if opts.suppress_default_interceptors?
|
74
|
+
Gruf.interceptors.remove(Gruf::Interceptors::ActiveRecord::ConnectionReset)
|
75
|
+
Gruf.interceptors.remove(Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
|
76
|
+
end
|
77
|
+
Gruf.backtrace_on_error = true if opts.backtrace_on_error?
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Parse all CLI arguments into an options result
|
82
|
+
#
|
83
|
+
# @return [Slop::Result]
|
84
|
+
#
|
85
|
+
def parse_options
|
86
|
+
::Slop.parse(@args) do |o|
|
87
|
+
o.null '-h', '--help', 'Display help message' do
|
88
|
+
puts o
|
89
|
+
exit(0)
|
90
|
+
end
|
91
|
+
o.string '--host', 'Specify the binding url for the gRPC service'
|
92
|
+
o.bool '--suppress-default-interceptors', 'Do not use the default interceptors'
|
93
|
+
o.bool '--backtrace-on-error', 'Push backtraces on exceptions to the error serializer'
|
94
|
+
o.null '-v', '--version', 'print gruf version' do
|
95
|
+
puts Gruf::VERSION
|
96
|
+
exit(0)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
+
# Represents an error that was returned from the server's trailing metadata. Used as a custom exception object
|
22
|
+
# that is instead raised in the case of the service returning serialized error data, as opposed to the normal
|
23
|
+
# GRPC::BadStatus error
|
24
|
+
#
|
25
|
+
class Error < StandardError
|
26
|
+
# @return [Object] error The deserialized error
|
27
|
+
attr_reader :error
|
28
|
+
|
29
|
+
##
|
30
|
+
# Initialize the client error
|
31
|
+
#
|
32
|
+
# @param [Object] error The deserialized error
|
33
|
+
#
|
34
|
+
def initialize(error)
|
35
|
+
@error = error
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# See https://github.com/grpc/grpc-go/blob/master/codes/codes.go for a detailed summary of each error type
|
42
|
+
#
|
43
|
+
module Errors
|
44
|
+
class Base < Gruf::Client::Error; end
|
45
|
+
class Error < Base; end
|
46
|
+
class Validation < Base; end
|
47
|
+
|
48
|
+
class Ok < Base; end
|
49
|
+
|
50
|
+
class InvalidArgument < Validation; end
|
51
|
+
class NotFound < Validation; end
|
52
|
+
class AlreadyExists < Validation; end
|
53
|
+
class OutOfRange < Validation; end
|
54
|
+
|
55
|
+
class Cancelled < Error; end
|
56
|
+
class DataLoss < Error; end
|
57
|
+
class DeadlineExceeded < Error; end
|
58
|
+
class FailedPrecondition < Error; end
|
59
|
+
class Internal < Error; end
|
60
|
+
class PermissionDenied < Error; end
|
61
|
+
class ResourceExhausted < Error; end
|
62
|
+
class Unauthenticated < Error; end
|
63
|
+
class Unavailable < Error; end
|
64
|
+
class Unimplemented < Error; end
|
65
|
+
class Unknown < Error; end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -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
|
data/lib/gruf/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
@@ -13,6 +15,9 @@
|
|
13
15
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
14
16
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
15
17
|
#
|
18
|
+
require_relative 'client/error'
|
19
|
+
require_relative 'client/error_factory'
|
20
|
+
|
16
21
|
module Gruf
|
17
22
|
##
|
18
23
|
# Abstracts out the calling interface for interacting with gRPC clients. Streamlines calling and provides
|
@@ -33,25 +38,6 @@ module Gruf
|
|
33
38
|
class Client < SimpleDelegator
|
34
39
|
include Gruf::Loggable
|
35
40
|
|
36
|
-
##
|
37
|
-
# Represents an error that was returned from the server's trailing metadata. Used as a custom exception object
|
38
|
-
# that is instead raised in the case of the service returning serialized error data, as opposed to the normal
|
39
|
-
# GRPC::BadStatus error
|
40
|
-
#
|
41
|
-
class Error < StandardError
|
42
|
-
# @return [Object] error The deserialized error
|
43
|
-
attr_reader :error
|
44
|
-
|
45
|
-
##
|
46
|
-
# Initialize the client error
|
47
|
-
#
|
48
|
-
# @param [Object] error The deserialized error
|
49
|
-
#
|
50
|
-
def initialize(error)
|
51
|
-
@error = error
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
41
|
# @return [Class] The base, friendly name of the service being requested
|
56
42
|
attr_reader :base_klass
|
57
43
|
# @return [Class] The class name of the gRPC service being requested
|
@@ -72,9 +58,12 @@ module Gruf
|
|
72
58
|
@base_klass = service
|
73
59
|
@service_klass = "#{base_klass}::Service".constantize
|
74
60
|
@opts = options || {}
|
75
|
-
@opts[:password] =
|
76
|
-
@opts[:hostname] =
|
77
|
-
|
61
|
+
@opts[:password] = @opts.fetch(:password, '').to_s
|
62
|
+
@opts[:hostname] = @opts.fetch(:hostname, Gruf.default_client_host)
|
63
|
+
@opts[:channel_credentials] = @opts.fetch(:channel_credentials, Gruf.default_channel_credentials)
|
64
|
+
@error_factory = Gruf::Client::ErrorFactory.new
|
65
|
+
client_options[:timeout] = parse_timeout(client_options[:timeout]) if client_options.key?(:timeout)
|
66
|
+
client = "#{service}::Stub".constantize.new(@opts[:hostname], build_ssl_credentials, **client_options)
|
78
67
|
super(client)
|
79
68
|
end
|
80
69
|
|
@@ -82,8 +71,8 @@ module Gruf
|
|
82
71
|
# Call the client's method with given params
|
83
72
|
#
|
84
73
|
# @param [String|Symbol] request_method The method that is being requested on the service
|
85
|
-
# @param [Hash] params (Optional) A hash of parameters that will be inserted into the gRPC request message that is
|
86
|
-
# for the given above call
|
74
|
+
# @param [Hash] params (Optional) A hash of parameters that will be inserted into the gRPC request message that is
|
75
|
+
# required for the given above call
|
87
76
|
# @param [Hash] metadata (Optional) A hash of metadata key/values that are transported with the client request
|
88
77
|
# @param [Hash] opts (Optional) A hash of options to send to the gRPC request_response method
|
89
78
|
# @return [Gruf::Response] The response from the server
|
@@ -96,18 +85,15 @@ module Gruf
|
|
96
85
|
md = build_metadata(metadata)
|
97
86
|
call_sig = call_signature(request_method)
|
98
87
|
|
99
|
-
|
88
|
+
unless call_sig
|
89
|
+
raise NotImplementedError, "The method #{request_method} has not been implemented in this service."
|
90
|
+
end
|
91
|
+
|
92
|
+
resp, operation = execute(call_sig, req, md, opts, &block)
|
100
93
|
|
101
|
-
|
94
|
+
raise @error_factory.from_exception(resp.result) unless resp.success?
|
102
95
|
|
103
|
-
Gruf::Response.new(resp.result, resp.time)
|
104
|
-
rescue GRPC::BadStatus => e
|
105
|
-
emk = Gruf.error_metadata_key.to_s
|
106
|
-
raise Gruf::Client::Error, error_deserializer_class.new(e.metadata[emk]).deserialize if e.respond_to?(:metadata) && e.metadata.key?(emk)
|
107
|
-
raise # passthrough
|
108
|
-
rescue StandardError => e
|
109
|
-
Gruf.logger.error e.message
|
110
|
-
raise
|
96
|
+
Gruf::Response.new(operation: operation, message: resp.result, execution_time: resp.time)
|
111
97
|
end
|
112
98
|
|
113
99
|
##
|
@@ -137,13 +123,17 @@ module Gruf
|
|
137
123
|
# @param [Object] req (Optional) The protobuf request message to send
|
138
124
|
# @param [Hash] metadata (Optional) A hash of metadata key/values that are transported with the client request
|
139
125
|
# @param [Hash] opts (Optional) A hash of options to send to the gRPC request_response method
|
126
|
+
# @return [Array<Gruf::Timer::Result, GRPC::ActiveCall::Operation>]
|
140
127
|
#
|
141
128
|
def execute(call_sig, req, metadata, opts = {}, &block)
|
142
|
-
|
129
|
+
operation = nil
|
130
|
+
result = Gruf::Timer.time do
|
143
131
|
opts[:return_op] = true
|
144
132
|
opts[:metadata] = metadata
|
145
|
-
send(call_sig, req, opts, &block)
|
133
|
+
operation = send(call_sig, req, opts, &block)
|
134
|
+
operation.execute
|
146
135
|
end
|
136
|
+
[result, operation]
|
147
137
|
end
|
148
138
|
|
149
139
|
##
|
@@ -165,7 +155,7 @@ module Gruf
|
|
165
155
|
#
|
166
156
|
def request_object(request_method, params = {})
|
167
157
|
desc = rpc_desc(request_method)
|
168
|
-
desc
|
158
|
+
desc&.input ? desc.input.new(params) : nil
|
169
159
|
end
|
170
160
|
|
171
161
|
##
|
@@ -175,7 +165,7 @@ module Gruf
|
|
175
165
|
#
|
176
166
|
def call_signature(request_method)
|
177
167
|
desc = rpc_desc(request_method)
|
178
|
-
desc
|
168
|
+
desc&.name ? desc.name.to_s.underscore.to_sym : nil
|
179
169
|
end
|
180
170
|
|
181
171
|
##
|
@@ -201,6 +191,8 @@ module Gruf
|
|
201
191
|
#
|
202
192
|
# :nocov:
|
203
193
|
def build_ssl_credentials
|
194
|
+
return opts[:channel_credentials] if opts[:channel_credentials]
|
195
|
+
|
204
196
|
cert = nil
|
205
197
|
if opts[:ssl_certificate_file]
|
206
198
|
cert = File.read(opts[:ssl_certificate_file]).to_s.strip
|
@@ -224,5 +216,27 @@ module Gruf
|
|
224
216
|
Gruf::Serializers::Errors::Json
|
225
217
|
end
|
226
218
|
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Handle various timeout values and prevent improper value setting
|
222
|
+
#
|
223
|
+
# @see GRPC::Core::TimeConsts#from_relative_time
|
224
|
+
# @param [mixed] timeout
|
225
|
+
# @return [Float]
|
226
|
+
# @return [GRPC::Core::TimeSpec]
|
227
|
+
#
|
228
|
+
def parse_timeout(timeout)
|
229
|
+
if timeout.nil?
|
230
|
+
GRPC::Core::TimeConsts::ZERO
|
231
|
+
elsif timeout.is_a?(GRPC::Core::TimeSpec)
|
232
|
+
timeout
|
233
|
+
elsif timeout.is_a?(Numeric)
|
234
|
+
timeout
|
235
|
+
elsif timeout.respond_to?(:to_f)
|
236
|
+
timeout.to_f
|
237
|
+
else
|
238
|
+
raise ArgumentError, 'timeout is not a valid value: does not respond to to_f'
|
239
|
+
end
|
240
|
+
end
|
227
241
|
end
|
228
242
|
end
|
data/lib/gruf/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
@@ -23,6 +25,8 @@ module Gruf
|
|
23
25
|
server_binding_url: '0.0.0.0:9001',
|
24
26
|
server_options: {},
|
25
27
|
interceptors: nil,
|
28
|
+
hooks: nil,
|
29
|
+
default_channel_credentials: nil,
|
26
30
|
default_client_host: '',
|
27
31
|
use_ssl: false,
|
28
32
|
ssl_crt_file: '',
|
@@ -36,17 +40,32 @@ module Gruf
|
|
36
40
|
append_server_errors_to_trailing_metadata: true,
|
37
41
|
use_default_interceptors: true,
|
38
42
|
backtrace_on_error: false,
|
43
|
+
backtrace_limit: 10,
|
39
44
|
use_exception_message: true,
|
40
|
-
internal_error_message: 'Internal Server Error'
|
45
|
+
internal_error_message: 'Internal Server Error',
|
46
|
+
event_listener_proc: nil,
|
47
|
+
synchronized_client_internal_cache_expiry: 60,
|
48
|
+
rpc_server_options: {
|
49
|
+
pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
|
50
|
+
max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
|
51
|
+
poll_period: GRPC::RpcServer::DEFAULT_POLL_PERIOD,
|
52
|
+
pool_keep_alive: GRPC::Pool::DEFAULT_KEEP_ALIVE,
|
53
|
+
connect_md_proc: nil,
|
54
|
+
server_args: {}
|
55
|
+
}.freeze
|
41
56
|
}.freeze
|
42
57
|
|
43
|
-
attr_accessor
|
58
|
+
attr_accessor(* VALID_CONFIG_KEYS.keys)
|
44
59
|
|
45
60
|
##
|
46
61
|
# Whenever this is extended into a class, setup the defaults
|
47
62
|
#
|
48
63
|
def self.extended(base)
|
49
|
-
|
64
|
+
if defined?(Rails)
|
65
|
+
Gruf::Integrations::Rails::Railtie.config.before_initialize { base.reset }
|
66
|
+
else
|
67
|
+
base.reset
|
68
|
+
end
|
50
69
|
end
|
51
70
|
|
52
71
|
##
|
@@ -79,15 +98,16 @@ module Gruf
|
|
79
98
|
#
|
80
99
|
def reset
|
81
100
|
VALID_CONFIG_KEYS.each do |k, v|
|
82
|
-
send(
|
101
|
+
send("#{k}=", v)
|
83
102
|
end
|
84
103
|
self.interceptors = Gruf::Interceptors::Registry.new
|
104
|
+
self.hooks = Gruf::Hooks::Registry.new
|
85
105
|
self.root_path = Rails.root.to_s.chomp('/') if defined?(Rails)
|
86
106
|
if defined?(Rails) && Rails.logger
|
87
107
|
self.logger = Rails.logger
|
88
108
|
else
|
89
109
|
require 'logger'
|
90
|
-
self.logger = ::Logger.new(
|
110
|
+
self.logger = ::Logger.new($stdout)
|
91
111
|
end
|
92
112
|
self.grpc_logger = logger if grpc_logger.nil?
|
93
113
|
self.ssl_crt_file = "#{root_path}config/ssl/#{environment}.crt"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
@@ -29,14 +31,19 @@ module Gruf
|
|
29
31
|
# @var [Gruf::Error] error
|
30
32
|
attr_reader :error
|
31
33
|
|
34
|
+
class << self
|
35
|
+
# @var [GRPC::GenericService] bound_service
|
36
|
+
attr_reader :bound_service
|
37
|
+
end
|
38
|
+
|
32
39
|
##
|
33
40
|
# Initialize the controller within the given request context
|
34
41
|
#
|
35
|
-
# @param [Symbol] method_key
|
36
|
-
# @param [GRPC::GenericService] service
|
37
|
-
# @param [GRPC::RpcDesc] rpc_desc
|
38
|
-
# @param [GRPC::ActiveCall] active_call
|
39
|
-
# @param [Google::Protobuf::MessageExts] message
|
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
|
40
47
|
#
|
41
48
|
def initialize(method_key:, service:, rpc_desc:, active_call:, message:)
|
42
49
|
@request = Request.new(
|
@@ -53,25 +60,41 @@ module Gruf
|
|
53
60
|
##
|
54
61
|
# Bind the controller to the given service and add it to the service registry
|
55
62
|
#
|
56
|
-
# @param [GRPC::GenericService] service
|
63
|
+
# @param [GRPC::GenericService] service The name of the service to bind this controller to
|
57
64
|
#
|
58
65
|
def self.bind(service)
|
59
|
-
|
60
|
-
|
66
|
+
service_class = service.name.constantize
|
67
|
+
Gruf.services << service_class
|
68
|
+
# rubocop:disable ThreadSafety/InstanceVariableInClassMethod
|
69
|
+
@bound_service = service_class
|
70
|
+
# rubocop:enable ThreadSafety/InstanceVariableInClassMethod
|
71
|
+
ServiceBinder.new(service_class).bind!(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Call a method on this controller.
|
76
|
+
# Override this in a subclass to modify the behavior around processing a method
|
77
|
+
#
|
78
|
+
# @param [Symbol] method_key The name of the gRPC service method being called as a Symbol
|
79
|
+
# @param [block] &block The passed block for executing the method
|
80
|
+
#
|
81
|
+
def process_action(method_key, &block)
|
82
|
+
send(method_key, &block)
|
61
83
|
end
|
62
84
|
|
63
85
|
##
|
64
86
|
# Call a method on this controller
|
65
87
|
#
|
66
|
-
# @param [Symbol] method_key
|
88
|
+
# @param [Symbol] method_key The name of the gRPC service method being called as a Symbol
|
89
|
+
# @param [block] &block The passed block for executing the method
|
67
90
|
#
|
68
91
|
def call(method_key, &block)
|
69
92
|
Interceptors::Context.new(@interceptors).intercept! do
|
70
|
-
|
93
|
+
process_action(method_key, &block)
|
71
94
|
end
|
72
95
|
rescue GRPC::BadStatus
|
73
|
-
raise # passthrough
|
74
|
-
rescue StandardError => e
|
96
|
+
raise # passthrough, to be caught by Gruf::Interceptors::Timer
|
97
|
+
rescue GRPC::Core::CallError, StandardError => e # CallError is not a StandardError
|
75
98
|
set_debug_info(e.message, e.backtrace) if Gruf.backtrace_on_error
|
76
99
|
error_message = Gruf.use_exception_message ? e.message : Gruf.internal_error_message
|
77
100
|
fail!(:internal, :unknown, error_message)
|