spacebunny 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +190 -0
- data/Rakefile +6 -0
- data/assets/logo.png +0 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/examples/device/auto_config_publish.rb +87 -0
- data/examples/device/manual_config.rb +83 -0
- data/examples/device/publish_with_confirm.rb +85 -0
- data/examples/device/receive_messages.rb +109 -0
- data/examples/device/tls_connection.rb +65 -0
- data/examples/live_stream/receive_messages.rb +128 -0
- data/examples/live_stream/tls_connection.rb +22 -0
- data/lib/spacebunny.rb +23 -0
- data/lib/spacebunny/device/amqp.rb +158 -0
- data/lib/spacebunny/device/base.rb +229 -0
- data/lib/spacebunny/device/message.rb +56 -0
- data/lib/spacebunny/endpoint_connection.rb +128 -0
- data/lib/spacebunny/exceptions.rb +143 -0
- data/lib/spacebunny/live_stream/amqp.rb +124 -0
- data/lib/spacebunny/live_stream/base.rb +198 -0
- data/lib/spacebunny/live_stream/message.rb +38 -0
- data/lib/spacebunny/logger.rb +35 -0
- data/lib/spacebunny/utils.rb +185 -0
- data/lib/spacebunny/version.rb +3 -0
- data/spacebunny.gemspec +27 -0
- metadata +157 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spacebunny'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Prerequisites: you have created a device through the SpaceBunny's web interface. You also have a 'data' channel (name
|
5
|
+
# is not mandatory, but we'll use this for our example). You have also enabled 'data' channel for the device. See our
|
6
|
+
# Getting Started [link] for a quick introduction to SpaceBunny's base concepts.
|
7
|
+
|
8
|
+
# Once everything is set up get your device's API key from SpaceBunny's web application: on the web interface,
|
9
|
+
# go to devices section and create or pick an existing device. Click on the 'SHOW CONFIGURATION' link, copy the API key
|
10
|
+
# and substitute it here:
|
11
|
+
|
12
|
+
device_key = 'your_awesome_device_key'
|
13
|
+
|
14
|
+
# Let's instantiate a SpaceBunny client, providing the device's API key, that's the fastest and simplest method
|
15
|
+
# to create a new client. If, for some reason, you need to customize the settings, take a look at
|
16
|
+
# examples/manual_config.rb for an example of connection settings customization.
|
17
|
+
|
18
|
+
dev = Spacebunny::Device.new key
|
19
|
+
|
20
|
+
# An equivalent method for providing the API key is through options: Spacebunny::Device.new(key: key)
|
21
|
+
|
22
|
+
# We need to call 'connect' in order to open the communication with SpaceBunny platform
|
23
|
+
|
24
|
+
dev.connect
|
25
|
+
|
26
|
+
# At this point the SDK is auto-configured and ready to use.
|
27
|
+
# Configurations are automatically lazy-fetched by the SDK itself when calling 'connect'.
|
28
|
+
|
29
|
+
# PUBLISHING MESSAGES WITH CONFIRM
|
30
|
+
|
31
|
+
# As said in the prerequisites, we'll assume that 'data' channel is enabled for your device.
|
32
|
+
# If you're in doubt, please check that this is true through SpaceBunny's web interface, by clicking on the device
|
33
|
+
# 'edit' (pencil icon) and verifying that 'data' channel is present and enabled for this device. Take a look at Getting
|
34
|
+
# Started [link] for a quick introduction to SpaceBunny's base concepts.
|
35
|
+
|
36
|
+
# Let's publish, for instance, some JSON. Payload can be everything you want, SpaceBunny does not impose any constraint
|
37
|
+
# on format or content of payload.
|
38
|
+
|
39
|
+
# Publish one message every second for a minute.
|
40
|
+
count = 0
|
41
|
+
5.times do
|
42
|
+
# Generate some random data
|
43
|
+
payload = {
|
44
|
+
count: count,
|
45
|
+
greetings: 'Hello, World!',
|
46
|
+
temp: rand(20.0..25.0),
|
47
|
+
foo: rand(100..200)
|
48
|
+
}.to_json
|
49
|
+
|
50
|
+
# Hint: the channel name can also be a string e.g: 'data'
|
51
|
+
dev.publish :data, payload, with_confirm: true
|
52
|
+
|
53
|
+
# 'publish' takes two mandatory arguments (channel's name and payload) and a variety of options: one of these options is
|
54
|
+
# the 'with_confirm' flag: when set to true this requires SpaceBunny's platform to confirm the receipt of the message.
|
55
|
+
# This is useful when message delivery assurance is mandatory for your use case.
|
56
|
+
# Take a look at SDK's documentation for further details.
|
57
|
+
|
58
|
+
# Give some feedback on what has been published
|
59
|
+
puts "Published #{payload}"
|
60
|
+
|
61
|
+
# Take a nap...
|
62
|
+
sleep 1
|
63
|
+
# Update counter
|
64
|
+
count += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
# Wait for publish confirmations: wait for SpaceBunny to confirm that all published messages have been
|
68
|
+
# accepted.
|
69
|
+
result = dev.wait_for_publish_confirms
|
70
|
+
|
71
|
+
# 'wait_for_publish_confirms' waits for every message published, regardless the channel, blocking execution until
|
72
|
+
# every confirm (or nack) has been received. 'wait_for_publish_confirms' returns an Hash whose keys are the channels'
|
73
|
+
# names on which you have published some message.
|
74
|
+
# For instance:
|
75
|
+
|
76
|
+
result.each do |channel, status|
|
77
|
+
# If result is false, some message has been nacked. A message may be nacked by SpaceBunny's platform if, for some
|
78
|
+
# reason, it cannot take responsibility for the message
|
79
|
+
unless status[:all_confirmed]
|
80
|
+
# do something with nacked messages
|
81
|
+
status[:nacked_set].each do |nacked_message|
|
82
|
+
# do something with nacked message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spacebunny'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Prerequisites: you have created a device through the SpaceBunny's web interface. See our Getting Started [link]
|
5
|
+
# for a quick introduction to SpaceBunny's base concepts.
|
6
|
+
|
7
|
+
# Once everything is set up get your device's API key from SpaceBunny's web application: on the web interface,
|
8
|
+
# go to devices section and create or pick an existing device. Click on the 'SHOW CONFIGURATION' link, copy the API key
|
9
|
+
# and substitute it here:
|
10
|
+
|
11
|
+
key = 'your_awesome_device_key'
|
12
|
+
|
13
|
+
# Let's instantiate a SpaceBunny (AMQP by default) client, providing the device's API key, that's the fastest and simplest method
|
14
|
+
# to create a new client. If, for some reason, you need to customize the settings, take a look at
|
15
|
+
# examples/manual_config.rb for an example of connection settings customization.
|
16
|
+
|
17
|
+
dev = Spacebunny::Device.new key
|
18
|
+
|
19
|
+
# An equivalent method for providing the API key is through options: Spacebunny::Device.new(key: key)
|
20
|
+
|
21
|
+
# We need to call 'connect' in order to open the communication with SpaceBunny platform
|
22
|
+
|
23
|
+
dev.connect
|
24
|
+
|
25
|
+
# At this point the SDK is auto-configured and ready to use.
|
26
|
+
# Configurations are automatically lazy-fetched by the SDK itself when calling 'connect'.
|
27
|
+
|
28
|
+
|
29
|
+
# RECEIVING MESSAGES
|
30
|
+
|
31
|
+
puts "Waiting for messages. Publish some from SpaceBunny's web interface to try this out:"
|
32
|
+
|
33
|
+
# Receiving messages is trivial:
|
34
|
+
|
35
|
+
dev.inbox(wait: true, ack: :auto) do |message|
|
36
|
+
puts "Received: #{message.payload}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# A Ruby block must be supplied to the 'inbox' method. It yields a Device::Message object containing
|
40
|
+
# all the useful data:
|
41
|
+
#
|
42
|
+
# dev.inbox do |message|
|
43
|
+
# puts "payload: #{message.payload}"
|
44
|
+
#
|
45
|
+
# # metadata that can be accessed as an Hash.
|
46
|
+
# # metadata[:headers] contains the message's headers for instance
|
47
|
+
# puts "metadata: #{message.metadata}"
|
48
|
+
#
|
49
|
+
# # sender_id (id of the device that sent the message)
|
50
|
+
# puts "sender_id: #{message.sender_id}"
|
51
|
+
#
|
52
|
+
# # channel name i.e. the name of the channel on which the message has been published
|
53
|
+
# puts "channel_name: #{message.channel_name}"
|
54
|
+
#
|
55
|
+
# # Connection and other low level info
|
56
|
+
# puts "delivery_info: #{message.delivery_info}"
|
57
|
+
# end
|
58
|
+
|
59
|
+
# inbox method's options:
|
60
|
+
# 'wait' (default false) causes the script to wait forever on the receive block. This is useful
|
61
|
+
# for 'read-only' scripts like workers or similar.
|
62
|
+
# 'ack' option can have two values: :manual (default) or :auto. When :manual you are responsible to ack the messages,
|
63
|
+
# for instance:
|
64
|
+
#
|
65
|
+
# dev.inbox(block: true, ack: :manual) do |message|
|
66
|
+
# puts "Received: #{message.payload}"
|
67
|
+
# message.ack
|
68
|
+
# end
|
69
|
+
# When 'ack' is :auto then the SDK will automatically ack the messages when the code provided in the inbox block
|
70
|
+
# has terminated execution. This behaviour is useful in cases in which the code executed inside the inbox block
|
71
|
+
# will always lead to an acked message (no matter the operations' result).
|
72
|
+
# If your code can lead to errors and, for instance, the message must be reprocessed, you must use :manual ack
|
73
|
+
# and manually call 'ack' as seen in the example above.
|
74
|
+
# Call 'nack' in case the message needs to be reprocessed and it will remain on SpaceBunny until successfully
|
75
|
+
# acked. For instance:
|
76
|
+
|
77
|
+
# dev.inbox(block: true, ack: :manual) do |message|
|
78
|
+
# puts message.payload
|
79
|
+
#
|
80
|
+
# begin
|
81
|
+
# # Do something nasty...
|
82
|
+
# raise
|
83
|
+
#
|
84
|
+
# # If everything is ok, ack
|
85
|
+
# # (note that this code will never be reached)
|
86
|
+
# message.ack
|
87
|
+
# rescue Exception => e
|
88
|
+
# message.nack
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
|
92
|
+
# ack options are:
|
93
|
+
# multiple: false ack multiple messages at once. Ack all the unacked
|
94
|
+
# messages up to the current one
|
95
|
+
|
96
|
+
# nack options are:
|
97
|
+
# multiple: false nack multiple messages at once. Nack all the messages up
|
98
|
+
# to the current one
|
99
|
+
# requeue: false requeue message. If false (default), the message will be
|
100
|
+
# discarded by SpaceBunny. If true message will made
|
101
|
+
# requeued and made available for delivery again
|
102
|
+
|
103
|
+
|
104
|
+
# Other 'inbox' options:
|
105
|
+
# 'discard_from_api' (default false) causes the SDK to filter out messages published through APIs (or WEB UI) or
|
106
|
+
# generally sent directly through SpaceBunny's platform.
|
107
|
+
# 'discard_mine' (default false) causes the SKD to filter out auto-messages i.e. messages sent from this device
|
108
|
+
# and, for some reason, returned to the sender. This can happen in some particular situation such as when using m2m
|
109
|
+
# groups.
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spacebunny'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Prerequisites: you have created a device through the Space Bunny's web interface. You also have a 'data' channel (name
|
5
|
+
# is not mandatory, but we'll use this for our example). You have also enabled 'data' channel for the device. See our
|
6
|
+
# Getting Started [link] for a quick introduction to Space Bunny's base concepts.
|
7
|
+
|
8
|
+
# Once everything is set up get your device's API key from Space Bunny's web application: on the web interface,
|
9
|
+
# go to devices section and create or pick an existing device. Click on the 'SHOW CONFIGURATION' link, copy the API key
|
10
|
+
# and substitute it here:
|
11
|
+
|
12
|
+
device_key = 'your_awesome_device_key'
|
13
|
+
|
14
|
+
# Let's instantiate a Space Bunny client, providing the device's API key, that's the fastest and simplest method
|
15
|
+
# to create a new client.
|
16
|
+
# Provide `tls: true` to use instantiate a tls-secured connection
|
17
|
+
|
18
|
+
dev = Spacebunny::Device.new device_key, tls: true
|
19
|
+
|
20
|
+
# An equivalent method for providing the API key is through options: Spacebunny::Device.new(key: key)
|
21
|
+
|
22
|
+
# We need to call 'connect' in order to open the communication with Space Bunny platform
|
23
|
+
|
24
|
+
dev.connect
|
25
|
+
|
26
|
+
# At this point the SDK is auto-configured and ready to use.
|
27
|
+
# Configurations are automatically lazy-fetched by the SDK itself when calling 'connect'.
|
28
|
+
|
29
|
+
# PUBLISHING MESSAGES
|
30
|
+
|
31
|
+
# As said in the prerequisites, we'll assume that 'data' channel is enabled for your device.
|
32
|
+
# If you're in doubt, please check that this is true through Space Bunny's web interface, by clicking on the device
|
33
|
+
# 'edit' (pencil icon) and verifying that 'data' channel is present and enabled for this device. Take a look at Getting
|
34
|
+
# Started [link] for a quick introduction to Space Bunny's base concepts.
|
35
|
+
|
36
|
+
# Let's publish, for instance, some JSON. Payload can be everything you want, Space Bunny does not impose any constraint
|
37
|
+
# on format or content of payload.
|
38
|
+
|
39
|
+
# Publish one message every second for a minute.
|
40
|
+
count = 0
|
41
|
+
60.times do
|
42
|
+
# Generate some random data
|
43
|
+
payload = {
|
44
|
+
count: count,
|
45
|
+
greetings: "Hello from #{dev.name}!",
|
46
|
+
temp: rand(20.0..25.0),
|
47
|
+
foo: rand(100..200)
|
48
|
+
}.to_json
|
49
|
+
|
50
|
+
# Hint: the channel name can also be a string e.g: 'data'
|
51
|
+
dev.publish :data, payload
|
52
|
+
|
53
|
+
# 'publish' takes two mandatory arguments (channel's name and payload) and a variety of options: one of these options is
|
54
|
+
# the 'with_confirm' flag: when set to true this requires Space Bunny's platform to confirm the receipt of the message.
|
55
|
+
# This is useful when message delivery assurance is mandatory for your use case.
|
56
|
+
# Take a look at SDK's documentation for further details.
|
57
|
+
|
58
|
+
# Give some feedback on what has been published
|
59
|
+
puts "Published #{payload}"
|
60
|
+
|
61
|
+
# Take a nap...
|
62
|
+
sleep 1
|
63
|
+
# Update counter
|
64
|
+
count += 1
|
65
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spacebunny'
|
2
|
+
|
3
|
+
# Prerequisites:
|
4
|
+
# - You have created a Live Stream named 'live_data' through the SpaceBunny's web interface.
|
5
|
+
# - The Live Stream has, as sources, two devices' 'data' channel.
|
6
|
+
# See our Getting Started [link] for a quick introduction to SpaceBunny's base concepts.
|
7
|
+
|
8
|
+
# Once everything is set up go to Users section on SpaceBunny Web UI, select one user and click on 'Access Keys'
|
9
|
+
# Pick or create one access key and copy 'Client' and 'Secret'.
|
10
|
+
|
11
|
+
client = 'live_stream_key_client'
|
12
|
+
secret = 'live_stream_key_secret'
|
13
|
+
|
14
|
+
# Instantiate a Spacebunny::LiveStream (AMQP by default) client, providing the 'client' and 'secret' options.
|
15
|
+
|
16
|
+
live = Spacebunny::LiveStream.new client: client, secret: secret
|
17
|
+
|
18
|
+
# We need to call 'connect' in order to open the communication with SpaceBunny platform
|
19
|
+
|
20
|
+
live.connect
|
21
|
+
|
22
|
+
# At this point the SDK is auto-configured and ready to use.
|
23
|
+
# Configurations are automatically lazy-fetched by the SDK itself.
|
24
|
+
|
25
|
+
# RECEIVING MESSAGES
|
26
|
+
|
27
|
+
# For the sake of this test, we need first to publish some message.
|
28
|
+
# If you have configured correctly your Live Stream from Web Interface, it should have at least two devices' data
|
29
|
+
# channels as sources: pick one of the devices and copy its Api Key. Fire up another terminal, edit
|
30
|
+
# examples/device/auto_config_publish.rb paste the Device Key and run the script.
|
31
|
+
|
32
|
+
puts 'Waiting for live messages'
|
33
|
+
|
34
|
+
# Receiving messages is trivial:
|
35
|
+
|
36
|
+
live.message_from_cache :live_data, wait: true, ack: :auto do |message|
|
37
|
+
puts "Received: #{message.payload}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# An alternative to the 'message_from_cache' method is:
|
41
|
+
# live.message_from :live_data, from_cache: true, wait: true, ack: :auto do |message|
|
42
|
+
# puts "Received #{message.payload}"
|
43
|
+
# end
|
44
|
+
|
45
|
+
# Now fire up another terminal, copy the other device's Api Key and replace it in the auto_config_publish.rb
|
46
|
+
# script. If you take a look at the output of running live_stream/receive_messages.rb (this running script)
|
47
|
+
# you should see that messages from the devices are almost alternated.
|
48
|
+
|
49
|
+
# You may notice that we used 'message_from_cache' method: each LiveStream has its own cache that will keep always
|
50
|
+
# last 100 messages (FIFO, when there are more than 100 messages, the oldest ones get discarded).
|
51
|
+
# If you want to consume messages in a parallel way, you should use the cache and connect as many LiveStream Clients
|
52
|
+
# as you need: this way messages will be equally and uniquely distributed to clients.
|
53
|
+
# A nacked message will return to the queue and will be delivered to the next available client.
|
54
|
+
|
55
|
+
# Conversely, if you want that each client will receive a copy of each message, don't use the cache:
|
56
|
+
|
57
|
+
# live.message_from :live_data, wait: true, ack: :auto do |message|
|
58
|
+
# puts "Received a copy of #{message.payload}"
|
59
|
+
# end
|
60
|
+
|
61
|
+
# Every client subscribed to the LiveStream in this way will receive a copy of the message.
|
62
|
+
|
63
|
+
### Options and notes
|
64
|
+
#
|
65
|
+
# A Ruby block must be supplied to the 'message_from' and 'message_from_cache' methods.
|
66
|
+
# These methods yield a Device::Message object that wrap all the useful data:
|
67
|
+
#
|
68
|
+
# dev.message_from do |message|
|
69
|
+
# puts "payload: #{message.payload}"
|
70
|
+
#
|
71
|
+
# # metadata that can be accessed as an Hash.
|
72
|
+
# # metadata[:headers] contains the message's headers for instance
|
73
|
+
# puts "metadata: #{message.metadata}"
|
74
|
+
#
|
75
|
+
# # sender_id (id of the device that sent the message)
|
76
|
+
# puts "sender_id: #{message.sender_id}"
|
77
|
+
#
|
78
|
+
# # channel name i.e. the name of the channel on which the message has been published
|
79
|
+
# puts "channel_name: #{message.channel_name}"
|
80
|
+
#
|
81
|
+
# # Connection and other low level info
|
82
|
+
# puts "delivery_info: #{message.delivery_info}"
|
83
|
+
# end
|
84
|
+
|
85
|
+
# message_from (message_from_cache) methods' options:
|
86
|
+
# 'wait' (default false) causes the script to wait forever on the receive block. This is useful
|
87
|
+
# for 'read-only' scripts like workers or similar.
|
88
|
+
# 'ack' option can have two values: :manual (default) or :auto. When :manual you are responsible to ack the messages,
|
89
|
+
# for instance:
|
90
|
+
#
|
91
|
+
# dev.message_from(block: true, ack: :manual) do |message|
|
92
|
+
# puts "Received: #{message.payload}"
|
93
|
+
# message.ack
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# When 'ack' is :auto then the SDK will automatically ack the messages when the code provided in the message_from block
|
97
|
+
# has terminated execution. This behaviour is useful in cases in which the code executed inside the message_from block
|
98
|
+
# will always lead to an acked message (no matter the operations' result).
|
99
|
+
# If your code can lead to errors and, for instance, the message must be reprocessed, you must use :manual ack
|
100
|
+
# and manually call 'ack' as seen in the example above.
|
101
|
+
# Call 'nack' in case the message needs to be reprocessed and it will remain on SpaceBunny until successfully
|
102
|
+
# acked. For instance:
|
103
|
+
|
104
|
+
# dev.message_from(block: true, ack: :manual) do |message|
|
105
|
+
# puts message.payload
|
106
|
+
#
|
107
|
+
# begin
|
108
|
+
# # Do something nasty...
|
109
|
+
# raise
|
110
|
+
#
|
111
|
+
# # If everything is ok, ack
|
112
|
+
# # (note that this code will never be reached)
|
113
|
+
# message.ack
|
114
|
+
# rescue Exception => e
|
115
|
+
# message.nack
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
|
119
|
+
# ack options are:
|
120
|
+
# multiple: false ack multiple messages at once. Ack all the unacked
|
121
|
+
# messages up to the current one
|
122
|
+
|
123
|
+
# nack options are:
|
124
|
+
# multiple: false nack multiple messages at once. Nack all the messages up
|
125
|
+
# to the current one
|
126
|
+
# requeue: false requeue message. If false (default), the message will be
|
127
|
+
# discarded by SpaceBunny. If true message will made
|
128
|
+
# requeued and made available for delivery again
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spacebunny'
|
2
|
+
|
3
|
+
# Prerequisites:
|
4
|
+
# - You have created a Live Stream named 'live_data' through the SpaceBunny's web interface.
|
5
|
+
# - The Live Stream has, as sources, two devices' 'data' channel.
|
6
|
+
# See our Getting Started [link] for a quick introduction to SpaceBunny's base concepts.
|
7
|
+
|
8
|
+
# Once everything is set up go to Users section on SpaceBunny Web UI, select one user and click on 'Access Keys'
|
9
|
+
# Pick or create one access key and copy 'Client' and 'Secret'.
|
10
|
+
|
11
|
+
client = 'live_stream_key_client'
|
12
|
+
secret = 'live_stream_key_secret'
|
13
|
+
|
14
|
+
# Instantiate a Spacebunny::LiveStream (AMQP by default) client, providing the 'client' and 'secret' options.
|
15
|
+
# Also provide tls: true to establish a tls-encrypted connection
|
16
|
+
|
17
|
+
live = Spacebunny::LiveStream.new client: client, secret: secret, tls: true
|
18
|
+
live.connect
|
19
|
+
|
20
|
+
live.message_from_cache :live_data, ack: :auto, wait: true do |message|
|
21
|
+
puts "Received: #{message.payload}"
|
22
|
+
end
|
data/lib/spacebunny.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# require 'active_support/core_ext/hash/keys'
|
2
|
+
|
3
|
+
module Spacebunny
|
4
|
+
class << self
|
5
|
+
attr_accessor :logger
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'spacebunny/version'
|
10
|
+
require 'spacebunny/utils'
|
11
|
+
require 'spacebunny/logger'
|
12
|
+
require 'spacebunny/exceptions'
|
13
|
+
require 'spacebunny/endpoint_connection'
|
14
|
+
require 'spacebunny/device/base'
|
15
|
+
require 'spacebunny/device/message'
|
16
|
+
require 'spacebunny/device/amqp'
|
17
|
+
require 'spacebunny/live_stream/base'
|
18
|
+
require 'spacebunny/live_stream/message'
|
19
|
+
require 'spacebunny/live_stream/amqp'
|
20
|
+
|
21
|
+
def path_to_resources(path)
|
22
|
+
File.join(File.dirname(File.expand_path(__FILE__)), path)
|
23
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
module Spacebunny
|
4
|
+
module Device
|
5
|
+
class Amqp < Base
|
6
|
+
DEFAULT_CHANNEL_OPTIONS = { passive: true }
|
7
|
+
ACK_TYPES = [:manual, :auto]
|
8
|
+
|
9
|
+
attr_reader :built_channels, :built_exchanges, :client
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super(:amqp, *args)
|
13
|
+
@built_channels = {}
|
14
|
+
@built_exchanges = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect
|
18
|
+
# 'Fix' attributes: start from common connection configs and adjust attributes to match what Bunny
|
19
|
+
# wants as connection args
|
20
|
+
connection_params = connection_configs.dup
|
21
|
+
connection_params[:user] = connection_params.delete :device_id
|
22
|
+
connection_params[:password] = connection_params.delete :secret
|
23
|
+
connection_params[:port] = connection_params.delete(:tls_port) if connection_params[:tls]
|
24
|
+
connection_params[:recover_from_connection_close] = connection_params.delete :auto_recover
|
25
|
+
connection_params[:log_level] = connection_params.delete(:log_level) || ::Logger::ERROR
|
26
|
+
|
27
|
+
# Re-create client every time connect is called
|
28
|
+
@client = Bunny.new(connection_params)
|
29
|
+
@client.start
|
30
|
+
end
|
31
|
+
|
32
|
+
def channel_from_name(name)
|
33
|
+
# In @built_channels in fact we have exchanges
|
34
|
+
with_channel_check name do
|
35
|
+
@built_exchanges[name]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def disconnect
|
40
|
+
super
|
41
|
+
client.stop
|
42
|
+
end
|
43
|
+
|
44
|
+
def input_channel
|
45
|
+
return @input_channel if @input_channel
|
46
|
+
@input_channel = client.create_channel
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_receive(options = {})
|
50
|
+
unless block_given?
|
51
|
+
raise BlockRequired
|
52
|
+
end
|
53
|
+
blocking = options.fetch :wait, false
|
54
|
+
to_ack, auto_ack = parse_ack options.fetch(:ack, :manual)
|
55
|
+
|
56
|
+
input_queue.subscribe(block: blocking, manual_ack: to_ack) do |delivery_info, metadata, payload|
|
57
|
+
message = Device::Message.new self, options, delivery_info, metadata, payload
|
58
|
+
|
59
|
+
# Skip message if required
|
60
|
+
if message.blacklisted?
|
61
|
+
message.nack
|
62
|
+
next
|
63
|
+
end
|
64
|
+
|
65
|
+
yield message
|
66
|
+
|
67
|
+
# If ack is :auto then ack current message
|
68
|
+
if to_ack && auto_ack
|
69
|
+
message.ack
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
alias_method :inbox, :on_receive
|
74
|
+
|
75
|
+
def publish(channel_name, message, options = {})
|
76
|
+
check_client
|
77
|
+
channel_key = if options[:with_confirm]
|
78
|
+
"#{channel_name}_confirm"
|
79
|
+
else
|
80
|
+
channel_name
|
81
|
+
end.to_sym
|
82
|
+
|
83
|
+
unless @built_exchanges[channel_key]
|
84
|
+
@built_exchanges[channel_key] = create_channel(channel_name, options)
|
85
|
+
end
|
86
|
+
# Call Bunny publish method
|
87
|
+
@built_exchanges[channel_key].publish message, channel_options(channel_name, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_for_publish_confirms
|
91
|
+
results = {}
|
92
|
+
threads = []
|
93
|
+
@built_channels.each do |name, channel|
|
94
|
+
if channel.using_publisher_confirmations?
|
95
|
+
threads << Thread.new do
|
96
|
+
results[name] = { all_confirmed: channel.wait_for_confirms, nacked_set: channel.nacked_set }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
threads.map{ |t| t.join }
|
101
|
+
results
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Merge default channel options with provided ones
|
107
|
+
def channel_options(channel, options)
|
108
|
+
options.merge({routing_key: "#{id}.#{channel}" })
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check if client has been prepared.
|
112
|
+
def check_client
|
113
|
+
unless client
|
114
|
+
raise ClientNotSetup
|
115
|
+
end
|
116
|
+
unless client.connected?
|
117
|
+
if raise_on_error
|
118
|
+
raise ClientNotConnected
|
119
|
+
else
|
120
|
+
@logger.error 'Client not connected! Check internet connection'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def create_channel(name, options = {})
|
126
|
+
with_channel_check name do
|
127
|
+
channel = client.create_channel
|
128
|
+
if options.delete(:with_confirm)
|
129
|
+
channel.confirm_select
|
130
|
+
end
|
131
|
+
@built_channels[name] = channel
|
132
|
+
channel.direct(id, DEFAULT_CHANNEL_OPTIONS)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def input_queue
|
137
|
+
return @input_queue if @input_queue
|
138
|
+
@input_queue = input_channel.queue "#{id}.inbox", passive: true
|
139
|
+
end
|
140
|
+
|
141
|
+
def parse_ack(ack)
|
142
|
+
to_ack = false
|
143
|
+
auto_ack = false
|
144
|
+
if ack
|
145
|
+
raise AckTypeError unless ACK_TYPES.include?(ack)
|
146
|
+
to_ack = true
|
147
|
+
case ack
|
148
|
+
when :manual
|
149
|
+
auto_ack = false
|
150
|
+
when :auto
|
151
|
+
auto_ack = true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return to_ack, auto_ack
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|