pantry 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.ruby-version +1 -0
- data/.travis.yml +19 -0
- data/Gemfile +15 -0
- data/Guardfile +16 -0
- data/LICENSE +20 -0
- data/README.md +53 -0
- data/Rakefile +18 -0
- data/Vagrantfile +86 -0
- data/bin/pantry +11 -0
- data/bin/pantry-client +38 -0
- data/bin/pantry-server +33 -0
- data/dist/client.yml +79 -0
- data/dist/server.yml +56 -0
- data/dist/upstart/pantry-client.conf +12 -0
- data/dist/upstart/pantry-server.conf +12 -0
- data/doc/message_packet.dot +19 -0
- data/doc/message_packet.dot.png +0 -0
- data/doc/network_topology.dot +42 -0
- data/doc/network_topology.dot.png +0 -0
- data/lib/celluloid_zmq_patches.rb +16 -0
- data/lib/opt_parse_plus.rb +184 -0
- data/lib/pantry.rb +197 -0
- data/lib/pantry/cli.rb +154 -0
- data/lib/pantry/client.rb +131 -0
- data/lib/pantry/client_info.rb +34 -0
- data/lib/pantry/client_registry.rb +104 -0
- data/lib/pantry/command.rb +194 -0
- data/lib/pantry/command_handler.rb +53 -0
- data/lib/pantry/command_line.rb +115 -0
- data/lib/pantry/commands/create_client.rb +30 -0
- data/lib/pantry/commands/download_directory.rb +35 -0
- data/lib/pantry/commands/echo.rb +32 -0
- data/lib/pantry/commands/edit_application.rb +60 -0
- data/lib/pantry/commands/register_client.rb +38 -0
- data/lib/pantry/commands/status.rb +78 -0
- data/lib/pantry/commands/sync_directory.rb +50 -0
- data/lib/pantry/commands/update_application.rb +45 -0
- data/lib/pantry/commands/upload_file.rb +68 -0
- data/lib/pantry/communication.rb +20 -0
- data/lib/pantry/communication/client.rb +75 -0
- data/lib/pantry/communication/client_filter.rb +117 -0
- data/lib/pantry/communication/file_service.rb +125 -0
- data/lib/pantry/communication/file_service/file_progress.rb +164 -0
- data/lib/pantry/communication/file_service/receive_file.rb +97 -0
- data/lib/pantry/communication/file_service/send_file.rb +74 -0
- data/lib/pantry/communication/publish_socket.rb +20 -0
- data/lib/pantry/communication/reading_socket.rb +89 -0
- data/lib/pantry/communication/receive_socket.rb +23 -0
- data/lib/pantry/communication/security.rb +44 -0
- data/lib/pantry/communication/security/authentication.rb +98 -0
- data/lib/pantry/communication/security/curve_key_store.rb +120 -0
- data/lib/pantry/communication/security/curve_security.rb +70 -0
- data/lib/pantry/communication/security/null_security.rb +32 -0
- data/lib/pantry/communication/send_socket.rb +19 -0
- data/lib/pantry/communication/serialize_message.rb +84 -0
- data/lib/pantry/communication/server.rb +97 -0
- data/lib/pantry/communication/subscribe_socket.rb +33 -0
- data/lib/pantry/communication/wait_list.rb +45 -0
- data/lib/pantry/communication/writing_socket.rb +46 -0
- data/lib/pantry/config.rb +182 -0
- data/lib/pantry/file_editor.rb +67 -0
- data/lib/pantry/logger.rb +78 -0
- data/lib/pantry/message.rb +134 -0
- data/lib/pantry/multi_command.rb +36 -0
- data/lib/pantry/server.rb +132 -0
- data/lib/pantry/test/acceptance.rb +83 -0
- data/lib/pantry/test/support/fake_fs.rb +31 -0
- data/lib/pantry/test/support/matchers.rb +13 -0
- data/lib/pantry/test/support/minitest.rb +13 -0
- data/lib/pantry/test/support/mock_ui.rb +23 -0
- data/lib/pantry/test/unit.rb +13 -0
- data/lib/pantry/ui.rb +68 -0
- data/lib/pantry/version.rb +3 -0
- data/pantry.gemspec +40 -0
- data/test/acceptance/cli/error_handling_test.rb +7 -0
- data/test/acceptance/cli/execute_command_on_clients_test.rb +32 -0
- data/test/acceptance/cli/request_info_from_server_test.rb +44 -0
- data/test/acceptance/communication/client_requests_info_from_server_test.rb +28 -0
- data/test/acceptance/communication/heartbeat_test.rb +19 -0
- data/test/acceptance/communication/pub_sub_communication_test.rb +53 -0
- data/test/acceptance/communication/security_test.rb +117 -0
- data/test/acceptance/communication/server_requests_info_from_client_test.rb +41 -0
- data/test/acceptance/test_helper.rb +25 -0
- data/test/fixtures/config.yml +22 -0
- data/test/fixtures/empty.yml +2 -0
- data/test/fixtures/file_to_upload +3 -0
- data/test/root_dir/.gitkeep +0 -0
- data/test/unit/cli_test.rb +173 -0
- data/test/unit/client_registry_test.rb +61 -0
- data/test/unit/client_test.rb +128 -0
- data/test/unit/command_handler_test.rb +79 -0
- data/test/unit/command_line_test.rb +5 -0
- data/test/unit/command_test.rb +206 -0
- data/test/unit/commands/create_client_test.rb +25 -0
- data/test/unit/commands/download_directory_test.rb +58 -0
- data/test/unit/commands/echo_test.rb +22 -0
- data/test/unit/commands/edit_application_test.rb +84 -0
- data/test/unit/commands/register_client_test.rb +41 -0
- data/test/unit/commands/status_test.rb +81 -0
- data/test/unit/commands/sync_directory_test.rb +75 -0
- data/test/unit/commands/update_application_test.rb +35 -0
- data/test/unit/commands/upload_file_test.rb +51 -0
- data/test/unit/communication/client_filter_test.rb +262 -0
- data/test/unit/communication/client_test.rb +99 -0
- data/test/unit/communication/file_service/receive_file_test.rb +214 -0
- data/test/unit/communication/file_service/send_file_test.rb +110 -0
- data/test/unit/communication/file_service_test.rb +56 -0
- data/test/unit/communication/publish_socket_test.rb +19 -0
- data/test/unit/communication/reading_socket_test.rb +110 -0
- data/test/unit/communication/receive_socket_test.rb +20 -0
- data/test/unit/communication/security/authentication_test.rb +97 -0
- data/test/unit/communication/security/curve_key_store_test.rb +110 -0
- data/test/unit/communication/security/curve_security_test.rb +44 -0
- data/test/unit/communication/security/null_security_test.rb +15 -0
- data/test/unit/communication/security_test.rb +49 -0
- data/test/unit/communication/send_socket_test.rb +19 -0
- data/test/unit/communication/serialize_message_test.rb +128 -0
- data/test/unit/communication/server_test.rb +106 -0
- data/test/unit/communication/subscribe_socket_test.rb +46 -0
- data/test/unit/communication/wait_list_test.rb +60 -0
- data/test/unit/communication/writing_socket_test.rb +46 -0
- data/test/unit/config_test.rb +150 -0
- data/test/unit/logger_test.rb +79 -0
- data/test/unit/message_test.rb +179 -0
- data/test/unit/multi_command_test.rb +45 -0
- data/test/unit/opt_parse_plus_test.rb +218 -0
- data/test/unit/pantry_test.rb +82 -0
- data/test/unit/server_test.rb +166 -0
- data/test/unit/test_helper.rb +25 -0
- data/test/unit/ui_test.rb +58 -0
- metadata +389 -13
@@ -0,0 +1,44 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
module Security
|
4
|
+
|
5
|
+
class UnknownSecurityStrategyError < Exception; end
|
6
|
+
|
7
|
+
AVAILABLE_SECURITY = {
|
8
|
+
nil => Pantry::Communication::Security::NullSecurity,
|
9
|
+
"curve" => Pantry::Communication::Security::CurveSecurity
|
10
|
+
}
|
11
|
+
|
12
|
+
# Check if ZeroMQ is built properly to support Curve encryption
|
13
|
+
def self.curve_supported?
|
14
|
+
begin
|
15
|
+
ZMQ::Util.curve_keypair
|
16
|
+
true
|
17
|
+
rescue
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Build a Client implementation of the security strategy
|
23
|
+
# configured in Pantry.config.security
|
24
|
+
def self.new_client(config = Pantry.config)
|
25
|
+
handler_class(config).client
|
26
|
+
end
|
27
|
+
|
28
|
+
# Build a Server implementation of the security strategy
|
29
|
+
# configured in Pantry.config.security
|
30
|
+
def self.new_server(config = Pantry.config)
|
31
|
+
handler_class(config).server
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.handler_class(config)
|
35
|
+
if handler = AVAILABLE_SECURITY[config.security]
|
36
|
+
handler
|
37
|
+
else
|
38
|
+
raise UnknownSecurityStrategyError, "Unknown security strategy #{config.security.inspect}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
module Security
|
4
|
+
|
5
|
+
# This class implements and manages the ZAP handler.
|
6
|
+
# For any connecting client, this handler receives a request to
|
7
|
+
# authenticate the Client. If the Client is allowed in, all proceeds as
|
8
|
+
# normal. If a Client is not allowed in then the connection is dropped.
|
9
|
+
#
|
10
|
+
# For Pantry, this is a very strict authentication mechanism that only
|
11
|
+
# allows Clients in whos public keys are in the server_keys.yml keystore.
|
12
|
+
# It also rejects any attempts to authenticate with a mechanism other than CURVE.
|
13
|
+
#
|
14
|
+
# ZAP: ZeroMQ Authentication Protocol :: http://rfc.zeromq.org/spec:27
|
15
|
+
class Authentication
|
16
|
+
include Celluloid::ZMQ
|
17
|
+
finalizer :shutdown
|
18
|
+
|
19
|
+
def initialize(key_store)
|
20
|
+
@key_store = key_store
|
21
|
+
|
22
|
+
@socket = Celluloid::ZMQ::RepSocket.new
|
23
|
+
@socket.linger = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def open
|
27
|
+
@socket.bind("inproc://zeromq.zap.01")
|
28
|
+
@running = true
|
29
|
+
self.async.process_requests
|
30
|
+
end
|
31
|
+
|
32
|
+
def shutdown
|
33
|
+
@socket.close
|
34
|
+
@running = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_requests
|
38
|
+
while @running
|
39
|
+
process_next_request
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_next_request
|
44
|
+
request = read_next_request
|
45
|
+
|
46
|
+
response_code, response_text = authenticate_request(request)
|
47
|
+
|
48
|
+
if response_code == "200"
|
49
|
+
Pantry.logger.debug("[AUTH] Client authentication successful")
|
50
|
+
else
|
51
|
+
Pantry.logger.debug("[AUTH] Client authentication rejected: #{response_text}")
|
52
|
+
end
|
53
|
+
|
54
|
+
write_response(request, response_code, response_text)
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_next_request
|
58
|
+
request = []
|
59
|
+
begin
|
60
|
+
request << @socket.read
|
61
|
+
end while @socket.more_parts?
|
62
|
+
request
|
63
|
+
end
|
64
|
+
|
65
|
+
def authenticate_request(request)
|
66
|
+
mechanism = request[5]
|
67
|
+
client_key = request[6]
|
68
|
+
|
69
|
+
if mechanism != "CURVE"
|
70
|
+
["400", "Invalid Mechanism"]
|
71
|
+
else
|
72
|
+
authenticate_client(client_key)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def authenticate_client(client_key)
|
77
|
+
if @key_store.known_client?(client_key)
|
78
|
+
["200", "OK"]
|
79
|
+
else
|
80
|
+
["400", "Unknown Client"]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def write_response(request, response_code, response_text)
|
85
|
+
@socket.write([
|
86
|
+
request[0], # Version
|
87
|
+
request[1], # Sequence / Request id
|
88
|
+
response_code,
|
89
|
+
response_text,
|
90
|
+
"", # username
|
91
|
+
"" # metadata
|
92
|
+
])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
module Security
|
4
|
+
|
5
|
+
# CurveKeyStore manages the storage, reading, and writing of all
|
6
|
+
# Curve-related key-pairs.
|
7
|
+
#
|
8
|
+
# Clients keep track of the public key of the server they talk to
|
9
|
+
# Servers keep track of the list of public keys of Clients who are
|
10
|
+
# allowed to connect.
|
11
|
+
#
|
12
|
+
# All keys are stored under Pantry.root/security/curve
|
13
|
+
class CurveKeyStore
|
14
|
+
|
15
|
+
attr_reader :public_key, :private_key, :server_public_key
|
16
|
+
|
17
|
+
def initialize(my_key_pair_name)
|
18
|
+
@base_key_dir = Pantry.root.join("security", "curve")
|
19
|
+
@my_keys_file = @base_key_dir.join("#{my_key_pair_name}.yml")
|
20
|
+
@known_clients = []
|
21
|
+
|
22
|
+
ensure_directory_structure
|
23
|
+
check_or_generate_my_keys
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check if the given client public key is known by this server or
|
27
|
+
# not. To facilitate the initial setup process of a new Pantry Server,
|
28
|
+
# this will allow and store the first client to connect to this server
|
29
|
+
# and will write out that client's public key as valid.
|
30
|
+
#
|
31
|
+
# Used solely by the Server
|
32
|
+
def known_client?(client_public_key)
|
33
|
+
encoded_key = z85_encode(client_public_key)
|
34
|
+
if @known_clients.empty?
|
35
|
+
store_known_client(encoded_key)
|
36
|
+
true
|
37
|
+
else
|
38
|
+
@known_clients.include?(encoded_key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Generate and store a new Client pub/priv key pair
|
43
|
+
# Only the Public key is stored locally for authentication purposes.
|
44
|
+
# Returns a hash of all relevant keys for the Client to connect
|
45
|
+
# and Auth.
|
46
|
+
def create_client
|
47
|
+
client_public, client_private = ZMQ::Util.curve_keypair
|
48
|
+
store_known_client(client_public)
|
49
|
+
|
50
|
+
{
|
51
|
+
server_public_key: @public_key,
|
52
|
+
public_key: client_public,
|
53
|
+
private_key: client_private
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
# TODO Move this logic into ffi-rzmq proper
|
60
|
+
def z85_encode(binary_key)
|
61
|
+
encoded = FFI::MemoryPointer.from_string(' ' * 41)
|
62
|
+
LibZMQ::zmq_z85_encode(encoded, binary_key, 32)
|
63
|
+
end
|
64
|
+
|
65
|
+
def ensure_directory_structure
|
66
|
+
FileUtils.mkdir_p(@base_key_dir)
|
67
|
+
FileUtils.chmod(0700, @base_key_dir)
|
68
|
+
end
|
69
|
+
|
70
|
+
def check_or_generate_my_keys
|
71
|
+
if File.exists?(@my_keys_file)
|
72
|
+
load_current_key_pair
|
73
|
+
end
|
74
|
+
|
75
|
+
generate_missing_keys
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_current_key_pair
|
79
|
+
keys = YAML.load_file(@my_keys_file)
|
80
|
+
@public_key = keys["public_key"]
|
81
|
+
@private_key = keys["private_key"]
|
82
|
+
@server_public_key = keys["server_public_key"]
|
83
|
+
@known_clients = keys["client_keys"] || []
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_missing_keys
|
87
|
+
if @public_key.nil? && @private_key.nil?
|
88
|
+
@public_key, @private_key = ZMQ::Util.curve_keypair
|
89
|
+
save_keys
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def store_known_client(client_public_key)
|
94
|
+
@known_clients << client_public_key
|
95
|
+
save_keys
|
96
|
+
end
|
97
|
+
|
98
|
+
def save_keys
|
99
|
+
File.open(@my_keys_file, "w+") do |f|
|
100
|
+
keys = {
|
101
|
+
"private_key" => @private_key,
|
102
|
+
"public_key" => @public_key
|
103
|
+
}
|
104
|
+
|
105
|
+
if @server_public_key
|
106
|
+
keys["server_public_key"] = @server_public_key
|
107
|
+
end
|
108
|
+
|
109
|
+
if @known_clients.length > 0
|
110
|
+
keys["client_keys"] = @known_clients
|
111
|
+
end
|
112
|
+
|
113
|
+
f.write YAML.dump(keys)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
module Security
|
4
|
+
|
5
|
+
# ZeroMQ Curve encryption strategy.
|
6
|
+
# For details about how the Curve encryption works in ZeroMQ, check out
|
7
|
+
# the following:
|
8
|
+
#
|
9
|
+
# * http://api.zeromq.org/4-0:zmq-curve
|
10
|
+
# * http://curvezmq.org/
|
11
|
+
#
|
12
|
+
class CurveSecurity
|
13
|
+
|
14
|
+
def self.client
|
15
|
+
Client.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.server
|
19
|
+
Server.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Client-side handling of Curve encryption.
|
23
|
+
class Client
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@key_store = CurveKeyStore.new("client_keys")
|
27
|
+
Pantry.logger.info("Configuring Client to use Curve encryption")
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure_socket(socket)
|
31
|
+
socket.set(::ZMQ::CURVE_SERVERKEY, @key_store.server_public_key)
|
32
|
+
socket.set(::ZMQ::CURVE_PUBLICKEY, @key_store.public_key)
|
33
|
+
socket.set(::ZMQ::CURVE_SECRETKEY, @key_store.private_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class Server
|
39
|
+
|
40
|
+
attr_reader :authentication
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@key_store = CurveKeyStore.new("server_keys")
|
44
|
+
@authentication = Authentication.new(@key_store)
|
45
|
+
@authentication.open
|
46
|
+
|
47
|
+
# We log the server's public key here to make it accessible for initial setup.
|
48
|
+
Pantry.logger.info("Configuring Server to use Curve encryption :: #{@key_store.public_key}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def link_to(parent)
|
52
|
+
parent.link(@authentication)
|
53
|
+
end
|
54
|
+
|
55
|
+
def configure_socket(socket)
|
56
|
+
socket.set(::ZMQ::CURVE_SERVER, 1)
|
57
|
+
socket.set(::ZMQ::CURVE_SECRETKEY, @key_store.private_key)
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_client
|
61
|
+
@key_store.create_client
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
module Security
|
4
|
+
|
5
|
+
# The no-security security strategy
|
6
|
+
class NullSecurity
|
7
|
+
|
8
|
+
def self.client
|
9
|
+
new
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.server
|
13
|
+
new
|
14
|
+
end
|
15
|
+
|
16
|
+
def link_to(parent)
|
17
|
+
# no-op
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure_socket(socket)
|
21
|
+
# no-op
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_client
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
|
4
|
+
# The SendSocket allows one-way asynchronous communication to the server.
|
5
|
+
# This is implemented through the ZMQ DEALER socket, which communicates with
|
6
|
+
# the Server's ROUTER socket.
|
7
|
+
class SendSocket < WritingSocket
|
8
|
+
|
9
|
+
def build_socket
|
10
|
+
Celluloid::ZMQ::DealerSocket.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def open_socket(socket)
|
14
|
+
socket.connect("tcp://#{host}:#{port}")
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Pantry
|
2
|
+
module Communication
|
3
|
+
|
4
|
+
# Handles all serialization of Pantry::Messages to and from the ZeroMQ
|
5
|
+
# communication stack
|
6
|
+
class SerializeMessage
|
7
|
+
|
8
|
+
# To prevent accidents like trying to send the raw contents of a
|
9
|
+
# JSON file and end up with a Ruby hash on the other side, we designate
|
10
|
+
# messages as being JSON using a simple one character prefix. This way
|
11
|
+
# we don't have to guess if it's JSON or not and will leave non encoded
|
12
|
+
# strings alone. Don't want to dive into anything more complicated unless
|
13
|
+
# it's really necessary (like msgpack).
|
14
|
+
IS_JSON = '⁂'
|
15
|
+
|
16
|
+
# Convert a message into an array of message parts that will
|
17
|
+
# be sent through ZeroMQ.
|
18
|
+
def self.to_zeromq(message)
|
19
|
+
ToZeromq.new(message).perform
|
20
|
+
end
|
21
|
+
|
22
|
+
# Given an array of message parts from ZeroMQ, built up a Pantry::Message
|
23
|
+
# containing the included information.
|
24
|
+
def self.from_zeromq(parts)
|
25
|
+
FromZeromq.new(parts).perform
|
26
|
+
end
|
27
|
+
|
28
|
+
class ToZeromq
|
29
|
+
def initialize(message)
|
30
|
+
@message = message
|
31
|
+
end
|
32
|
+
|
33
|
+
def perform
|
34
|
+
[
|
35
|
+
@message.to || "",
|
36
|
+
@message.metadata.to_json,
|
37
|
+
encode_message_body
|
38
|
+
].flatten.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def encode_message_body
|
44
|
+
@message.body.map do |entry|
|
45
|
+
case entry
|
46
|
+
when Hash, Array
|
47
|
+
"#{IS_JSON}#{entry.to_json}"
|
48
|
+
else
|
49
|
+
entry.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class FromZeromq
|
56
|
+
def initialize(parts)
|
57
|
+
@parts = parts
|
58
|
+
end
|
59
|
+
|
60
|
+
def perform
|
61
|
+
Pantry::Message.new.tap do |message|
|
62
|
+
message.metadata = JSON.parse(@parts[1], symbolize_names: true)
|
63
|
+
message.to = @parts[0]
|
64
|
+
message.body = parse_body_parts(@parts[2..-1])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def parse_body_parts(body_parts)
|
71
|
+
body_parts.map do |raw_part|
|
72
|
+
part = raw_part.force_encoding("UTF-8")
|
73
|
+
|
74
|
+
if part.start_with?(IS_JSON)
|
75
|
+
JSON.parse(part[1..-1], symbolize_names: true) rescue part
|
76
|
+
else
|
77
|
+
raw_part
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|