carrot_rpc 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +40 -11
- data/lib/carrot_rpc/cli.rb +15 -0
- data/lib/carrot_rpc/client_server.rb +9 -0
- data/lib/carrot_rpc/configuration.rb +5 -1
- data/lib/carrot_rpc/exception.rb +1 -0
- data/lib/carrot_rpc/format.rb +23 -0
- data/lib/carrot_rpc/reply.rb +60 -0
- data/lib/carrot_rpc/rpc_client.rb +34 -37
- data/lib/carrot_rpc/rpc_server.rb +47 -76
- data/lib/carrot_rpc/scrub.rb +16 -0
- data/lib/carrot_rpc/server_runner/autoload_rails.rb +14 -1
- data/lib/carrot_rpc/server_runner.rb +2 -1
- data/lib/carrot_rpc/tagged_log.rb +6 -0
- data/lib/carrot_rpc/version.rb +1 -1
- data/lib/carrot_rpc.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b377cb46e74db89d8b6bcd97d305a26a0964d539
|
4
|
+
data.tar.gz: 266101d5211ce7409438a958dc5f9629e20cd43e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b42e357b5212e94d321a9ef2448567f23d905330a931218046f5920a39c818dadf20f3ad940d20da7b63841bb0a9e97595b911da978ae78d6305fa12c1b8dd7
|
7
|
+
data.tar.gz: a23da7aca020a9b0dfc1e17a521aea69a2675db343a1065c152b628d72d90e04c47655c8a6f9b218480174e16bb282112ab6664decd22ad6ec7b0bde07a51b07
|
data/CHANGELOG.md
CHANGED
@@ -40,6 +40,14 @@
|
|
40
40
|
# Changelog
|
41
41
|
All significant changes in the project are documented here.
|
42
42
|
|
43
|
+
## v0.6.0
|
44
|
+
### Enhancements
|
45
|
+
* [#34](https://github.com/C-S-D/carrot_rpc/pull/34) - `--server_test_mode` options for `carrot_rpc` sets `CarrotRpc.configuration.server_test_mode` to `true`. When `server_test_mode` is true, `_test` is appended to the queue name used by `CarrotRpc::RpcServer` and `CarrotRpc::RpcClient`, so that tests don't use the same queue as production or development. - [@shamil614](https://github.com/C-S-D/carrot_rpc/pull/34)
|
46
|
+
* [#36](https://github.com/C-S-D/carrot_rpc/pull/36) - Request in thread-local variable, so it can be used for client request - [@KronicDeth](https://github.com/KronicDeth)
|
47
|
+
* `carrot_rpc --thread-request VARIABLE` allows the request payload to be put in a Thread-local `VARIABLE, so that client that are invoked during an RPC server request can use parts of the request, most importantly, parts of "meta" in their own requests. This is needed to pass along ownership information for db_connection in Ecto 2.0.
|
48
|
+
* Tag the log with the correlation_id, as it can be filtered for then.
|
49
|
+
* Clarify whether a request or response is being published or received, so that server vs client logging can be distinguished.
|
50
|
+
|
43
51
|
## v0.5.1
|
44
52
|
### Bug Fixes
|
45
53
|
* [#31](https://github.com/C-S-D/carrot_rpc/pull/31) - If the server does not respond to a method in the `request_message`, then return a "Method not found" JSONRPC 2.0 error instead of the server crashing with `NoMethodError` exception. - [KronicDeth)(https://github.com/KronicDeth)
|
data/README.md
CHANGED
@@ -53,18 +53,19 @@ By typing in `carrot_rpc -h` you will see all the command line options:
|
|
53
53
|
Usage: server [options]
|
54
54
|
|
55
55
|
Process options:
|
56
|
-
-d, --daemonize
|
57
|
-
--pidfile PIDFILE
|
58
|
-
-s, --runloop_sleep VALUE
|
59
|
-
--autoload_rails VALUE
|
60
|
-
--logfile VALUE
|
61
|
-
--loglevel VALUE
|
62
|
-
--rabbitmq_url VALUE
|
56
|
+
-d, --daemonize run daemonized in the background (default: false)
|
57
|
+
--pidfile PIDFILE the pid filename
|
58
|
+
-s, --runloop_sleep VALUE Configurable sleep time in the runloop
|
59
|
+
--autoload_rails VALUE loads rails env by default. Uses Rails Logger by default.
|
60
|
+
--logfile VALUE relative path and name for Log file. Overrides Rails logger.
|
61
|
+
--loglevel VALUE levels of loggin: DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
62
|
+
--rabbitmq_url VALUE connection string to RabbitMQ 'amqp://user:pass@host:10000/vhost'
|
63
|
+
--thread-request VARIABLE Copies the current request into VARIABLE Thread local variable, where it can be retrieved with `Thread.thread_variable_get(<VARIABLE>)`
|
63
64
|
|
64
65
|
Ruby options:
|
65
|
-
-I, --include PATH
|
66
|
-
--debug
|
67
|
-
--warn
|
66
|
+
-I, --include PATH an additional $LOAD_PATH
|
67
|
+
--debug set $DEBUG to true
|
68
|
+
--warn enable warnings
|
68
69
|
|
69
70
|
Common options:
|
70
71
|
-h, --help
|
@@ -95,9 +96,10 @@ CarrotRpc.configure do |config|
|
|
95
96
|
config.rpc_client_response_key_format = :underscore
|
96
97
|
|
97
98
|
# Don't use. Server implementation only. The values below are set via CLI:
|
99
|
+
# config.logfile = nil
|
98
100
|
# config.pidfile = nil
|
99
101
|
# config.runloop_sleep = 0
|
100
|
-
# config.
|
102
|
+
# config.thread_request_variable = nil
|
101
103
|
end
|
102
104
|
```
|
103
105
|
|
@@ -182,6 +184,33 @@ car_client = CarClient.new(config)
|
|
182
184
|
```
|
183
185
|
By duplicating the `Configuration` instance you can override the global configuration and pass a custom configuration to the RpcClient instance.
|
184
186
|
|
187
|
+
#### Using request threading in clients
|
188
|
+
|
189
|
+
If you run the server with `--thread-request VARIABLE`, you can retrieve that variable in the `before_request` callback, such as to pass along important meta data:
|
190
|
+
|
191
|
+
```sh
|
192
|
+
carrot_rpc --thread-request carrot_rpc_server_request_message
|
193
|
+
```
|
194
|
+
|
195
|
+
Example Client: `app/clients/profile_client.rb`
|
196
|
+
```
|
197
|
+
# Allows calls to other RPC server's profile resource.
|
198
|
+
class ProfileClient < CarrotRpc::RpcClient
|
199
|
+
before_request ->(params){
|
200
|
+
request_message = Thread.current.thread_variable_get(:carrot_rpc_server_request_message)
|
201
|
+
|
202
|
+
if request_message
|
203
|
+
meta = params[:meta] || {}
|
204
|
+
# beam is test meta data specific to Elixir
|
205
|
+
meta = meta.merge(beam: request_message[:params][:meta][:beam])
|
206
|
+
params.merge(meta: meta)
|
207
|
+
else
|
208
|
+
params
|
209
|
+
end
|
210
|
+
}
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
185
214
|
### Support for JSONAPI::Resources
|
186
215
|
In the case that you're writing an application that uses the `jsonapi-resources` gem and you want the `RpcServer` to have the same functionality, then we got you covered. All you need to do is import a few modules. See [jsonapi-resources](https://github.com/cerebris/jsonapi-resources) for details on how to implement resources for your models.
|
187
216
|
|
data/lib/carrot_rpc/cli.rb
CHANGED
@@ -38,6 +38,12 @@ module CarrotRpc::CLI
|
|
38
38
|
CarrotRpc.configuration.runloop_sleep = value
|
39
39
|
end
|
40
40
|
|
41
|
+
stm_msg = "runs servers with '_test' appended to queue names." \
|
42
|
+
"Set Rails Rack env vars to 'test' when used in conjunction with '--autoload_rails'"
|
43
|
+
option_parser.on(" ", "--server_test_mode", stm_msg) do
|
44
|
+
CarrotRpc.configuration.server_test_mode = true
|
45
|
+
end
|
46
|
+
|
41
47
|
option_parser.on(
|
42
48
|
" ",
|
43
49
|
"--autoload_rails value",
|
@@ -67,6 +73,15 @@ module CarrotRpc::CLI
|
|
67
73
|
) do |value|
|
68
74
|
CarrotRpc.configuration.bunny = Bunny.new(value)
|
69
75
|
end
|
76
|
+
|
77
|
+
option_parser.on(
|
78
|
+
" ",
|
79
|
+
"--thread-request VARIABLE",
|
80
|
+
"Copies the current request into VARIABLE Thread local variable, " \
|
81
|
+
"where it can be retrieved with `Thread.thread_variable_get(<VARIABLE>)`"
|
82
|
+
) do |variable|
|
83
|
+
CarrotRpc.configuration.thread_request_variable = variable
|
84
|
+
end
|
70
85
|
end
|
71
86
|
|
72
87
|
def self.add_ruby_options(option_parser)
|
@@ -22,4 +22,13 @@ module CarrotRpc::ClientServer
|
|
22
22
|
"queue_name(new_name) :: new_name or queue_name() :: current_name are the only ways to call queue_name"
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
def test_queue_name(name, append_name = false)
|
27
|
+
return name unless append_name
|
28
|
+
if name
|
29
|
+
"#{name}_test"
|
30
|
+
else
|
31
|
+
fail CarrotRpc::Exception::InvalidQueueName
|
32
|
+
end
|
33
|
+
end
|
25
34
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# Global configuration for {CarrotRpc}. Access with {CarrotRpc.configuration}.
|
2
2
|
class CarrotRpc::Configuration
|
3
3
|
attr_accessor :logger, :logfile, :loglevel, :daemonize, :pidfile, :runloop_sleep, :autoload_rails, :bunny,
|
4
|
-
:before_request, :rpc_client_timeout, :rpc_client_response_key_format, :rpc_client_request_key_format
|
4
|
+
:before_request, :rpc_client_timeout, :rpc_client_response_key_format, :rpc_client_request_key_format,
|
5
|
+
:server_test_mode, :client_test_mode, :thread_request_variable
|
5
6
|
|
6
7
|
# logfile - set logger to a file. overrides rails logger.
|
7
8
|
|
@@ -18,5 +19,8 @@ class CarrotRpc::Configuration
|
|
18
19
|
@rpc_client_timeout = 5
|
19
20
|
@rpc_client_response_key_format = :none
|
20
21
|
@rpc_client_request_key_format = :none
|
22
|
+
@client_test_mode = false
|
23
|
+
@server_test_mode = false
|
24
|
+
@thread_request_variable = nil
|
21
25
|
end # rubocop:enable Metrics/MethodLength
|
22
26
|
end
|
data/lib/carrot_rpc/exception.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Used to format keys
|
2
|
+
module CarrotRpc::Format
|
3
|
+
using CarrotRpc::HashExtensions
|
4
|
+
|
5
|
+
# Logic to process the renaming of keys in a hash.
|
6
|
+
# @param format [Symbol] :dasherize changes keys that have "_" to "-"
|
7
|
+
# @param format [Symbol] :underscore changes keys that have "-" to "_"
|
8
|
+
# @param format [Symbol] :skip, will not rename the keys
|
9
|
+
# @param data [Hash] data structure to be transformed
|
10
|
+
# @return [Hash] the transformed data
|
11
|
+
def self.keys(format, data)
|
12
|
+
case format
|
13
|
+
when :dasherize
|
14
|
+
data.rename_keys("_", "-")
|
15
|
+
when :underscore
|
16
|
+
data.rename_keys("-", "_")
|
17
|
+
when :none
|
18
|
+
data
|
19
|
+
else
|
20
|
+
data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Publishes properly formatted replies to consumed requests
|
2
|
+
module CarrotRpc::Reply
|
3
|
+
private
|
4
|
+
|
5
|
+
def reply(properties:, response_message:)
|
6
|
+
payload = response_message.to_json
|
7
|
+
|
8
|
+
logger.debug "Publishing response: #{payload}"
|
9
|
+
|
10
|
+
@exchange.publish payload,
|
11
|
+
correlation_id: properties.correlation_id,
|
12
|
+
routing_key: properties.reply_to
|
13
|
+
end
|
14
|
+
|
15
|
+
# See http://www.jsonrpc.org/specification#error_object
|
16
|
+
def reply_error(error, properties:, request_message:)
|
17
|
+
response_message = { error: error, id: request_message[:id], jsonrpc: "2.0" }
|
18
|
+
|
19
|
+
reply properties: properties,
|
20
|
+
response_message: response_message
|
21
|
+
end
|
22
|
+
|
23
|
+
def reply_method_not_found(method:, properties:, request_message:)
|
24
|
+
error = CarrotRpc::Error.new code: CarrotRpc::Error::Code::METHOD_NOT_FOUND,
|
25
|
+
data: {
|
26
|
+
method: method
|
27
|
+
},
|
28
|
+
message: "Method not found"
|
29
|
+
logger.error(error)
|
30
|
+
|
31
|
+
reply_error error.serialized_message,
|
32
|
+
properties: properties,
|
33
|
+
request_message: request_message
|
34
|
+
end
|
35
|
+
|
36
|
+
# See http://www.jsonrpc.org/specification#response_object
|
37
|
+
def reply_result(result, properties:, request_message:)
|
38
|
+
if result && result.is_a?(Hash) && result["errors"]
|
39
|
+
reply_result_with_errors(result, properties: properties, request_message: request_message)
|
40
|
+
else
|
41
|
+
reply_result_without_errors(result, properties: properties, request_message: request_message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def reply_result_with_errors(result, properties:, request_message:)
|
46
|
+
scrubbed_result = result.merge(
|
47
|
+
"errors" => CarrotRpc::Scrub.errors(result.fetch("errors"))
|
48
|
+
)
|
49
|
+
reply_error({ code: 422, data: scrubbed_result, message: "JSONAPI error" },
|
50
|
+
properties: properties,
|
51
|
+
request_message: request_message)
|
52
|
+
end
|
53
|
+
|
54
|
+
def reply_result_without_errors(result, properties:, request_message:)
|
55
|
+
response_message = { id: request_message[:id], jsonrpc: "2.0", result: result }
|
56
|
+
|
57
|
+
reply properties: properties,
|
58
|
+
response_message: response_message
|
59
|
+
end
|
60
|
+
end
|
@@ -4,8 +4,6 @@ require "securerandom"
|
|
4
4
|
# Let's define a naming convention here for subclasses becuase I don't want to write a Confluence doc.
|
5
5
|
# All subclasses should have the following naming convention: <Name>RpcConsumer ex: PostRpcConsumer
|
6
6
|
class CarrotRpc::RpcClient
|
7
|
-
using CarrotRpc::HashExtensions
|
8
|
-
|
9
7
|
attr_reader :channel, :server_queue, :logger
|
10
8
|
|
11
9
|
extend CarrotRpc::ClientServer
|
@@ -21,25 +19,6 @@ class CarrotRpc::RpcClient
|
|
21
19
|
end
|
22
20
|
end
|
23
21
|
|
24
|
-
# Logic to process the renaming of keys in a hash.
|
25
|
-
# @param format [Symbol] :dasherize changes keys that have "_" to "-"
|
26
|
-
# @param format [Symbol] :underscore changes keys that have "-" to "_"
|
27
|
-
# @param format [Symbol] :skip, will not rename the keys
|
28
|
-
# @param data [Hash] data structure to be transformed
|
29
|
-
# @return [Hash] the transformed data
|
30
|
-
def self.format_keys(format, data)
|
31
|
-
case format
|
32
|
-
when :dasherize
|
33
|
-
data.rename_keys("_", "-")
|
34
|
-
when :underscore
|
35
|
-
data.rename_keys("-", "_")
|
36
|
-
when :none
|
37
|
-
data
|
38
|
-
else
|
39
|
-
data
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
22
|
# Use defaults for application level connection to RabbitMQ.
|
44
23
|
#
|
45
24
|
# @example pass custom {Configuration} class as an argument to override.
|
@@ -59,8 +38,9 @@ class CarrotRpc::RpcClient
|
|
59
38
|
# Create a new channel on each request because the channel should be closed after each request.
|
60
39
|
@channel = @config.bunny.create_channel
|
61
40
|
|
41
|
+
queue_name = self.class.test_queue_name(self.class.queue_name, @config.client_test_mode)
|
62
42
|
# auto_delete => false keeps the queue around until RabbitMQ restarts or explicitly deleted
|
63
|
-
@server_queue = @channel.queue(
|
43
|
+
@server_queue = @channel.queue(queue_name, auto_delete: false)
|
64
44
|
|
65
45
|
# Setup a direct exchange.
|
66
46
|
@exchange = @channel.default_exchange
|
@@ -76,12 +56,8 @@ class CarrotRpc::RpcClient
|
|
76
56
|
|
77
57
|
# setup subscribe block to Service
|
78
58
|
# block => false is a non blocking IO option.
|
79
|
-
@reply_queue.subscribe(block: false) do |
|
80
|
-
|
81
|
-
|
82
|
-
result = parse_response(response)
|
83
|
-
result = response_key_formatter(result).with_indifferent_access if result.is_a? Hash
|
84
|
-
@results[properties[:correlation_id]].push(result)
|
59
|
+
@reply_queue.subscribe(block: false) do |delivery_info, properties, payload|
|
60
|
+
consume(delivery_info, properties, payload)
|
85
61
|
end
|
86
62
|
end
|
87
63
|
|
@@ -97,9 +73,11 @@ class CarrotRpc::RpcClient
|
|
97
73
|
start
|
98
74
|
subscribe
|
99
75
|
correlation_id = SecureRandom.uuid
|
100
|
-
|
101
|
-
|
102
|
-
|
76
|
+
logger.with_correlation_id(correlation_id) do
|
77
|
+
params = self.class.before_request.call(params) if self.class.before_request
|
78
|
+
publish(correlation_id: correlation_id, method: remote_method, params: request_key_formatter(params))
|
79
|
+
wait_for_result(correlation_id)
|
80
|
+
end
|
103
81
|
end
|
104
82
|
|
105
83
|
def wait_for_result(correlation_id)
|
@@ -120,14 +98,14 @@ class CarrotRpc::RpcClient
|
|
120
98
|
# @param payload [Hash] response data received from the remote server.
|
121
99
|
# @return [Hash] formatted data structure.
|
122
100
|
def response_key_formatter(payload)
|
123
|
-
|
101
|
+
CarrotRpc::Format.keys @config.rpc_client_response_key_format, payload
|
124
102
|
end
|
125
103
|
|
126
104
|
# Formats keys in the request data.
|
127
105
|
# @param payload [Hash] request data to be sent to the remote server.
|
128
106
|
# @return [Hash] formatted data structure.
|
129
107
|
def request_key_formatter(params)
|
130
|
-
|
108
|
+
CarrotRpc::Format.keys @config.rpc_client_request_key_format, params
|
131
109
|
end
|
132
110
|
|
133
111
|
# A @reply_queue is deleted when the channel is closed.
|
@@ -138,10 +116,7 @@ class CarrotRpc::RpcClient
|
|
138
116
|
params: params,
|
139
117
|
method: method
|
140
118
|
)
|
141
|
-
|
142
|
-
# Correlation ID => identify the results that belong to the unique call made
|
143
|
-
@exchange.publish(message.to_json, routing_key: @server_queue.name, correlation_id: correlation_id,
|
144
|
-
reply_to: @reply_queue.name)
|
119
|
+
publish_payload(message.to_json, correlation_id: correlation_id)
|
145
120
|
end
|
146
121
|
|
147
122
|
def message(correlation_id:, method:, params:)
|
@@ -155,6 +130,18 @@ class CarrotRpc::RpcClient
|
|
155
130
|
|
156
131
|
private
|
157
132
|
|
133
|
+
def consume(_delivery_info, properties, payload)
|
134
|
+
logger.with_correlation_id(properties[:correlation_id]) do
|
135
|
+
logger.debug "Receiving response: #{payload}"
|
136
|
+
|
137
|
+
response = JSON.parse(payload).with_indifferent_access
|
138
|
+
|
139
|
+
result = parse_response(response)
|
140
|
+
result = response_key_formatter(result).with_indifferent_access if result.is_a? Hash
|
141
|
+
@results[properties[:correlation_id]].push(result)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
158
145
|
# Logic to find the data from the RPC response.
|
159
146
|
# @param [Hash] response from rpc call
|
160
147
|
# @return [Hash,nil]
|
@@ -169,4 +156,14 @@ class CarrotRpc::RpcClient
|
|
169
156
|
response
|
170
157
|
end
|
171
158
|
end
|
159
|
+
|
160
|
+
def publish_payload(payload, correlation_id:)
|
161
|
+
# Reply To => make sure the service knows where to send it's response.
|
162
|
+
# Correlation ID => identify the results that belong to the unique call made
|
163
|
+
logger.debug "Publishing request: #{payload}"
|
164
|
+
@exchange.publish payload,
|
165
|
+
correlation_id: correlation_id,
|
166
|
+
reply_to: @reply_queue.name,
|
167
|
+
routing_key: @server_queue.name
|
168
|
+
end
|
172
169
|
end
|
@@ -4,19 +4,22 @@ class CarrotRpc::RpcServer
|
|
4
4
|
|
5
5
|
using CarrotRpc::HashExtensions
|
6
6
|
|
7
|
-
attr_reader :channel, :server_queue, :logger
|
7
|
+
attr_reader :channel, :server_queue, :logger, :thread_request_variable
|
8
8
|
# method_reciver => object that receives the method. can be a class or anything responding to send
|
9
9
|
|
10
10
|
extend CarrotRpc::ClientServer
|
11
|
+
include CarrotRpc::Reply
|
11
12
|
|
12
13
|
# Documentation advises not to share a channel connection. Create new channel for each server instance.
|
13
14
|
def initialize(config: nil, block: true)
|
14
15
|
# create a channel and exchange that both client and server know about
|
15
16
|
config ||= CarrotRpc.configuration
|
17
|
+
@thread_request_variable = config.thread_request_variable
|
16
18
|
@channel = config.bunny.create_channel
|
17
19
|
@logger = config.logger
|
18
20
|
@block = block
|
19
|
-
|
21
|
+
queue_name = self.class.test_queue_name(self.class.queue_name, config.server_test_mode)
|
22
|
+
@server_queue = @channel.queue(queue_name)
|
20
23
|
@exchange = @channel.default_exchange
|
21
24
|
end
|
22
25
|
|
@@ -24,28 +27,21 @@ class CarrotRpc::RpcServer
|
|
24
27
|
# method => object that receives the method. can be a class or anything responding to send
|
25
28
|
def start
|
26
29
|
# subscribe is like a callback
|
27
|
-
@server_queue.subscribe(block: @block) do |
|
28
|
-
|
29
|
-
|
30
|
-
request_message = JSON.parse(payload).with_indifferent_access
|
31
|
-
|
32
|
-
process_request(request_message, properties: properties)
|
30
|
+
@server_queue.subscribe(block: @block) do |delivery_info, properties, payload|
|
31
|
+
consume(delivery_info, properties, payload)
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
35
|
def process_request(request_message, properties:)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
method: method,
|
47
|
-
properties: properties,
|
48
|
-
request_message: request_message
|
36
|
+
maybe_thread_request(request_message: request_message) do
|
37
|
+
method = request_message[:method]
|
38
|
+
handler = method_handler(method)
|
39
|
+
|
40
|
+
send handler,
|
41
|
+
method: method,
|
42
|
+
properties: properties,
|
43
|
+
request_message: request_message
|
44
|
+
end
|
49
45
|
end
|
50
46
|
|
51
47
|
private
|
@@ -64,73 +60,48 @@ class CarrotRpc::RpcServer
|
|
64
60
|
request_message: request_message
|
65
61
|
end
|
66
62
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
routing_key: properties.reply_to
|
71
|
-
end
|
72
|
-
|
73
|
-
# See http://www.jsonrpc.org/specification#error_object
|
74
|
-
def reply_error(error, properties:, request_message:)
|
75
|
-
response_message = { error: error, id: request_message[:id], jsonrpc: "2.0" }
|
63
|
+
def consume(_delivery_info, properties, payload)
|
64
|
+
logger.tagged("server", "queue=#{server_queue.name}", "correlation_id=#{properties[:correlation_id]}") do
|
65
|
+
logger.debug "Receiving request: #{payload}"
|
76
66
|
|
77
|
-
|
67
|
+
# rubocop:disable Lint/RescueException
|
68
|
+
begin
|
69
|
+
request_message = JSON.parse(payload).with_indifferent_access
|
78
70
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
data: {
|
86
|
-
method: method
|
87
|
-
},
|
88
|
-
message: "Method not found"
|
89
|
-
logger.error(error)
|
90
|
-
|
91
|
-
reply_error error.serialized_message,
|
92
|
-
properties: properties,
|
93
|
-
request_message: request_message
|
71
|
+
process_request(request_message, properties: properties)
|
72
|
+
rescue Exception => exception
|
73
|
+
logger.error(exception)
|
74
|
+
end
|
75
|
+
# rubocop:enable Lint/RescueException
|
76
|
+
end
|
94
77
|
end
|
95
78
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
reply_result_with_errors(result, properties: properties, request_message: request_message)
|
79
|
+
def maybe_thread_request(request_message:)
|
80
|
+
if thread_request_variable
|
81
|
+
thread_request(request_message: request_message, &Proc.new)
|
100
82
|
else
|
101
|
-
|
83
|
+
yield
|
102
84
|
end
|
103
85
|
end
|
104
86
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
request_message: request_message)
|
87
|
+
def method_handler(method)
|
88
|
+
if respond_to? method
|
89
|
+
:call_found_method
|
90
|
+
else
|
91
|
+
:reply_method_not_found
|
92
|
+
end
|
112
93
|
end
|
113
94
|
|
114
|
-
def
|
115
|
-
|
95
|
+
def thread_request(request_message:)
|
96
|
+
logger.debug "Threading request (#{request_message.inspect}) " \
|
97
|
+
"in Thread.current.thread_variable_get(#{thread_request_variable.inspect})"
|
116
98
|
|
117
|
-
|
99
|
+
Thread.current.thread_variable_set(thread_request_variable, request_message)
|
118
100
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
def scrub_error(error)
|
125
|
-
error.reject { |_, value|
|
126
|
-
value.nil?
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
# Removes `nil` values as JSONAPI spec expects unset keys not to be transmitted
|
131
|
-
def scrub_errors(errors)
|
132
|
-
errors.map { |error|
|
133
|
-
scrub_error(error)
|
134
|
-
}
|
101
|
+
begin
|
102
|
+
yield
|
103
|
+
ensure
|
104
|
+
Thread.current.thread_variable_set(thread_request_variable, nil)
|
105
|
+
end
|
135
106
|
end
|
136
107
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Removes `nil` valued keys from nested `Hash`es.
|
2
|
+
module CarrotRpc::Scrub
|
3
|
+
# Removes `nil` values as JSONAPI spec expects unset keys not to be transmitted
|
4
|
+
def self.error(error)
|
5
|
+
error.reject { |_, value|
|
6
|
+
value.nil?
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Removes `nil` values as JSONAPI spec expects unset keys not to be transmitted
|
11
|
+
def self.errors(errors)
|
12
|
+
errors.map { |error|
|
13
|
+
error(error)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
@@ -19,7 +19,7 @@ module CarrotRpc::ServerRunner::AutoloadRails
|
|
19
19
|
|
20
20
|
if File.exist?(rails_path)
|
21
21
|
logger.info "Rails app found at: #{rails_path}"
|
22
|
-
|
22
|
+
set_rack_rails_env
|
23
23
|
require rails_path
|
24
24
|
::Rails.application.eager_load!
|
25
25
|
true
|
@@ -38,4 +38,17 @@ module CarrotRpc::ServerRunner::AutoloadRails
|
|
38
38
|
load_root(root, logger: logger)
|
39
39
|
end
|
40
40
|
end
|
41
|
+
|
42
|
+
# Set Rails/Rack env vars to test when server test mode is enabled.
|
43
|
+
#
|
44
|
+
# @return [Void]
|
45
|
+
def self.set_rack_rails_env
|
46
|
+
if CarrotRpc.configuration.server_test_mode
|
47
|
+
env_mode = "test"
|
48
|
+
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = env_mode
|
49
|
+
else
|
50
|
+
env_mode = "development"
|
51
|
+
ENV["RACK_ENV"] ||= ENV["RAILS_ENV"] ||= env_mode
|
52
|
+
end
|
53
|
+
end
|
41
54
|
end
|
@@ -81,7 +81,8 @@ class CarrotRpc::ServerRunner
|
|
81
81
|
server_klass_name = file.to_s.split("/").last.gsub(".rb", "").camelize
|
82
82
|
server_klass = server_klass_name.constantize
|
83
83
|
|
84
|
-
|
84
|
+
stm = CarrotRpc.configuration.server_test_mode
|
85
|
+
logger.info "Starting: #{server_klass} | Rails/Rack ENV: #{ENV['RAILS_ENV']} | Server Test Mode: #{stm}"
|
85
86
|
|
86
87
|
server = server_klass.new(block: false)
|
87
88
|
server.start
|
@@ -21,4 +21,10 @@ class CarrotRpc::TaggedLog
|
|
21
21
|
logger.tagged(tags) { logger.send(level, msg || block.call) }
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
delegate :tagged, to: :logger
|
26
|
+
|
27
|
+
def with_correlation_id(correlation_id, &block)
|
28
|
+
tagged("correlation_id=#{correlation_id}", &block)
|
29
|
+
end
|
24
30
|
end
|
data/lib/carrot_rpc/version.rb
CHANGED
data/lib/carrot_rpc.rb
CHANGED
@@ -23,10 +23,13 @@ module CarrotRpc
|
|
23
23
|
autoload :Configuration
|
24
24
|
autoload :Error
|
25
25
|
autoload :Exception
|
26
|
+
autoload :Format
|
26
27
|
autoload :HashExtensions
|
28
|
+
autoload :Reply
|
27
29
|
autoload :RpcClient
|
28
30
|
autoload :RpcServer
|
29
31
|
autoload :ServerRunner
|
32
|
+
autoload :Scrub
|
30
33
|
autoload :TaggedLog
|
31
34
|
|
32
35
|
class << self
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: carrot_rpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Hamilton
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-07-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -158,11 +158,14 @@ files:
|
|
158
158
|
- lib/carrot_rpc/error.rb
|
159
159
|
- lib/carrot_rpc/error/code.rb
|
160
160
|
- lib/carrot_rpc/exception.rb
|
161
|
+
- lib/carrot_rpc/format.rb
|
161
162
|
- lib/carrot_rpc/hash_extensions.rb
|
163
|
+
- lib/carrot_rpc/reply.rb
|
162
164
|
- lib/carrot_rpc/rpc_client.rb
|
163
165
|
- lib/carrot_rpc/rpc_server.rb
|
164
166
|
- lib/carrot_rpc/rpc_server/jsonapi_resources.rb
|
165
167
|
- lib/carrot_rpc/rpc_server/jsonapi_resources/actions.rb
|
168
|
+
- lib/carrot_rpc/scrub.rb
|
166
169
|
- lib/carrot_rpc/server_runner.rb
|
167
170
|
- lib/carrot_rpc/server_runner/autoload_rails.rb
|
168
171
|
- lib/carrot_rpc/server_runner/logger.rb
|