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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 809635436c6083ffcc66ee67660196d6fedd0161
4
- data.tar.gz: fc2d5da13567805306c78c295da4642c09b2b168
3
+ metadata.gz: 6803b3cb787519bd1f861c931211696fa0b35afb
4
+ data.tar.gz: 20537f9462a2a9bdbacb51c2d24d801788b29ed1
5
5
  SHA512:
6
- metadata.gz: 637ca3db4ef68539e09bfe8a4889c23f4d56bcb3a70a81ab9fe1be719c5b7810ec2ed6fee6dae1f8918840b373e4757dc0c0920b4acdce964fc818a4e8656a99
7
- data.tar.gz: ce91068690716748971db0a22f8874da88641d1f5b2b2b40a8951f6033679fe63b45418a71e16fdc004c95e940e1b53445e35afcdd118ffd3329ee1e43aa5c27
6
+ metadata.gz: 83cbd543626b9031e998e4cbf1bd4c0e94af45d0d09e4c689d8c2d1a1d34cb80a4c60ff6079c6cfcef73b420daf579dd6f9b09366629beeb064c91bf393a6371
7
+ data.tar.gz: 086aed862b969022d4cfec48defbe19eacb3e7f949ff4c21da2e0c5d44a9bd81d69e496956ed54af0200acc2e057f80e335782a76ef77cab1cb29e3711b54d41
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -2,11 +2,12 @@
2
2
  module Nutella
3
3
 
4
4
 
5
- # Initializes the nutella library
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(run_id, broker_hostname, component_id)
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
- # Accessors for module instance variables
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
- # Nutella library modules loading
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
- def self.parse_args(args)
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 "Couldn't read run_id and broker address from the command line, impossible to initialize library!"
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 actor name based on the the folder where we are executing
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
@@ -1,3 +1,4 @@
1
+ # Extension to kernel module so we can use nutella as nutella.blah.blah
1
2
  module Kernel
2
3
 
3
4
  def nutella
@@ -1,117 +1,282 @@
1
1
  module Nutella
2
2
 
3
- # This class implements the pub/sub and request/response nutella protocol
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
- # Subscribe to a channel or to a set of channels if using wildcards
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 the channel we are subscribing to, can be wildcard
14
- # @param [Proc] callback a lambda expression that takes as parameters:
15
- # - the received message. Messages that are not JSON are discarded.
16
- # - the channel the message was received on (in case of wildcard subscription)
17
- # - the sender's component_id
18
- # - the sender's resource_id (if set by the sender)
19
- def Net.subscribe (channel, callback)
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? channel
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?(channel)
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
- mqtt_channel.slice!("#{Nutella.run_id}/")
31
- type, payload, component_id, resource_id = extract_fields_from_message mqtt_message
32
- callback.call(payload, mqtt_channel, component_id, resource_id) if type=='publish'
33
- rescue
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, payload, component_id, resource_id = extract_fields_from_message message
42
- callback.call(payload, component_id, resource_id) if type=='publish'
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
- # Subscribe
49
- @subscriptions.push channel
207
+ # Add to subscriptions, save mqtt callback and subscribe
208
+ @subscriptions.push padded_channel
50
209
  @callbacks.push mqtt_cb
51
- Nutella.mqtt.subscribe(new_channel, mqtt_cb)
210
+ Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
52
211
  end
53
212
 
54
213
 
55
- # Unsubscribe from a channel
56
- def Net.unsubscribe(channel)
57
- idx = @subscriptions.index channel
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
- # Pad the channel
225
+ # Fetch the mqtt_callback associated with this channel/subscription
61
226
  mqtt_cb = @callbacks[idx]
62
- new_channel = Nutella.run_id + '/' + channel
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( new_channel, mqtt_cb )
230
+ Nutella.mqtt.unsubscribe( padded_channel, mqtt_cb )
67
231
  end
68
232
 
69
233
 
70
234
  # Publishes a message to a channel
71
- # Message can be:
72
- # empty (equivalent of a GET)
73
- # string (the string will be wrapped into a JSON string automatically. Format: {"payload":"<message>"})
74
- # hash (the hash will be converted into a JSON string automatically)
75
- # json string (the JSON string will be sent as is)
76
- def Net.publish(channel, message=nil)
77
- # Pad the channel
78
- new_channel = Nutella.run_id + '/' + channel
79
- # Publish
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(message)
82
- Nutella.mqtt.publish(new_channel, m)
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
- # Message can be:
91
- # empty (equivalent of a GET)
92
- # string (the string will be wrapped into a JSON string automatically. Format: {"payload":"<message>"})
93
- # hash (the hash will be converted into a JSON string automatically)
94
- # json string (the JSON string will be sent as is)
95
- def Net.sync_request (channel, message=nil)
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
- new_channel = "#{Nutella.run_id}/#{channel}"
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 = extract_id_from_message mqtt_message
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( new_channel, mqtt_cb )
273
+ Nutella.mqtt.unsubscribe( padded_channel, mqtt_cb )
109
274
  end
110
275
  end
111
276
  # Subscribe
112
- Nutella.mqtt.subscribe( new_channel, mqtt_cb )
277
+ Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
113
278
  # Publish message
114
- Nutella.mqtt.publish( new_channel, m )
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
- # Message can be:
123
- # empty (equivalent of a GET)
124
- # string (the string will be wrapped into a JSON string automatically. Format: {"payload":"<message>"})
125
- # hash (the hash will be converted into a JSON string automatically)
126
- # json string (the JSON string will be sent as is)
127
- def Net.async_request (channel, message=nil, callback)
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
- new_channel = "#{Nutella.run_id}/#{channel}"
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 |message|
135
- m_id = extract_id_from_message message
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( new_channel, mqtt_cb )
306
+ Nutella.mqtt.unsubscribe( padded_channel, mqtt_cb )
140
307
  end
141
308
  end
142
309
  # Subscribe
143
- Nutella.mqtt.subscribe( new_channel, mqtt_cb )
310
+ Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
144
311
  # Publish message
145
- Nutella.mqtt.publish( new_channel, m )
312
+ Nutella.mqtt.publish( padded_channel, m )
146
313
  end
147
314
 
148
315
 
149
-
150
- # Handle requests
151
- def Net.handle_requests( channel, callback)
152
- # Pad the channel
153
- new_channel = "#{Nutella.run_id}/#{channel}"
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, payload, component_id, resource_id = extract_fields_from_message request
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
- m = Net.prepare_message_for_response( callback.call( payload, component_id, resource_id ), id )
162
- Nutella.mqtt.publish( new_channel, m )
163
- # Assemble the response and check that it's proper JSON
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(new_channel, mqtt_cb)
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