gruf 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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