gruf 1.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.
@@ -0,0 +1,49 @@
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 Serializers
19
+ module Errors
20
+ ##
21
+ # Base class for serialization of errors for transport across the grpc protocol
22
+ #
23
+ class Base
24
+ attr_reader :error
25
+
26
+ ##
27
+ # @param [Gruf::Error|String] err
28
+ #
29
+ def initialize(err)
30
+ @error = err
31
+ end
32
+
33
+ ##
34
+ # @return [String]
35
+ #
36
+ def serialize
37
+ raise NotImplementedError
38
+ end
39
+
40
+ ##
41
+ # @return [Object|Hash]
42
+ #
43
+ def deserialize
44
+ raise NotImplementedError
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
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 'json'
18
+
19
+ module Gruf
20
+ module Serializers
21
+ module Errors
22
+ class Json < Base
23
+ ##
24
+ # @return [String]
25
+ #
26
+ def serialize
27
+ @error.to_h.to_json
28
+ end
29
+
30
+ ##
31
+ # @return [Object|Hash]
32
+ #
33
+ def deserialize
34
+ JSON.parse(@error)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,92 @@
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
+ ##
19
+ # Represents a gRPC server. Automatically loads and augments gRPC handlers and services
20
+ # based on configuration values.
21
+ #
22
+ class Server
23
+ include Gruf::Loggable
24
+
25
+ attr_accessor :services
26
+
27
+ def initialize(services: [])
28
+ setup!
29
+ load_services(services)
30
+ end
31
+
32
+ ##
33
+ # Start the gRPC server
34
+ #
35
+ # :nocov:
36
+ def start!
37
+ logger.info { 'Booting gRPC Server...' }
38
+ server = GRPC::RpcServer.new
39
+ server.add_http2_port Gruf.server_binding_url, ssl_credentials
40
+ services.each do |s|
41
+ server.handle(s)
42
+ end
43
+ server.run_till_terminated
44
+ logger.info { 'Shutting down gRPC server...' }
45
+ end
46
+ # :nocov:
47
+
48
+ private
49
+
50
+ ##
51
+ # Return all loaded gRPC services
52
+ #
53
+ # @param [Array<Class>] svcs
54
+ # @return [Array<Class>]
55
+ #
56
+ def load_services(svcs)
57
+ unless @services
58
+ @services = Gruf.services.concat(svcs)
59
+ @services.uniq!
60
+ end
61
+ @services
62
+ end
63
+
64
+ ##
65
+ # Auto-load all gRPC handlers
66
+ #
67
+ # :nocov:
68
+ def setup!
69
+ Dir["#{Gruf.servers_path}/**/*.rb"].each do |f|
70
+ logger.info "- Loading gRPC service file: #{f}"
71
+ require f
72
+ end
73
+ end
74
+ # :nocov:
75
+
76
+ ##
77
+ # @return [GRPC::Core::ServerCredentials|Symbol]
78
+ #
79
+ # :nocov:
80
+ def ssl_credentials
81
+ if Gruf.use_ssl
82
+ private_key = File.read Gruf.ssl_key_file
83
+ cert_chain = File.read Gruf.ssl_crt_file
84
+ certs = [nil, [{ private_key: private_key, cert_chain: cert_chain }], false]
85
+ GRPC::Core::ServerCredentials.new(*certs)
86
+ else
87
+ :this_port_is_insecure
88
+ end
89
+ end
90
+ # :nocov:
91
+ end
92
+ end
@@ -0,0 +1,271 @@
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
+ ##
19
+ # Module for gRPC endpoints
20
+ #
21
+ module Service
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ include Gruf::Loggable
26
+
27
+ ##
28
+ # Hook into method_added to add pre/post interceptors for endpoints
29
+ #
30
+ # @param [String] method_name
31
+ #
32
+ def self.method_added(method_name)
33
+ return if @__last_methods_added && @__last_methods_added.include?(method_name)
34
+ return unless rpc_handler_names.include?(method_name)
35
+
36
+ with = :"#{method_name}_with_intercept"
37
+ without = :"#{method_name}_without_intercept"
38
+ @__last_methods_added = [method_name, with, without]
39
+ define_method with do |*args, &block|
40
+ call_chain(without, args[0], args[1], &block)
41
+ end
42
+ alias_method without, method_name
43
+ alias_method method_name, with
44
+ @__last_methods_added = nil
45
+ end
46
+
47
+ ##
48
+ # Properly find all RPC handler methods
49
+ #
50
+ def self.rpc_handler_names
51
+ rpc_descs.keys.map { |n| n.to_s.underscore.to_sym }.uniq
52
+ end
53
+
54
+ ##
55
+ # Mount the service into the server automatically
56
+ #
57
+ def self.mount
58
+ Gruf.services << name.constantize
59
+ end
60
+
61
+ mount
62
+ end
63
+
64
+ ##
65
+ # Happens before a call.
66
+ #
67
+ # @param [Symbol] call_signature The method being called
68
+ # @param [Object] req The request object
69
+ # @param [GRPC::ActiveCall] call The gRPC active call object
70
+ #
71
+ def before_call(call_signature, req, call)
72
+ authenticate(call_signature, req, call)
73
+ Gruf::Hooks::Registry.each do |_name, h|
74
+ h.new(self, Gruf.hook_options).before(call_signature, req, call) if h.instance_methods.include?(:before)
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Happens around a call.
80
+ #
81
+ # @param [Symbol] call_signature The gRPC method being called
82
+ # @param [Object] req The request object
83
+ # @param [GRPC::ActiveCall] call The gRPC active call object
84
+ #
85
+ def around_call(call_signature, req, call, &block)
86
+ around_hooks = []
87
+ Gruf::Hooks::Registry.each do |_name, h|
88
+ around_hooks << h.new(self, Gruf.hook_options) if h.instance_methods.include?(:around)
89
+ end
90
+ if around_hooks.any?
91
+ run_around_hook(around_hooks, call_signature, req, call, &block)
92
+ else
93
+ yield
94
+ end
95
+ end
96
+
97
+ ##
98
+ # Run all around hooks recursively, starting with the last loaded
99
+ #
100
+ # @param [Array<Gruf::Hooks::Base>] hooks The current stack of hooks
101
+ # @param [Symbol] call_signature The gRPC method being called
102
+ # @param [Object] req The request object
103
+ # @param [GRPC::ActiveCall] call The gRPC active call object
104
+ #
105
+ def run_around_hook(hooks, call_signature, req, call, &_)
106
+ h = hooks.pop
107
+ h.around(call_signature, req, call) do
108
+ if hooks.any?
109
+ run_around_hook(hooks, call_signature, req, call) { yield }
110
+ else
111
+ yield
112
+ end
113
+ end
114
+ end
115
+
116
+ ##
117
+ # Happens around the entire call chain - before, around, the call itself, and after hooks.
118
+ #
119
+ # @param [Symbol] call_signature The gRPC method being called
120
+ # @param [Object] req The request object
121
+ # @param [GRPC::ActiveCall] call The gRPC active call object
122
+ #
123
+ def outer_around_call(call_signature, req, call, &block)
124
+ outer_around_hooks = []
125
+ Gruf::Hooks::Registry.each do |_name, h|
126
+ outer_around_hooks << h.new(self, Gruf.hook_options) if h.instance_methods.include?(:outer_around)
127
+ end
128
+ if outer_around_hooks.any?
129
+ run_outer_around_hook(outer_around_hooks, call_signature, req, call, &block)
130
+ else
131
+ yield
132
+ end
133
+ end
134
+
135
+ ##
136
+ # Run all outer around hooks recursively, starting with the last loaded
137
+ #
138
+ # @param [Array<Gruf::Hooks::Base>] hooks The current stack of hooks
139
+ # @param [Symbol] call_signature The gRPC method being called
140
+ # @param [Object] req The request object
141
+ # @param [GRPC::ActiveCall] call The gRPC active call object
142
+ #
143
+ def run_outer_around_hook(hooks, call_signature, req, call, &_)
144
+ h = hooks.pop
145
+ h.outer_around(call_signature, req, call) do
146
+ if hooks.any?
147
+ run_outer_around_hook(hooks, call_signature, req, call) { yield }
148
+ else
149
+ yield
150
+ end
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Happens after a call
156
+ #
157
+ # @param [Boolean] success Whether or not the result was successful
158
+ # @param [Object] response The response object returned from the gRPC call
159
+ # @param [Symbol] call_signature The method being called
160
+ # @param [Object] req The request object
161
+ # @param [GRPC::ActiveCall] call The gRPC active call object
162
+ # @return [Object] If extending this method or using the after_call_hook, you must return the response object
163
+ #
164
+ def after_call(success, response, call_signature, req, call)
165
+ Gruf::Hooks::Registry.each do |_name, h|
166
+ h.new(self, Gruf.hook_options).after(success, response, call_signature, req, call) if h.instance_methods.include?(:after)
167
+ end
168
+ end
169
+
170
+ ##
171
+ # Authenticate the endpoint caller.
172
+ #
173
+ # @param [Symbol] _method The method being called
174
+ # @param [Object] req The request object
175
+ # @param [GRPC::ActiveCall] call The gRPC active call object
176
+ #
177
+ def authenticate(_method, req, call)
178
+ fail!(req, call, :unauthenticated) unless Authentication.verify(call)
179
+ end
180
+
181
+ ##
182
+ # Will issue a GRPC BadStatus exception, with a code based on the code passed.
183
+ #
184
+ # @param [Object] _req The request object being sent
185
+ # @param [GRPC::ActiveCall] call The gRPC active call
186
+ # @param [Symbol] error_code The network error code that maps to gRPC status codes
187
+ # @param [Symbol] app_code The application-specific code for the error
188
+ # @param [String] message (Optional) A detail message about the error
189
+ # @param [Hash] metadata (Optional) Any metadata to inject into the trailing metadata for the response
190
+ # @return [RPC::Error]
191
+ #
192
+ def fail!(_req, call, error_code, app_code = nil, message = '', metadata = {})
193
+ error.code = error_code.to_sym
194
+ error.app_code = app_code ? app_code.to_sym : error.code
195
+ error.message = message.to_s
196
+ error.metadata = metadata
197
+ error.fail!(call)
198
+ end
199
+
200
+ private
201
+
202
+ ##
203
+ # Encapsulate the call chain to provide before/around/after hooks
204
+ #
205
+ # @param [Symbol] original_call_sig The original call signature for the service
206
+ # @param [Object] req The request object
207
+ # @param [GRPC::ActiveCall] call The ActiveCall object being executed
208
+ # @return [Object] The response object
209
+ #
210
+ def call_chain(original_call_sig, req, call, &block)
211
+ outer_around_call(original_call_sig, req, call) do
212
+ before_call(original_call_sig, req, call)
213
+ timed = Timer.time do
214
+ around_call(original_call_sig, req, call) do
215
+ send(original_call_sig, req, call, &block) # send the actual request to gRPC
216
+ end
217
+ end
218
+ after_call(timed.success?, timed.result, original_call_sig, req, call)
219
+
220
+ Gruf::Instrumentation::Registry.each do |_name, h|
221
+ h.new(self, req, timed.result, timed.time, original_call_sig, call, Gruf.instrumentation_options).call
222
+ end
223
+
224
+ raise timed.result unless timed.success?
225
+
226
+ timed.result
227
+ end
228
+ rescue => e
229
+ raise e if e.is_a?(GRPC::BadStatus)
230
+ set_debug_info(e.message, e.backtrace) if Gruf.backtrace_on_error
231
+ fail!(req, call, :internal, :unknown, e.message)
232
+ end
233
+
234
+ ##
235
+ # Add a field error to this endpoint
236
+ #
237
+ # @param [Symbol] field_name
238
+ # @param [Symbol] error_code
239
+ # @param [String] message
240
+ #
241
+ def add_field_error(field_name, error_code, message = '')
242
+ error.add_field_error(field_name, error_code, message)
243
+ end
244
+
245
+ ##
246
+ # Return true if there are any present field errors
247
+ #
248
+ # @return [Boolean]
249
+ #
250
+ def has_field_errors?
251
+ error.field_errors.any?
252
+ end
253
+
254
+ ##
255
+ # Set debugging information on the error payload
256
+ #
257
+ # @param [String] detail A string message that represents debugging information
258
+ # @param [Array<String>] stack_trace An array of strings that contain the backtrace
259
+ #
260
+ def set_debug_info(detail, stack_trace = [])
261
+ error.set_debug_info(detail, stack_trace)
262
+ end
263
+
264
+ ##
265
+ # @return [Gruf::Error]
266
+ #
267
+ def error
268
+ @error ||= Gruf::Error.new
269
+ end
270
+ end
271
+ end