nutella_lib 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/nutella_lib/core.rb +65 -11
- data/lib/nutella_lib/ext/kernel.rb +1 -0
- data/lib/nutella_lib/net.rb +264 -140
- data/lib/nutella_lib/net_app.rb +271 -0
- data/lib/nutella_lib/persist.rb +15 -3
- data/lib/nutella_lib.rb +1 -12
- data/lib/simple_mqtt_client/simple_mqtt_client.rb +77 -57
- data/nutella_lib.gemspec +7 -5
- data/test/test_nutella_net.rb +79 -0
- data/test/test_nutella_net_app.rb +104 -0
- data/test/test_simple_mqtt_client.rb +50 -2
- metadata +6 -4
- data/test/test_nutella_lib.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6803b3cb787519bd1f861c931211696fa0b35afb
|
4
|
+
data.tar.gz: 20537f9462a2a9bdbacb51c2d24d801788b29ed1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83cbd543626b9031e998e4cbf1bd4c0e94af45d0d09e4c689d8c2d1a1d34cb80a4c60ff6079c6cfcef73b420daf579dd6f9b09366629beeb064c91bf393a6371
|
7
|
+
data.tar.gz: 086aed862b969022d4cfec48defbe19eacb3e7f949ff4c21da2e0c5d44a9bd81d69e496956ed54af0200acc2e057f80e335782a76ef77cab1cb29e3711b54d41
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/nutella_lib/core.rb
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
module Nutella
|
3
3
|
|
4
4
|
|
5
|
-
# Initializes
|
5
|
+
# Initializes this component as a run component
|
6
6
|
# @param [String] run_id
|
7
7
|
# @param [String] broker_hostname
|
8
8
|
# @param [String] component_id
|
9
|
-
def self.init(
|
9
|
+
def self.init( broker_hostname, app_id, run_id, component_id )
|
10
|
+
@app_id = app_id
|
10
11
|
@run_id = run_id
|
11
12
|
@component_id = component_id
|
12
13
|
@resource_id = nil
|
@@ -14,33 +15,84 @@ module Nutella
|
|
14
15
|
end
|
15
16
|
|
16
17
|
|
17
|
-
#
|
18
|
+
# Initializes this component as an application component
|
19
|
+
# @param [String] broker_hostname
|
20
|
+
# @param [String] component_id
|
21
|
+
def self.init_as_app_component( broker_hostname, app_id, component_id )
|
22
|
+
@app_id = app_id
|
23
|
+
@run_id = nil
|
24
|
+
@component_id = component_id
|
25
|
+
@resource_id = nil
|
26
|
+
@mqtt = SimpleMQTTClient.new broker_hostname
|
27
|
+
# Fetch the `run_id`s list for this application and subscribe to its updates
|
28
|
+
@app_runs_list = net.app.sync_request('app_runs_list')
|
29
|
+
net.app.subscribe('app_runs_list', lambda {|message, _| @app_runs_list = message })
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Accessors for app_id
|
34
|
+
def self.app_id; @app_id end
|
35
|
+
|
36
|
+
# Accessors for run_id
|
18
37
|
def self.run_id; @run_id end
|
19
|
-
def self.component_id; @component_id end
|
20
|
-
def self.resource_id; @resource_id end
|
21
|
-
def self.mqtt; @mqtt end
|
22
38
|
|
39
|
+
# Accessors for mqtt client
|
40
|
+
def self.mqtt;
|
41
|
+
raise 'Nutella has not been initialized: you need to call the proper init method before you can start using nutella' if @mqtt.nil?
|
42
|
+
@mqtt
|
43
|
+
end
|
23
44
|
|
45
|
+
# Accessors for component_id
|
46
|
+
def self.component_id;
|
47
|
+
raise 'Nutella has not been initialized: you need to call the proper init method before you can start using nutella' if @component_id.nil?
|
48
|
+
@component_id
|
49
|
+
end
|
50
|
+
|
51
|
+
# Accessors for resource_id
|
52
|
+
def self.resource_id; @resource_id end
|
24
53
|
|
25
|
-
#
|
54
|
+
# Accessor for runs list
|
55
|
+
def self.app_runs_list; @app_runs_list end
|
56
|
+
|
57
|
+
# Accessor for the net module
|
26
58
|
def self.net; Nutella::Net end
|
59
|
+
|
60
|
+
# Accessor for the persist module
|
27
61
|
def self.persist; Nutella::Persist end
|
28
62
|
|
29
63
|
|
30
64
|
# Utility functions
|
31
65
|
|
32
66
|
|
33
|
-
# Parse command line arguments
|
34
|
-
|
67
|
+
# Parse command line arguments for run level components
|
68
|
+
#
|
69
|
+
# @param [Array] args command line arguments array
|
70
|
+
# @return [String, String, String] broker, app_id and run_id
|
71
|
+
def self.parse_run_component_args(args)
|
72
|
+
if args.length < 3
|
73
|
+
STDERR.puts 'Couldn\'t read broker address, app_id and run_id from the command line, impossible to initialize component!'
|
74
|
+
return
|
75
|
+
end
|
76
|
+
return args[0], args[1], args[2]
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Parse command line arguments for app level components
|
81
|
+
#
|
82
|
+
# @param [Array] args command line arguments array
|
83
|
+
# @return [String, String] broker and app_id
|
84
|
+
def self.parse_app_component_args(args)
|
35
85
|
if args.length < 2
|
36
|
-
STDERR.puts
|
86
|
+
STDERR.puts 'Couldn\'t read broker address and app_id from the command line, impossible to initialize component!'
|
37
87
|
return
|
38
88
|
end
|
39
89
|
return args[0], args[1]
|
40
90
|
end
|
41
91
|
|
42
92
|
|
43
|
-
# Extracts the
|
93
|
+
# Extracts the component name from the folder where the code for this component is located
|
94
|
+
#
|
95
|
+
# @return [String] the component name
|
44
96
|
def self.extract_component_id
|
45
97
|
path = Dir.pwd
|
46
98
|
path[path.rindex('/')+1..path.length-1]
|
@@ -48,6 +100,8 @@ module Nutella
|
|
48
100
|
|
49
101
|
|
50
102
|
# Sets the resource id
|
103
|
+
#
|
104
|
+
# @param [String] resource_id the resource id (i.e. the particular instance of this component)
|
51
105
|
def self.set_resource_id( resource_id )
|
52
106
|
@resource_id = resource_id
|
53
107
|
end
|
data/lib/nutella_lib/net.rb
CHANGED
@@ -1,117 +1,282 @@
|
|
1
1
|
module Nutella
|
2
2
|
|
3
|
-
# This
|
4
|
-
# @author Alessandro Gnoli <tebemis@gmail.com>
|
3
|
+
# This module implements the pub/sub and request/response APIs at the run level
|
5
4
|
module Net
|
6
5
|
|
7
6
|
# Store the subscriptions and the relative callbacks
|
8
7
|
@subscriptions = []
|
9
8
|
@callbacks = []
|
10
9
|
|
11
|
-
#
|
10
|
+
# Provides access to the net.app sub-module
|
11
|
+
def Net.app; Nutella::Net::App end
|
12
|
+
|
13
|
+
# Provides access to the subscriptions
|
14
|
+
def Net.subscriptions; @subscriptions end
|
15
|
+
|
16
|
+
# Provides access to callbacks
|
17
|
+
def Net.callbacks; @callbacks end
|
18
|
+
|
19
|
+
|
20
|
+
# Subscribes to a channel or to a set of channels.
|
21
|
+
#
|
22
|
+
# @param [String] channel the channel or filter we are subscribing to. Can contain wildcard(s)
|
23
|
+
# @param [Proc] callback a lambda expression that is fired whenever a message is received.
|
24
|
+
# The passed callback takes the following parameters:
|
25
|
+
# - [String] message: the received message. Messages that are not JSON are discarded.
|
26
|
+
# - [String] channel: the channel the message was received on (optional, only for wildcard subscriptions)
|
27
|
+
# - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
|
28
|
+
def Net.subscribe( channel, callback )
|
29
|
+
subscribe_to( channel, callback, Nutella.app_id, Nutella.run_id)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Un-subscribes from a channel
|
34
|
+
#
|
35
|
+
# @param [String] channel we want to unsubscribe from. Can contain wildcard(s).
|
36
|
+
def Net.unsubscribe( channel )
|
37
|
+
unsubscribe_to( channel, Nutella.app_id, Nutella.run_id)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Publishes a message to a channel
|
42
|
+
#
|
43
|
+
# @param [String] channel we want to publish the message to. *CANNOT* contain wildcard(s)!
|
44
|
+
# @param [Object] message the message we are publishing. This can be,
|
45
|
+
# nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
|
46
|
+
def Net.publish( channel, message=nil )
|
47
|
+
publish_to( channel, message, Nutella.app_id, Nutella.run_id)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Performs a synchronous request.
|
52
|
+
#
|
53
|
+
# @param [String] channel we want to make the request to. *CANNOT* contain wildcard(s)!
|
54
|
+
# @param [Object] message the body of request. This can be,
|
55
|
+
# nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
|
56
|
+
def Net.sync_request( channel, message=nil )
|
57
|
+
sync_request_to(channel, message, Nutella.app_id, Nutella.run_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Performs an asynchronous request
|
62
|
+
#
|
63
|
+
# @param [String] channel we want to make the request to. *CANNOT* contain wildcard(s)!
|
64
|
+
# @param [Object] message the body of request. This can be,
|
65
|
+
# nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
|
66
|
+
# @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response).
|
67
|
+
def Net.async_request( channel, message=nil, callback )
|
68
|
+
async_request_to(channel, message, callback, Nutella.app_id, Nutella.run_id)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Handles requests on a certain channel
|
12
73
|
#
|
13
|
-
# @param [String] channel
|
14
|
-
# @param [Proc] callback a lambda expression that
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
74
|
+
# @param [String] channel we want to listen for requests on. Can contain wildcard(s).
|
75
|
+
# @param [Proc] callback a lambda expression that is fired whenever a message is received.
|
76
|
+
# The passed callback takes the following parameters:
|
77
|
+
# - [String] the received message (payload). Messages that are not JSON are discarded.
|
78
|
+
# - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
|
79
|
+
# - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
|
80
|
+
#
|
81
|
+
def Net.handle_requests( channel, callback )
|
82
|
+
handle_requests_on(channel, callback, Nutella.app_id, Nutella.run_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Listens for incoming messages. All this function
|
87
|
+
# does is to put the thread to sleep and wait for something to
|
88
|
+
# happen over the network to wake up.
|
89
|
+
def Net.listen
|
90
|
+
begin
|
91
|
+
sleep
|
92
|
+
rescue Interrupt
|
93
|
+
# Simply returns once interrupted
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
|
101
|
+
def Net.extract_fields_from_message(message)
|
102
|
+
mh = JSON.parse message
|
103
|
+
return mh['type'], mh['from'], mh['payload'], mh['id']
|
104
|
+
end
|
105
|
+
|
106
|
+
def Net.pad_channel( channel, app_id, run_id )
|
107
|
+
raise 'If the run_id is specified, app_id needs to also be specified' if (!run_id.nil? && app_id.nil?)
|
108
|
+
return "/nutella/#{channel}" if (app_id.nil? && run_id.nil?)
|
109
|
+
return "/nutella/apps/#{app_id}/#{channel}" if (!app_id.nil? && run_id.nil?)
|
110
|
+
"/nutella/apps/#{app_id}/runs/#{run_id}/#{channel}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def Net.un_pad_channel( channel, app_id, run_id )
|
114
|
+
raise 'If the run_id is specified, app_id needs to also be specified' if (!run_id.nil? && app_id.nil?)
|
115
|
+
return channel.gsub('/nutella/', '') if (app_id.nil? && run_id.nil?)
|
116
|
+
return channel.gsub("/nutella/apps/#{app_id}/", '') if (!app_id.nil? && run_id.nil?)
|
117
|
+
channel.gsub("/nutella/apps/#{app_id}/runs/#{run_id}/", '')
|
118
|
+
end
|
119
|
+
|
120
|
+
def Net.assemble_from
|
121
|
+
from = Hash.new
|
122
|
+
if Nutella.run_id.nil?
|
123
|
+
from['type'] = 'app'
|
124
|
+
else
|
125
|
+
from['type'] = 'run'
|
126
|
+
from['run_id'] = Nutella.run_id
|
127
|
+
end
|
128
|
+
from['app_id'] = Nutella.app_id
|
129
|
+
from['component_id'] = Nutella.component_id
|
130
|
+
from['resource_id'] = Nutella.resource_id unless Nutella.resource_id.nil?
|
131
|
+
from
|
132
|
+
end
|
133
|
+
|
134
|
+
def Net.prepare_message_for_publish( message )
|
135
|
+
if message.nil?
|
136
|
+
return {type: 'publish', from: assemble_from}.to_json
|
137
|
+
end
|
138
|
+
{type: 'publish', from: assemble_from, payload: message}.to_json
|
139
|
+
end
|
140
|
+
|
141
|
+
def Net.prepare_message_for_request( message )
|
142
|
+
id = message.hash + Random.rand(100)
|
143
|
+
if message.nil?
|
144
|
+
return {id: id, type: 'request', from: assemble_from}.to_json, id
|
145
|
+
end
|
146
|
+
return {id: id, type: 'request', from: assemble_from, payload: message}.to_json, id
|
147
|
+
end
|
148
|
+
|
149
|
+
def Net.prepare_message_for_response( message, id )
|
150
|
+
if message.nil?
|
151
|
+
return {id: id, type: 'response', from: assemble_from}.to_json
|
152
|
+
end
|
153
|
+
{id: id, type: 'response', from: assemble_from, payload: message}.to_json
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# Subscribes to a channel or to a set of channels.
|
158
|
+
#
|
159
|
+
# @param [String] channel the channel or filter we are subscribing to. Can contain wildcard(s)
|
160
|
+
# @param [Proc] callback a lambda expression that is fired whenever a message is received.
|
161
|
+
# The passed callback takes the following parameters:
|
162
|
+
# - [String] message: the received message. Messages that are not JSON are discarded.
|
163
|
+
# - [String] channel: the channel the message was received on (optional, only for wildcard subscriptions)
|
164
|
+
# - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
|
165
|
+
# @param [String] app_id used to pad channels
|
166
|
+
# @param [String] run_id used to pad channels
|
167
|
+
def Net.subscribe_to( channel, callback, app_id, run_id )
|
168
|
+
# Check the passed callback has the right number of arguments
|
169
|
+
if Nutella.mqtt.is_channel_wildcard?(channel)
|
170
|
+
raise 'You need to pass a callback with 3 parameters (message, channel, from) when subscribing to a wildcard channel!' if callback.parameters.length!=3
|
171
|
+
else
|
172
|
+
raise 'You need to pass a callback with 2 parameters (message, from) when subscribing to a channel!' if callback.parameters.length!=2
|
173
|
+
end
|
174
|
+
# Pad channel
|
175
|
+
padded_channel = pad_channel(channel, app_id, run_id)
|
20
176
|
# Maintain unique subscriptions
|
21
|
-
raise 'You can`t subscribe twice to the same channel`' if @subscriptions.include?
|
22
|
-
# Pad the channel
|
23
|
-
new_channel = "#{Nutella.run_id}/#{channel}"
|
177
|
+
raise 'You can`t subscribe twice to the same channel`' if @subscriptions.include? padded_channel
|
24
178
|
# Depending on what type of channel we are subscribing to (wildcard or simple)
|
25
179
|
# register a different kind of callback
|
26
|
-
if Nutella.mqtt.is_channel_wildcard?(
|
180
|
+
if Nutella.mqtt.is_channel_wildcard?(padded_channel)
|
27
181
|
mqtt_cb = lambda do |mqtt_message, mqtt_channel|
|
28
|
-
# Make sure the message is JSON, if not drop the message
|
29
182
|
begin
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
183
|
+
type, from, payload, _ = extract_fields_from_message mqtt_message
|
184
|
+
callback.call(payload, un_pad_channel(mqtt_channel, app_id, run_id), from) if type=='publish'
|
185
|
+
rescue JSON::ParserError
|
186
|
+
# Make sure the message is JSON, if not drop the message
|
34
187
|
return
|
188
|
+
rescue
|
189
|
+
# Check the passed callback has the right number of arguments
|
190
|
+
STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'payload', 'channel' and 'from'"
|
35
191
|
end
|
36
192
|
end
|
37
193
|
else
|
38
194
|
mqtt_cb = lambda do |message|
|
39
|
-
# Make sure the message is JSON, if not drop the message
|
40
195
|
begin
|
41
|
-
type,
|
42
|
-
callback.call(payload,
|
43
|
-
rescue
|
196
|
+
type, from, payload, _ = extract_fields_from_message message
|
197
|
+
callback.call(payload, from) if type=='publish'
|
198
|
+
rescue JSON::ParserError
|
199
|
+
# Make sure the message is JSON, if not drop the message
|
44
200
|
return
|
201
|
+
rescue
|
202
|
+
# Check the passed callback has the right number of arguments
|
203
|
+
STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'payload' and 'from'"
|
45
204
|
end
|
46
205
|
end
|
47
206
|
end
|
48
|
-
#
|
49
|
-
@subscriptions.push
|
207
|
+
# Add to subscriptions, save mqtt callback and subscribe
|
208
|
+
@subscriptions.push padded_channel
|
50
209
|
@callbacks.push mqtt_cb
|
51
|
-
Nutella.mqtt.subscribe(
|
210
|
+
Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
|
52
211
|
end
|
53
212
|
|
54
213
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
214
|
+
# Un-subscribes from a channel
|
215
|
+
#
|
216
|
+
# @param [String] channel we want to unsubscribe from. Can contain wildcard(s).
|
217
|
+
# @param [String] app_id used to pad channels
|
218
|
+
# @param [String] run_id used to pad channels
|
219
|
+
def Net.unsubscribe_to( channel, app_id, run_id )
|
220
|
+
# Pad channel
|
221
|
+
padded_channel = pad_channel(channel, app_id, run_id)
|
222
|
+
idx = @subscriptions.index padded_channel
|
58
223
|
# If we are not subscribed to this channel, return (no error is given)
|
59
224
|
return if idx.nil?
|
60
|
-
#
|
225
|
+
# Fetch the mqtt_callback associated with this channel/subscription
|
61
226
|
mqtt_cb = @callbacks[idx]
|
62
|
-
|
63
|
-
# Unsubscribe
|
227
|
+
# Remove from subscriptions, callbacks and unsubscribe
|
64
228
|
@subscriptions.delete_at idx
|
65
229
|
@callbacks.delete_at idx
|
66
|
-
Nutella.mqtt.unsubscribe(
|
230
|
+
Nutella.mqtt.unsubscribe( padded_channel, mqtt_cb )
|
67
231
|
end
|
68
232
|
|
69
233
|
|
70
234
|
# Publishes a message to a channel
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
235
|
+
#
|
236
|
+
# @param [String] channel we want to publish the message to. *CANNOT* contain wildcard(s)!
|
237
|
+
# @param [Object] message the message we are publishing. This can be,
|
238
|
+
# nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
|
239
|
+
# @param [String] app_id used to pad channels
|
240
|
+
# @param [String] run_id used to pad channels
|
241
|
+
def Net.publish_to( channel, message=nil, app_id, run_id )
|
242
|
+
# Pad channel
|
243
|
+
padded_channel = pad_channel(channel, app_id, run_id)
|
244
|
+
# Throw exception if trying to publish something that is not JSON
|
80
245
|
begin
|
81
|
-
m = Net.prepare_message_for_publish
|
82
|
-
Nutella.mqtt.publish(
|
246
|
+
m = Net.prepare_message_for_publish message
|
247
|
+
Nutella.mqtt.publish( padded_channel, m )
|
83
248
|
rescue
|
84
|
-
STDERR.puts
|
249
|
+
STDERR.puts 'Error: you are trying to publish something that is not JSON'
|
85
250
|
end
|
86
251
|
end
|
87
252
|
|
88
253
|
|
89
|
-
# Performs a synchronous request
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
254
|
+
# Performs a synchronous request.
|
255
|
+
#
|
256
|
+
# @param [String] channel we want to make the request to. *CANNOT* contain wildcard(s)!
|
257
|
+
# @param [Object] message the body of request. This can be,
|
258
|
+
# nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
|
259
|
+
# @param [String] app_id used to pad channels
|
260
|
+
# @param [String] run_id used to pad channels
|
261
|
+
def Net.sync_request_to( channel, message=nil, app_id, run_id )
|
96
262
|
# Pad channel
|
97
|
-
|
263
|
+
padded_channel = pad_channel(channel, app_id, run_id)
|
98
264
|
# Prepare message
|
99
265
|
m, id = prepare_message_for_request message
|
100
266
|
# Initialize response
|
101
267
|
response = nil
|
102
268
|
# Prepare callback
|
103
269
|
mqtt_cb = lambda do |mqtt_message|
|
104
|
-
m_id =
|
105
|
-
type, payload = extract_fields_from_response mqtt_message
|
270
|
+
type, _, payload, m_id = extract_fields_from_message mqtt_message
|
106
271
|
if m_id==id && type=='response'
|
107
272
|
response = payload
|
108
|
-
Nutella.mqtt.unsubscribe(
|
273
|
+
Nutella.mqtt.unsubscribe( padded_channel, mqtt_cb )
|
109
274
|
end
|
110
275
|
end
|
111
276
|
# Subscribe
|
112
|
-
Nutella.mqtt.subscribe(
|
277
|
+
Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
|
113
278
|
# Publish message
|
114
|
-
Nutella.mqtt.publish(
|
279
|
+
Nutella.mqtt.publish( padded_channel, m )
|
115
280
|
# Wait for the response to come back
|
116
281
|
sleep(0.1) while response.nil?
|
117
282
|
response
|
@@ -119,110 +284,69 @@ module Nutella
|
|
119
284
|
|
120
285
|
|
121
286
|
# Performs an asynchronous request
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
|
287
|
+
#
|
288
|
+
# @param [String] channel we want to make the request to. *CANNOT* contain wildcard(s)!
|
289
|
+
# @param [Object] message the body of request. This can be,
|
290
|
+
# nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
|
291
|
+
# @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response).
|
292
|
+
# @param [String] app_id used to pad channels
|
293
|
+
# @param [String] run_id used to pad channels
|
294
|
+
def Net.async_request_to( channel, message=nil, callback, app_id, run_id )
|
295
|
+
# Check the passed callback has the right number of arguments
|
296
|
+
raise 'You need to pass a callback with 1 parameter (response) when making an asynchronous request!' if callback.parameters.length!=1
|
128
297
|
# Pad channel
|
129
|
-
|
298
|
+
padded_channel = pad_channel(channel, app_id, run_id)
|
130
299
|
# Prepare message
|
131
300
|
m, id = prepare_message_for_request message
|
132
|
-
# Initialize response
|
133
301
|
# Prepare callback
|
134
|
-
mqtt_cb = lambda do |
|
135
|
-
m_id =
|
136
|
-
type, payload = extract_fields_from_response message
|
302
|
+
mqtt_cb = lambda do |mqtt_message|
|
303
|
+
type, _, payload, m_id = extract_fields_from_message mqtt_message
|
137
304
|
if m_id==id && type=='response'
|
138
305
|
callback.call(payload)
|
139
|
-
Nutella.mqtt.unsubscribe(
|
306
|
+
Nutella.mqtt.unsubscribe( padded_channel, mqtt_cb )
|
140
307
|
end
|
141
308
|
end
|
142
309
|
# Subscribe
|
143
|
-
Nutella.mqtt.subscribe(
|
310
|
+
Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
|
144
311
|
# Publish message
|
145
|
-
Nutella.mqtt.publish(
|
312
|
+
Nutella.mqtt.publish( padded_channel, m )
|
146
313
|
end
|
147
314
|
|
148
315
|
|
149
|
-
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
316
|
+
# Handles requests on a certain channel
|
317
|
+
#
|
318
|
+
# @param [String] channel we want to listen for requests on. Can contain wildcard(s).
|
319
|
+
# @param [Proc] callback a lambda expression that is fired whenever a message is received.
|
320
|
+
# The passed callback takes the following parameters:
|
321
|
+
# - [String] the received message (payload). Messages that are not JSON are discarded.
|
322
|
+
# - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
|
323
|
+
# - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
|
324
|
+
# @param [String] app_id used to pad channels
|
325
|
+
# @param [String] run_id used to pad channels
|
326
|
+
def Net.handle_requests_on( channel, callback, app_id, run_id )
|
327
|
+
# Check the passed callback has the right number of arguments
|
328
|
+
raise 'You need to pass a callback with 2 parameter (request, from) when handling requests!' if callback.parameters.length!=2
|
329
|
+
# Pad channel
|
330
|
+
padded_channel = pad_channel(channel, app_id, run_id)
|
154
331
|
mqtt_cb = lambda do |request|
|
155
332
|
begin
|
156
333
|
# Extract nutella fields
|
157
|
-
type,
|
158
|
-
id = extract_id_from_message request
|
334
|
+
type, from, payload, id = extract_fields_from_message request
|
159
335
|
# Only handle requests that have proper id set
|
160
336
|
return if type!='request' || id.nil?
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
rescue
|
337
|
+
# Execute callback and send response
|
338
|
+
m = Net.prepare_message_for_response( callback.call( payload, from), id )
|
339
|
+
Nutella.mqtt.publish( padded_channel, m )
|
340
|
+
rescue JSON::ParserError
|
341
|
+
# Make sure that request contains JSON, if not drop the message
|
165
342
|
return
|
343
|
+
rescue
|
344
|
+
# Check that the passed callback has the right number of arguments
|
345
|
+
STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'payload' and 'from'"
|
166
346
|
end
|
167
347
|
end
|
168
348
|
# Subscribe to the channel
|
169
|
-
Nutella.mqtt.subscribe(
|
349
|
+
Nutella.mqtt.subscribe(padded_channel, mqtt_cb)
|
170
350
|
end
|
171
|
-
|
172
|
-
|
173
|
-
# Listens for incoming messages
|
174
|
-
def Net.listen
|
175
|
-
begin
|
176
|
-
sleep
|
177
|
-
rescue Interrupt
|
178
|
-
# Simply returns
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
|
183
|
-
private
|
184
|
-
|
185
|
-
def Net.extract_fields_from_message(message)
|
186
|
-
mh = JSON.parse(message)
|
187
|
-
from = mh['from'].split('/')
|
188
|
-
r_id = from.length==1 ? nil : from[1]
|
189
|
-
return mh['type'], mh['payload'], from[0], r_id
|
190
|
-
end
|
191
|
-
|
192
|
-
def Net.extract_id_from_message( message )
|
193
|
-
mh = JSON.parse(message)
|
194
|
-
mh['id']
|
195
|
-
end
|
196
|
-
|
197
|
-
def Net.extract_fields_from_response( message )
|
198
|
-
mh = JSON.parse(message)
|
199
|
-
return mh['type'], mh['payload']
|
200
|
-
end
|
201
|
-
|
202
|
-
def Net.prepare_message_for_publish( message )
|
203
|
-
from = Nutella.resource_id.nil? ? Nutella.component_id : "#{Nutella.component_id}/#{Nutella.resource_id}"
|
204
|
-
if message.nil?
|
205
|
-
return {type: 'publish', from: from}.to_json
|
206
|
-
end
|
207
|
-
{type: 'publish', from: from, payload: message}.to_json
|
208
|
-
end
|
209
|
-
|
210
|
-
def Net.prepare_message_for_response( message, id )
|
211
|
-
from = Nutella.resource_id.nil? ? Nutella.component_id : "#{Nutella.component_id}/#{Nutella.resource_id}"
|
212
|
-
if message.nil?
|
213
|
-
return {id: id, type: 'response', from: from}.to_json
|
214
|
-
end
|
215
|
-
{id: id, type: 'response', from: from, payload: message}.to_json
|
216
|
-
end
|
217
|
-
|
218
|
-
def Net.prepare_message_for_request( message )
|
219
|
-
from = Nutella.resource_id.nil? ? Nutella.component_id : "#{Nutella.component_id}/#{Nutella.resource_id}"
|
220
|
-
if message.nil?
|
221
|
-
return {id: message.hash, type: 'request', from: from}.to_json, message.hash
|
222
|
-
end
|
223
|
-
return {id: message.hash, type: 'request', from: from, payload: message}.to_json, message.hash
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
351
|
end
|
228
352
|
end
|