spacebunny 1.0.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 +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
|