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 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