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