gruf 1.2.7 → 2.0.0

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +98 -119
  4. data/bin/gruf +9 -3
  5. data/lib/gruf.rb +4 -4
  6. data/lib/gruf/configuration.rb +11 -20
  7. data/lib/gruf/controllers/base.rb +82 -0
  8. data/lib/gruf/controllers/request.rb +96 -0
  9. data/lib/gruf/controllers/service_binder.rb +86 -0
  10. data/lib/gruf/error.rb +9 -0
  11. data/lib/gruf/errors/helpers.rb +40 -0
  12. data/lib/gruf/{hooks → interceptors}/active_record/connection_reset.rb +4 -10
  13. data/lib/gruf/interceptors/authentication/basic.rb +80 -0
  14. data/lib/gruf/interceptors/base.rb +51 -0
  15. data/lib/gruf/{instrumentation/output_metadata_timer.rb → interceptors/context.rb} +25 -15
  16. data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +59 -0
  17. data/lib/gruf/{instrumentation → interceptors/instrumentation}/request_logging/formatters/base.rb +15 -13
  18. data/lib/gruf/{instrumentation → interceptors/instrumentation}/request_logging/formatters/logstash.rb +15 -13
  19. data/lib/gruf/{instrumentation → interceptors/instrumentation}/request_logging/formatters/plain.rb +21 -19
  20. data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +191 -0
  21. data/lib/gruf/interceptors/instrumentation/statsd.rb +80 -0
  22. data/lib/gruf/interceptors/registry.rb +131 -0
  23. data/lib/gruf/{authentication/none.rb → interceptors/server_interceptor.rb} +8 -7
  24. data/lib/gruf/interceptors/timer.rb +79 -0
  25. data/lib/gruf/response.rb +1 -2
  26. data/lib/gruf/server.rb +40 -25
  27. data/lib/gruf/version.rb +1 -1
  28. metadata +19 -20
  29. data/lib/gruf/authentication.rb +0 -65
  30. data/lib/gruf/authentication/base.rb +0 -65
  31. data/lib/gruf/authentication/basic.rb +0 -74
  32. data/lib/gruf/authentication/strategies.rb +0 -107
  33. data/lib/gruf/hooks/base.rb +0 -66
  34. data/lib/gruf/hooks/registry.rb +0 -110
  35. data/lib/gruf/instrumentation/base.rb +0 -114
  36. data/lib/gruf/instrumentation/registry.rb +0 -104
  37. data/lib/gruf/instrumentation/request_context.rb +0 -82
  38. data/lib/gruf/instrumentation/request_logging/hook.rb +0 -185
  39. data/lib/gruf/instrumentation/statsd.rb +0 -80
  40. data/lib/gruf/service.rb +0 -333
@@ -0,0 +1,131 @@
1
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
7
+ #
8
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9
+ # Software.
10
+ #
11
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+ #
16
+ module Gruf
17
+ module Interceptors
18
+ ##
19
+ # Handles registration of interceptors
20
+ #
21
+ class Registry
22
+ class InterceptorNotFoundError < StandardError; end
23
+
24
+ def initialize
25
+ @registry ||= []
26
+ end
27
+
28
+ ##
29
+ # Add an interceptor to the registry
30
+ #
31
+ # @param [Gruf::Interceptors::Base] interceptor_class The class of the interceptor to add
32
+ # @param [Hash] options A hash of options to pass into the interceptor during initialization
33
+ #
34
+ def use(interceptor_class, options = {})
35
+ interceptors_mutex do
36
+ @registry << {
37
+ klass: interceptor_class,
38
+ options: options
39
+ }
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Insert an interceptor before another specified interceptor
45
+ #
46
+ # @param [Gruf::Interceptors::Base] before_class The interceptor to insert before
47
+ # @param [Gruf::Interceptors::Base] interceptor_class The class of the interceptor to add
48
+ # @param [Hash] options A hash of options to pass into the interceptor during initialization
49
+ #
50
+ def insert_before(before_class, interceptor_class, options = {})
51
+ interceptors_mutex do
52
+ pos = @registry.find_index { |opts| opts.fetch(:klass, '') == before_class }
53
+ raise InterceptorNotFoundError if pos.nil?
54
+ @registry.insert(
55
+ pos,
56
+ klass: interceptor_class,
57
+ options: options
58
+ )
59
+ end
60
+ end
61
+
62
+ ##
63
+ # Insert an interceptor after another specified interceptor
64
+ #
65
+ # @param [Gruf::Interceptors::Base] after_class The interceptor to insert after
66
+ # @param [Gruf::Interceptors::Base] interceptor_class The class of the interceptor to add
67
+ # @param [Hash] options A hash of options to pass into the interceptor during initialization
68
+ #
69
+ def insert_after(after_class, interceptor_class, options = {})
70
+ interceptors_mutex do
71
+ pos = @registry.find_index { |opts| opts.fetch(:klass, '') == after_class }
72
+ raise InterceptorNotFoundError if pos.nil?
73
+ @registry.insert(
74
+ (pos + 1),
75
+ klass: interceptor_class,
76
+ options: options
77
+ )
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Load and return all interceptors for the given request
83
+ #
84
+ # @param [Gruf::Controllers::Request] request
85
+ # @param [Gruf::Error] error
86
+ # @return [Array<Gruf::Interceptors::Base>]
87
+ #
88
+ def prepare(request, error)
89
+ is = []
90
+ interceptors_mutex do
91
+ @registry.each do |o|
92
+ is << o[:klass].new(request, error, o[:options])
93
+ end
94
+ end
95
+ is
96
+ end
97
+
98
+ ##
99
+ # Clear the registry
100
+ #
101
+ def clear
102
+ interceptors_mutex do
103
+ @registry = []
104
+ end
105
+ end
106
+
107
+ ##
108
+ # @return [Integer] The number of interceptors currently loaded
109
+ #
110
+ def count
111
+ interceptors_mutex do
112
+ @registry ||= []
113
+ @registry.count
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ ##
120
+ # Handle mutations to the interceptor registry in a thread-safe manner
121
+ #
122
+ def interceptors_mutex(&block)
123
+ @interceptors_mutex ||= begin
124
+ require 'monitor'
125
+ Monitor.new
126
+ end
127
+ @interceptors_mutex.synchronize(&block)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -15,17 +15,18 @@
15
15
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
16
  #
17
17
  module Gruf
18
- module Authentication
18
+ module Interceptors
19
19
  ##
20
- # No authentication strategy
20
+ # Intercepts server requests
21
21
  #
22
- class None < Base
22
+ class ServerInterceptor < Base
23
+ include Gruf::Errors::Helpers
24
+
23
25
  ##
24
- # @param [GRPC::ActiveCall] _call The gRPC active call for the given operation
25
- # @return [TrueClass] Always true for this passthrough strategy.
26
+ # Call the interceptor
26
27
  #
27
- def valid?(_call)
28
- true
28
+ def call
29
+ raise NotImplementedError
29
30
  end
30
31
  end
31
32
  end
@@ -0,0 +1,79 @@
1
+ # coding: utf-8
2
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
7
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
8
+ #
9
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10
+ # Software.
11
+ #
12
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+ #
17
+ module Gruf
18
+ module Interceptors
19
+ ##
20
+ # Utility class that can be used by interceptors to time requests
21
+ #
22
+ class Timer
23
+ ##
24
+ # Represents a timed result for an interceptor
25
+ #
26
+ class Result
27
+ attr_reader :message
28
+ attr_reader :elapsed
29
+
30
+ def initialize(message, elapsed, successful)
31
+ @message = message
32
+ @elapsed = elapsed.to_f
33
+ @successful = successful ? true : false
34
+ end
35
+
36
+ ##
37
+ # @return [Boolean] True if this was a successful request
38
+ #
39
+ def successful?
40
+ @successful
41
+ end
42
+
43
+ ##
44
+ # @return [String] The name of the message class
45
+ #
46
+ def message_class_name
47
+ @message.class.name
48
+ end
49
+
50
+ ##
51
+ # Return the execution time rounded to a specified precision
52
+ #
53
+ # @param [Integer] precision The amount of decimal places to round to
54
+ # @return [Float] The execution time rounded to the appropriate decimal point
55
+ #
56
+ def elapsed_rounded(precision: 2)
57
+ @elapsed.to_f.round(precision)
58
+ end
59
+ end
60
+
61
+ ##
62
+ # Time a given code block and return a Timer::Result object
63
+ #
64
+ # @return [Gruf::Interceptors::Timer::Result]
65
+ #
66
+ def self.time
67
+ start_time = Time.now
68
+ message = yield
69
+ end_time = Time.now
70
+ elapsed = (end_time - start_time) * 1000.0
71
+ Result.new(message, elapsed, true)
72
+ rescue GRPC::BadStatus => e
73
+ end_time = Time.now
74
+ elapsed = (end_time - start_time) * 1000.0
75
+ Result.new(e, elapsed, false)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -63,8 +63,7 @@ module Gruf
63
63
  # @return [Float] The execution time of the response
64
64
  #
65
65
  def internal_execution_time
66
- key = Gruf.instrumentation_options.fetch(:output_metadata_timer, {}).fetch(:metadata_key, 'timer')
67
- trailing_metadata[key].to_f
66
+ trailing_metadata['timer'].to_f
68
67
  end
69
68
  end
70
69
  end
@@ -22,21 +22,24 @@ module Gruf
22
22
  class Server
23
23
  include Gruf::Loggable
24
24
 
25
- # @return [Array<Class>] The services this server is handling
26
- attr_accessor :services
27
25
  # @return [GRPC::RpcServer] The underlying GRPC server instance
28
26
  attr_reader :server
29
27
  # @return [Integer] The port the server is bound to
30
28
  attr_reader :port
29
+ # @return [Hash] Hash of options passed into the server
30
+ attr_reader :options
31
31
 
32
32
  ##
33
33
  # Initialize the server and load and setup the services
34
34
  #
35
- # @param [Array<Class>] services The services that this server should handle
35
+ # @param [Hash] options
36
36
  #
37
- def initialize(services: [])
37
+ def initialize(options = {})
38
+ @options = options || {}
39
+ @interceptors = options.fetch(:interceptor_registry, Gruf.interceptors)
40
+ @interceptors = Gruf::Interceptors::Registry.new unless @interceptors.is_a?(Gruf::Interceptors::Registry)
41
+ @services = []
38
42
  setup!
39
- load_services(services)
40
43
  end
41
44
 
42
45
  ##
@@ -44,11 +47,9 @@ module Gruf
44
47
  #
45
48
  def server
46
49
  unless @server
47
- @server = GRPC::RpcServer.new(Gruf.server_options)
48
- @port = @server.add_http2_port(Gruf.server_binding_url, ssl_credentials)
49
- services.each do |s|
50
- @server.handle(s)
51
- end
50
+ @server = GRPC::RpcServer.new(options)
51
+ @port = @server.add_http2_port(options.fetch(:hostname, Gruf.server_binding_url), ssl_credentials)
52
+ @services.each { |s| @server.handle(s) }
52
53
  end
53
54
  @server
54
55
  end
@@ -64,34 +65,48 @@ module Gruf
64
65
  end
65
66
  # :nocov:
66
67
 
67
- private
68
+ ##
69
+ # @param [Class] klass
70
+ #
71
+ def add_service(klass)
72
+ @services << klass unless @services.include?(klass)
73
+ end
68
74
 
69
75
  ##
70
- # Return all loaded gRPC services
76
+ # Add an interceptor to the server
71
77
  #
72
- # @param [Array<Class>] svcs An array of service classes that will be handled by this server
73
- # @return [Array<Class>] The given services that were added
78
+ # @param [Gruf::Interceptors::Base]
79
+ # @param [Hash]
74
80
  #
75
- def load_services(svcs)
76
- unless @services
77
- @services = Gruf.services.concat(svcs)
78
- @services.uniq!
79
- end
80
- @services
81
+ def add_interceptor(klass, opts = {})
82
+ @interceptors.use(klass, opts)
81
83
  end
82
84
 
85
+ private
86
+
83
87
  ##
84
88
  # Auto-load all gRPC handlers
85
89
  #
86
90
  # :nocov:
87
91
  def setup!
88
- Dir["#{Gruf.servers_path}/**/*.rb"].each do |f|
92
+ return unless File.directory?(controllers_path)
93
+ path = File.realpath(controllers_path)
94
+ $LOAD_PATH.unshift(path)
95
+ Dir["#{path}/**/*.rb"].each do |f|
96
+ next if f.include?('_pb') # exclude if people include proto generated files in app/rpc
89
97
  logger.info "- Loading gRPC service file: #{f}"
90
- require f
98
+ load File.realpath(f)
91
99
  end
92
100
  end
93
101
  # :nocov:
94
102
 
103
+ ##
104
+ # @param [String]
105
+ #
106
+ def controllers_path
107
+ options.fetch(:controllers_path, Gruf.controllers_path)
108
+ end
109
+
95
110
  ##
96
111
  # Load the SSL/TLS credentials for this server
97
112
  #
@@ -99,9 +114,9 @@ module Gruf
99
114
  #
100
115
  # :nocov:
101
116
  def ssl_credentials
102
- if Gruf.use_ssl
103
- private_key = File.read Gruf.ssl_key_file
104
- cert_chain = File.read Gruf.ssl_crt_file
117
+ if options.fetch(:use_ssl, Gruf.use_ssl)
118
+ private_key = File.read(options.fetch(:ssl_key_file, Gruf.ssl_key_file))
119
+ cert_chain = File.read(options.fetch(:ssl_crt_file, Gruf.ssl_crt_file))
105
120
  certs = [nil, [{ private_key: private_key, cert_chain: cert_chain }], false]
106
121
  GRPC::Core::ServerCredentials.new(*certs)
107
122
  else
@@ -15,5 +15,5 @@
15
15
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
16
  #
17
17
  module Gruf
18
- VERSION = '1.2.7'.freeze
18
+ VERSION = '2.0.0'.freeze
19
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gruf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.7
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shaun McCormick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-26 00:00:00.000000000 Z
11
+ date: 2017-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,35 +108,34 @@ files:
108
108
  - bin/gruf
109
109
  - gruf.gemspec
110
110
  - lib/gruf.rb
111
- - lib/gruf/authentication.rb
112
- - lib/gruf/authentication/base.rb
113
- - lib/gruf/authentication/basic.rb
114
- - lib/gruf/authentication/none.rb
115
- - lib/gruf/authentication/strategies.rb
116
111
  - lib/gruf/client.rb
117
112
  - lib/gruf/configuration.rb
113
+ - lib/gruf/controllers/base.rb
114
+ - lib/gruf/controllers/request.rb
115
+ - lib/gruf/controllers/service_binder.rb
118
116
  - lib/gruf/error.rb
119
117
  - lib/gruf/errors/debug_info.rb
120
118
  - lib/gruf/errors/field.rb
121
- - lib/gruf/hooks/active_record/connection_reset.rb
122
- - lib/gruf/hooks/base.rb
123
- - lib/gruf/hooks/registry.rb
124
- - lib/gruf/instrumentation/base.rb
125
- - lib/gruf/instrumentation/output_metadata_timer.rb
126
- - lib/gruf/instrumentation/registry.rb
127
- - lib/gruf/instrumentation/request_context.rb
128
- - lib/gruf/instrumentation/request_logging/formatters/base.rb
129
- - lib/gruf/instrumentation/request_logging/formatters/logstash.rb
130
- - lib/gruf/instrumentation/request_logging/formatters/plain.rb
131
- - lib/gruf/instrumentation/request_logging/hook.rb
132
- - lib/gruf/instrumentation/statsd.rb
119
+ - lib/gruf/errors/helpers.rb
120
+ - lib/gruf/interceptors/active_record/connection_reset.rb
121
+ - lib/gruf/interceptors/authentication/basic.rb
122
+ - lib/gruf/interceptors/base.rb
123
+ - lib/gruf/interceptors/context.rb
124
+ - lib/gruf/interceptors/instrumentation/output_metadata_timer.rb
125
+ - lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb
126
+ - lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb
127
+ - lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb
128
+ - lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb
129
+ - lib/gruf/interceptors/instrumentation/statsd.rb
130
+ - lib/gruf/interceptors/registry.rb
131
+ - lib/gruf/interceptors/server_interceptor.rb
132
+ - lib/gruf/interceptors/timer.rb
133
133
  - lib/gruf/loggable.rb
134
134
  - lib/gruf/logging.rb
135
135
  - lib/gruf/response.rb
136
136
  - lib/gruf/serializers/errors/base.rb
137
137
  - lib/gruf/serializers/errors/json.rb
138
138
  - lib/gruf/server.rb
139
- - lib/gruf/service.rb
140
139
  - lib/gruf/timer.rb
141
140
  - lib/gruf/version.rb
142
141
  homepage: https://github.com/bigcommerce/gruf
@@ -1,65 +0,0 @@
1
- # coding: utf-8
2
- # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5
- # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6
- # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
7
- # persons to whom the Software is furnished to do so, subject to the following conditions:
8
- #
9
- # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10
- # Software.
11
- #
12
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13
- # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14
- # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15
- # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
- #
17
- require_relative 'authentication/strategies'
18
- require_relative 'authentication/base'
19
- require_relative 'authentication/basic'
20
- require_relative 'authentication/none'
21
-
22
- module Gruf
23
- ##
24
- # Handles authentication for gruf services
25
- #
26
- module Authentication
27
- ##
28
- # Custom error class for handling unauthorized requests
29
- #
30
- class UnauthorizedError < StandardError; end
31
-
32
- ##
33
- # Verify a given gruf request
34
- #
35
- # @param [GRPC::ActiveCall] call The gRPC active call with marshalled data that is being executed
36
- # @param [Symbol] strategy The authentication strategy to use
37
- # @return [Boolean]
38
- #
39
- def self.verify(call)
40
- credentials = call && call.respond_to?(:metadata) ? call.metadata[Gruf.authorization_metadata_key] : nil
41
-
42
- if Gruf::Authentication::Strategies.any?
43
- verified = false
44
- Gruf::Authentication::Strategies.each do |_label, klass|
45
- begin
46
- # if a strategy passes, we've successfully authenticated and can proceed
47
- if klass.verify(call, credentials)
48
- verified = true
49
- break
50
- end
51
- rescue => e
52
- Gruf.logger.error "#{e.message} - #{e.backtrace[0..4].join("\n")}"
53
- # NOOP, we don't want to fail other strategies because of a bad neighbor
54
- # or if a strategy throws an exception because of a failed auth
55
- # we should just proceed to the next strategy
56
- end
57
- end
58
- verified
59
- else
60
- # we're not using any strategies, so no auth
61
- true
62
- end
63
- end
64
- end
65
- end