heroic-sns 1.1.0 → 1.1.1
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 +6 -14
- data/.gitignore +1 -0
- data/CHANGELOG.md +21 -0
- data/README.md +79 -60
- data/Rakefile +6 -0
- data/heroic-sns.gemspec +2 -1
- data/lib/heroic/lru_cache.rb +37 -28
- data/lib/heroic/sns/endpoint.rb +43 -27
- data/lib/heroic/sns/message.rb +20 -6
- data/lib/heroic/sns/version.rb +1 -1
- data/test/test_lru_cache.rb +41 -42
- metadata +24 -18
- data/CHANGELOG +0 -13
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
ZTkxZjczZTVjODdjMjBlMTM0OWU2YjFmYzI4NzM1YzZmMWYzZWFlZjQ2YzMx
|
10
|
-
ODM1ODk3ZmNlMDRmYjE4NmI3MWE4ZTVmM2RmNTgwZTc2YjMwMjEzZjRhNjEy
|
11
|
-
NTVhMDZkZjlmN2IwYjkxNTdjM2M1Mzc2ODFjMzU3ZjhhZGRjZTg=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZTY3YzJmZWRkZTE3OGE4ODRkNjgxNjMwMmYwYzRmZGQ4ODQ1MzE1YjMzZTcw
|
14
|
-
ZmYzMTJkYzhhM2Y5MmQ2YTQ5ZGRlYmUwMWMwYTc5YWUwM2RhMGFiOGJkZmVh
|
15
|
-
OWUyODNjYWE3Njg3Y2MyMzQ1ODI2N2Q0MGE4MmU5MTU0ODIzMDU=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11d6f41c3874ec698df32ac516c3d103f74bfd7b
|
4
|
+
data.tar.gz: 9dbf1f1690e3ec3888a52c661c4755c6d5c1a05e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0e8fe315fd65c499060616c8780586c427b1ed3ff3a829df29437fe17128d764cdc502f1eed9f450c7e09c068b80e65bf9fddce08253ab2ba1d6b4dc3d1062f
|
7
|
+
data.tar.gz: 20d5a1e66117f7c30e15d4107d93d6f84d8439dad5fd58919f46eb2e7bf0209067e47c048e3e7c040dbe365dfe8e18ddb4e33f16e6eb1898bb211b20b9028eac
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
### 1.1.1 (August 9, 2013)
|
2
|
+
|
3
|
+
* Ease up json gem dependency - [@speedmanly]
|
4
|
+
* Documentation cleaned up and tests improved.
|
5
|
+
|
6
|
+
### 1.1.0 (June 20, 2013)
|
7
|
+
|
8
|
+
* Rework Cert logic - [@sbeckeriv]
|
9
|
+
|
10
|
+
### 1.0.1 (May 17, 2013)
|
11
|
+
|
12
|
+
* Gem housekeeping, based on [advice by @dblock](http://code.dblock.org/your-first-ruby-gem)
|
13
|
+
|
14
|
+
### 1.0.0 (May 5, 2013)
|
15
|
+
|
16
|
+
* Initial public release - [@benzado]
|
17
|
+
|
18
|
+
[@benzado]: http://github.com/benzado
|
19
|
+
[@sbeckeriv]: http://github.com/sbeckeriv
|
20
|
+
[@dblock]: http://github.com/dblock
|
21
|
+
[@speedmanly]: http://github.com/speedmanly
|
data/README.md
CHANGED
@@ -3,21 +3,26 @@
|
|
3
3
|
Heroic::SNS provides secure, lightweight Rack middleware for AWS Simple
|
4
4
|
Notification Service (SNS) endpoints.
|
5
5
|
|
6
|
-
Any SNS messages POSTed by Amazon to your web application are
|
7
|
-
parsed, verified, and then passed along via the
|
6
|
+
Any SNS messages POSTed by Amazon to your web application are
|
7
|
+
intercepted, parsed, verified, and then passed along via the
|
8
|
+
`sns.message` environment key.
|
8
9
|
|
9
|
-
If something goes wrong, the error will be passed along via the
|
10
|
-
environment key. `Heroic::SNS::Endpoint` does not log any
|
10
|
+
If something goes wrong, the error will be passed along via the
|
11
|
+
`sns.error` environment key. `Heroic::SNS::Endpoint` does not log any
|
12
|
+
messages itself.
|
11
13
|
|
12
|
-
**Heroic::SNS aims to be secure.** All message signatures are verified
|
13
|
-
forgeries) and stale messages are rejected (to avoid replay
|
14
|
+
**Heroic::SNS aims to be secure.** All message signatures are verified
|
15
|
+
(to avoid forgeries) and stale messages are rejected (to avoid replay
|
16
|
+
attacks).
|
14
17
|
|
15
|
-
**Heroic::SNS aims to be lightweight.** Beside Ruby standard libraries
|
16
|
-
no dependencies
|
17
|
-
|
18
|
+
**Heroic::SNS aims to be lightweight.** Beside Ruby standard libraries
|
19
|
+
there are no dependencies besides [json][] and [rack][]. Specifically,
|
20
|
+
Heroic::SNS *does not* depend on [aws-sdk][]. They will be friendly to
|
21
|
+
each other, however, if you include both in a project.
|
18
22
|
|
19
|
-
[
|
23
|
+
[json]: https://rubygems.org/gems/json
|
20
24
|
[rack]: http://rack.github.io/
|
25
|
+
[aws-sdk]: https://github.com/aws/aws-sdk-ruby
|
21
26
|
|
22
27
|
## Overview
|
23
28
|
|
@@ -29,8 +34,8 @@ in a project.
|
|
29
34
|
|
30
35
|
## How to use it
|
31
36
|
|
32
|
-
Once you have installed the gem, simply add the following to your
|
33
|
-
file:
|
37
|
+
Once you have installed the gem, simply add the following to your
|
38
|
+
`config.ru` file:
|
34
39
|
|
35
40
|
use Heroic::SNS::Endpoint, :topics => /:aws-ses-bounces$/
|
36
41
|
|
@@ -38,52 +43,64 @@ On Rails, you could also install it in `/config/initializers/sns_endpoint.rb`:
|
|
38
43
|
|
39
44
|
Rails.application.config.middleware.use Heroic::SNS::Endpoint, :topic => ...
|
40
45
|
|
41
|
-
The Endpoint class takes an options hash as an argument, and understands
|
42
|
-
options:
|
46
|
+
The Endpoint class takes an options hash as an argument, and understands
|
47
|
+
these options:
|
48
|
+
|
49
|
+
### :topic
|
43
50
|
|
44
|
-
`:topic` is required, and provides a filter that defines what SNS topics
|
45
|
-
handled by this endpoint. **A message is considered either
|
46
|
-
"off-topic".** You can supply any of the following:
|
51
|
+
`:topic` is required, and provides a filter that defines what SNS topics
|
52
|
+
are handled by this endpoint. **A message is considered either
|
53
|
+
"on-topic" or "off-topic".** You can supply any of the following:
|
47
54
|
|
48
55
|
- a `String` containing a single topic ARN
|
56
|
+
|
49
57
|
- an `Array` of `String` representing a list of topic ARNs
|
58
|
+
|
50
59
|
- a `RegExp` which matches on-topic ARNs
|
51
|
-
|
52
|
-
|
60
|
+
|
61
|
+
- a `Proc` which accepts an ARN as an argument and returns `true` or
|
62
|
+
`false` for on-topic and off-topic ARNs, respectively.
|
53
63
|
|
54
64
|
The key `:topics` is also supported.
|
55
65
|
|
66
|
+
### :auto_confirm
|
67
|
+
|
56
68
|
`:auto_confirm` affects how on-topic subscription confirmations are handled.
|
57
69
|
|
58
|
-
- If `true`, they are confirmed by retrieving the URL in the
|
59
|
-
field of the SNS message, and your app is
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
70
|
+
- If `true`, they are confirmed by retrieving the URL in the
|
71
|
+
`SubscribeURL` field of the SNS message, and your app is NOT notified.
|
72
|
+
This is the default.
|
73
|
+
|
74
|
+
- If `false`, they are ignored; your app is NOT notified.
|
75
|
+
|
76
|
+
- If `nil`, there is no special handling and the message is passed along
|
77
|
+
to your app.
|
78
|
+
|
79
|
+
### :auto_resubscribe
|
65
80
|
|
66
81
|
`:auto_resubscribe` affects how on-topic unsubscribe confirmations are handled.
|
67
82
|
|
68
|
-
- If `
|
69
|
-
|
70
|
-
|
71
|
-
- If `
|
72
|
-
app
|
83
|
+
- If `false`, they are ignored and your app is NOT notified. This is the
|
84
|
+
default.
|
85
|
+
|
86
|
+
- If `true`, they topic is automatically re-subscribed by retrieving the
|
87
|
+
URL in the `SubscribeURL` field of the SNS message, and your app is
|
88
|
+
NOT notified.
|
73
89
|
|
74
|
-
|
90
|
+
- If `nil`, there is no special handling and the message is passed along
|
91
|
+
to your app.
|
75
92
|
|
76
|
-
If you are a control-freak and want no special handling whatsoever, use
|
77
|
-
options:
|
93
|
+
If you are a control-freak and want no special handling whatsoever, use
|
94
|
+
these options:
|
78
95
|
|
79
96
|
use Heroic::SNS::Endpoint, :topics => Proc.new { true }, :auto_confirm => nil, :auto_resubscribe => nil
|
80
97
|
|
81
|
-
Then the object will simply parse and verify SNS messages it finds and
|
82
|
-
along to your app, taking no action.
|
98
|
+
Then the object will simply parse and verify SNS messages it finds and
|
99
|
+
pass them along to your app, taking no action.
|
83
100
|
|
84
|
-
Once the middleware is set up, any notifications will be made available
|
85
|
-
Rack environment under the `sns.message` key. If you are using
|
86
|
-
controller would have a method like this:
|
101
|
+
Once the middleware is set up, any notifications will be made available
|
102
|
+
in your Rack environment under the `sns.message` key. If you are using
|
103
|
+
Rails, your controller would have a method like this:
|
87
104
|
|
88
105
|
skip_before_filter :verify_authenticity_token, :only => [:handle_notification]
|
89
106
|
|
@@ -98,43 +115,45 @@ controller would have a method like this:
|
|
98
115
|
head :ok
|
99
116
|
end
|
100
117
|
|
101
|
-
You must skip the authenticity token verification to allow Amazon to
|
102
|
-
controller action. Be careful not to disable it for more
|
103
|
-
Be sure to disable any authentication checks for
|
118
|
+
You must skip the authenticity token verification to allow Amazon to
|
119
|
+
POST to the controller action. Be careful not to disable it for more
|
120
|
+
actions than you need. Be sure to disable any authentication checks for
|
121
|
+
that action, too.
|
104
122
|
|
105
123
|
## Multiple endpoint URLs
|
106
124
|
|
107
|
-
If you are receiving multiple notifications at multiple endpoint URLs,
|
108
|
-
should only include one instance of the Endpoint in your middleware
|
109
|
-
ensure that its topic filter allows all the notifications you
|
110
|
-
to pass through.
|
125
|
+
If you are receiving multiple notifications at multiple endpoint URLs,
|
126
|
+
you should only include one instance of the Endpoint in your middleware
|
127
|
+
stack, and ensure that its topic filter allows all the notifications you
|
128
|
+
are interested in to pass through.
|
111
129
|
|
112
130
|
`Endpoint` does not interact with the URL path at all; if you want your
|
113
131
|
subscriptions to go to different URLs, simply set them up that way.
|
114
132
|
|
115
133
|
## Off-topic notifications
|
116
134
|
|
117
|
-
As a security measure, `Endpoint` requires you to set up a topic filter.
|
118
|
-
notifications that do not match this filter are not passed along to
|
119
|
-
application.
|
135
|
+
As a security measure, `Endpoint` requires you to set up a topic filter.
|
136
|
+
Any notifications that do not match this filter are not passed along to
|
137
|
+
your application.
|
120
138
|
|
121
|
-
All off-topic messages are ignored with one exception: if the message is
|
122
|
-
regular notification (meaning your app has an active subscription)
|
123
|
-
message can be verified as authentic (by checking its
|
124
|
-
will cancel the subscription by visiting the URL
|
125
|
-
of the message.
|
139
|
+
All off-topic messages are ignored with one exception: if the message is
|
140
|
+
a regular notification (meaning your app has an active subscription)
|
141
|
+
*and* the message can be verified as authentic (by checking its
|
142
|
+
signature), `Endpoint` will cancel the subscription by visiting the URL
|
143
|
+
in the `UnsubscribeURL` field of the message.
|
126
144
|
|
127
|
-
If you would rather make decision about on-topic and off-topic
|
128
|
-
your own code, simply pass `Proc.new { true }` as the
|
129
|
-
messages will be treated as on topic. Be aware
|
130
|
-
`:auto_confirm` enabled with a permissive
|
131
|
-
anyone to subscribe your web app to any
|
145
|
+
If you would rather make decision about on-topic and off-topic
|
146
|
+
notifications in your own code, simply pass `Proc.new { true }` as the
|
147
|
+
topic filter, and all messages will be treated as on topic. Be aware
|
148
|
+
that it is dangerous to leave `:auto_confirm` enabled with a permissive
|
149
|
+
topic filter, as this will allow anyone to subscribe your web app to any
|
150
|
+
SNS notification.
|
132
151
|
|
133
152
|
## Contributing
|
134
153
|
|
135
154
|
* Fork the project.
|
136
155
|
* Make your feature addition or bug fix and include tests.
|
137
|
-
* Update `CHANGELOG`.
|
156
|
+
* Update `CHANGELOG.md`.
|
138
157
|
* Send a pull request.
|
139
158
|
|
140
159
|
## Copyright and License
|
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/gem_tasks'
|
3
3
|
require 'rake/testtask'
|
4
|
+
require 'rdoc/task'
|
4
5
|
|
5
6
|
Bundler.setup(:default, :development)
|
6
7
|
|
@@ -8,6 +9,11 @@ Rake::TestTask.new do |t|
|
|
8
9
|
t.libs << 'test'
|
9
10
|
end
|
10
11
|
|
12
|
+
RDoc::Task.new do |rdoc|
|
13
|
+
rdoc.main = "README.md"
|
14
|
+
rdoc.rdoc_files.include("README.md", "CHANGELOG.md", "lib")
|
15
|
+
end
|
16
|
+
|
11
17
|
task :demo do
|
12
18
|
sh 'rackup -Ilib demo/config.ru'
|
13
19
|
end
|
data/heroic-sns.gemspec
CHANGED
@@ -26,6 +26,7 @@ EOD
|
|
26
26
|
|
27
27
|
s.platform = Gem::Platform::RUBY
|
28
28
|
s.required_ruby_version = '>= 1.8.7'
|
29
|
+
s.add_development_dependency 'rdoc', '~> 4.0'
|
29
30
|
s.add_runtime_dependency 'rack', '~> 1.4'
|
30
|
-
s.add_runtime_dependency 'json', '~> 1.7
|
31
|
+
s.add_runtime_dependency 'json', '~> 1.7'
|
31
32
|
end
|
data/lib/heroic/lru_cache.rb
CHANGED
@@ -3,12 +3,12 @@ module Heroic
|
|
3
3
|
# This LRU Cache is a generic key-value store that is designed to be safe for
|
4
4
|
# concurrent access. It uses a doubly-linked-list to identify which item was
|
5
5
|
# least recently retrieved, and a Hash for fast retrieval by key.
|
6
|
-
|
6
|
+
#
|
7
7
|
# To support concurrent access, it uses two levels of locks: a cache-level lock
|
8
8
|
# is used to locate or create the desired node and move it to the front of the
|
9
9
|
# LRU list. A node-level lock is used to synchronize access to the node's
|
10
10
|
# value.
|
11
|
-
|
11
|
+
#
|
12
12
|
# If a thread is busy generating a value to be stored in the cache, other
|
13
13
|
# threads will still be able to read and write to other keys with no conflict.
|
14
14
|
# However, if a second thread tries to read the value that the first thread is
|
@@ -16,24 +16,6 @@ module Heroic
|
|
16
16
|
|
17
17
|
class LRUCache
|
18
18
|
|
19
|
-
class Node
|
20
|
-
attr_reader :key
|
21
|
-
attr_accessor :left, :right
|
22
|
-
def initialize(key)
|
23
|
-
@key = key
|
24
|
-
@lock = Mutex.new
|
25
|
-
end
|
26
|
-
def read
|
27
|
-
@lock.synchronize { @value ||= yield(@key) }
|
28
|
-
end
|
29
|
-
def write(value)
|
30
|
-
@lock.synchronize { @value = value }
|
31
|
-
end
|
32
|
-
def to_s
|
33
|
-
sprintf '<Node:%x(%s)>', self.object_id, @key.inspect
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
19
|
# If you yield a block to the constructor, it will be called on every cache
|
38
20
|
# miss to generate the needed value. This is optional but recommended, as
|
39
21
|
# the block will run while holding a lock on the cache node associated with
|
@@ -53,25 +35,34 @@ module Heroic
|
|
53
35
|
@rightmost = nil
|
54
36
|
end
|
55
37
|
|
38
|
+
# Retrieve a value from the cache for a given key. If the value is in the
|
39
|
+
# cache, the method will return immediately. If the value is not in the
|
40
|
+
# cache and a block was provided to the constructor, it will be invoked to
|
41
|
+
# generate the value and insert it into the cache. If another thread is in
|
42
|
+
# the process of generating the same value, the current thread will wait for
|
43
|
+
# it to complete.
|
44
|
+
#
|
45
|
+
# If the cache is full, this method may cause the least recently used item
|
46
|
+
# to be evicted from the cache.
|
47
|
+
|
56
48
|
def get(key)
|
57
49
|
node = node_for_key(key)
|
58
50
|
node.read(&@block)
|
59
51
|
end
|
60
52
|
|
53
|
+
# Inserts a value into the cache, assigning it to the given key. (Instead of
|
54
|
+
# directly inserting values into the cache, it is recommended that you supply
|
55
|
+
# a block to the constructor to generate values on demand.)
|
56
|
+
|
61
57
|
def put(key, value)
|
62
58
|
node = node_for_key(key)
|
63
59
|
node.write(value)
|
64
60
|
end
|
65
61
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@leftmost = nil
|
70
|
-
@rightmost = nil
|
71
|
-
end
|
72
|
-
end
|
62
|
+
# Verify the data structures used to maintain the cache. If a problem is
|
63
|
+
# detected, an exception is raised. This method is intended for testing and
|
64
|
+
# debugging only.
|
73
65
|
|
74
|
-
# Verify the list structure. Intended for testing and debugging only.
|
75
66
|
def verify!
|
76
67
|
@lock.synchronize do
|
77
68
|
left_to_right = Array.new
|
@@ -111,6 +102,24 @@ module Heroic
|
|
111
102
|
|
112
103
|
private
|
113
104
|
|
105
|
+
class Node # :nodoc:
|
106
|
+
attr_reader :key
|
107
|
+
attr_accessor :left, :right
|
108
|
+
def initialize(key)
|
109
|
+
@key = key
|
110
|
+
@lock = Mutex.new
|
111
|
+
end
|
112
|
+
def read
|
113
|
+
@lock.synchronize { @value ||= yield(@key) }
|
114
|
+
end
|
115
|
+
def write(value)
|
116
|
+
@lock.synchronize { @value = value }
|
117
|
+
end
|
118
|
+
def to_s
|
119
|
+
sprintf '<Node:%x(%s)>', self.object_id, @key.inspect
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
114
123
|
def node_for_key(key)
|
115
124
|
@lock.synchronize do
|
116
125
|
node = @store[key]
|
data/lib/heroic/sns/endpoint.rb
CHANGED
@@ -1,38 +1,54 @@
|
|
1
1
|
require 'openssl'
|
2
2
|
require 'open-uri'
|
3
3
|
|
4
|
-
# Heroic::SNS::Endpoint is Rack middleware which intercepts messages from
|
5
|
-
# Amazon's Simple Notification Service (SNS). It makes the parsed and verified
|
6
|
-
# message available to your application in the Rack environment under the
|
7
|
-
# 'sns.message' key. If an error occurred during message handling, the error
|
8
|
-
# is available in the Rack environment in the 'sns.error' key.
|
9
|
-
|
10
|
-
# Endpoint is to be initialized with a hash of options. It understands three
|
11
|
-
# different options:
|
12
|
-
# - :topic (or :topics) specifies a filter that defines what SNS topics are
|
13
|
-
# handled by this endpoint ("on-topic"). You can supply any of the following:
|
14
|
-
# - a topic ARN as a String
|
15
|
-
# - a list of topic ARNs as an Array of Strings
|
16
|
-
# - a regular expression matching on-topic ARNs
|
17
|
-
# - a Proc which takes a topic ARN as a String and returns true or false.
|
18
|
-
# You must specify a topic filter. Use Proc.new { true } if you insist on
|
19
|
-
# indiscriminately accepting all notifications.
|
20
|
-
# - :auto_confirm determines how SubscriptionConfirmation messages are handled.
|
21
|
-
# If true, the subscription is confirmed and your app is not notified.
|
22
|
-
# If false, the subscription is ignored and your app is not notified.
|
23
|
-
# If nil, the message is passed along to your app.
|
24
|
-
|
25
|
-
# You can install this in your config.ru:
|
26
|
-
# use Heroic::SNS::Endpoint, :topics => /whatever/
|
27
|
-
|
28
|
-
# For Rails, you can also install it in /config/initializers/sns_endpoint.rb:
|
29
|
-
# Rails.application.config.middleware.use Heroic::SNS::Endpoint, :topic => ...
|
30
|
-
|
31
4
|
module Heroic
|
32
5
|
module SNS
|
33
6
|
|
34
7
|
SUBSCRIPTION_ARN_HTTP_HEADER = 'HTTP_X_AMZ_SNS_SUBSCRIPTION_ARN'
|
35
8
|
|
9
|
+
=begin rdoc
|
10
|
+
|
11
|
+
Heroic::SNS::Endpoint is Rack middleware which intercepts messages from
|
12
|
+
Amazon's Simple Notification Service (SNS). It makes the parsed and
|
13
|
+
verified message available to your application in the Rack environment
|
14
|
+
under the 'sns.message' key. If an error occurred during message handling,
|
15
|
+
the error is available in the Rack environment in the 'sns.error' key.
|
16
|
+
|
17
|
+
Endpoint is to be initialized with a hash of options. It understands three
|
18
|
+
different options:
|
19
|
+
|
20
|
+
+:topic+ (or +:topics+) specifies a filter that defines what SNS topics
|
21
|
+
are handled by this endpoint ("on-topic"). You can supply any of the
|
22
|
+
following:
|
23
|
+
- a topic ARN as a String
|
24
|
+
- a list of topic ARNs as an Array of Strings
|
25
|
+
- a regular expression matching on-topic ARNs
|
26
|
+
- a Proc which takes a topic ARN as a String and returns true or false.
|
27
|
+
You *must* specify a topic filter. Use <code>Proc.new{true}</code> if
|
28
|
+
you insist on indiscriminately accepting all notifications.
|
29
|
+
|
30
|
+
+:auto_confirm+ determines how SubscriptionConfirmation messages are handled.
|
31
|
+
- If true, the subscription is confirmed and your app is not notified.
|
32
|
+
This is the default.
|
33
|
+
- If false, the subscription is ignored and your app is not notified.
|
34
|
+
- If nil, the message is passed along to your app.
|
35
|
+
|
36
|
+
+:auto_resubscribe+ affects how on-topic UnsubscribeConfirmation messages are handled.
|
37
|
+
- If false, they are ignored and your app is also not notified.
|
38
|
+
This is the default.
|
39
|
+
- If true, they topic is automatically re-subscribed by retrieving the URL in
|
40
|
+
the `SubscribeURL` field of the SNS message, and your app is not notified.
|
41
|
+
- If nil, there is no special handling and the message is passed along to your
|
42
|
+
app.
|
43
|
+
|
44
|
+
You can install this in your config.ru:
|
45
|
+
use Heroic::SNS::Endpoint, :topics => /whatever/
|
46
|
+
|
47
|
+
For Rails, you can also install it in /config/initializers/sns_endpoint.rb:
|
48
|
+
Rails.application.config.middleware.use Heroic::SNS::Endpoint, :topic => ...
|
49
|
+
|
50
|
+
=end
|
51
|
+
|
36
52
|
class Endpoint
|
37
53
|
|
38
54
|
DEFAULT_OPTIONS = { :auto_confirm => true, :auto_resubscribe => false }
|
data/lib/heroic/sns/message.rb
CHANGED
@@ -19,7 +19,10 @@ module Heroic
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
# Encapsulates an SNS message.
|
22
|
+
# Encapsulates an SNS message. Since Endpoint takes care of authenticating
|
23
|
+
# the message, most of the time you will simply be interested in retrieving
|
24
|
+
# the +subject+ and +body+ from the message and acting on it.
|
25
|
+
#
|
23
26
|
# See: http://docs.aws.amazon.com/sns/latest/gsg/json-formats.html
|
24
27
|
class Message
|
25
28
|
|
@@ -29,6 +32,8 @@ module Heroic
|
|
29
32
|
raise Error.new("failed to parse message as JSON: #{e.message}")
|
30
33
|
end
|
31
34
|
|
35
|
+
# The message type will be one of +SubscriptionConfirmation+,
|
36
|
+
# +UnsubscribeConfirmation+, and +Notification+.
|
32
37
|
def type
|
33
38
|
@msg['Type']
|
34
39
|
end
|
@@ -37,11 +42,14 @@ module Heroic
|
|
37
42
|
@msg['TopicArn']
|
38
43
|
end
|
39
44
|
|
45
|
+
# A Universally Unique Identifier, unique for each message published. For
|
46
|
+
# a notification that Amazon SNS resends during a retry, the message ID of
|
47
|
+
# the original message is used.
|
40
48
|
def id
|
41
49
|
@msg['MessageId']
|
42
50
|
end
|
43
51
|
|
44
|
-
# The timestamp as a Time object.
|
52
|
+
# The timestamp when the message was published, as a Time object.
|
45
53
|
def timestamp
|
46
54
|
Time.xmlschema(@msg['Timestamp'])
|
47
55
|
end
|
@@ -64,6 +72,9 @@ module Heroic
|
|
64
72
|
@msg['Subject']
|
65
73
|
end
|
66
74
|
|
75
|
+
# The message payload. As far as Amazon and this class are concerned, the
|
76
|
+
# message payload is just a string of bytes. If you are expecting, for
|
77
|
+
# example, a JSON object, you will need to pass this to a JSON parser.
|
67
78
|
def body
|
68
79
|
@msg['Message']
|
69
80
|
end
|
@@ -77,13 +88,13 @@ module Heroic
|
|
77
88
|
end
|
78
89
|
|
79
90
|
# The token is used to confirm subscriptions via the SNS API. If you visit
|
80
|
-
# the
|
91
|
+
# the +subscribe_url+, you can ignore this field.
|
81
92
|
def token
|
82
93
|
@msg['Token']
|
83
94
|
end
|
84
95
|
|
85
|
-
def ==(
|
86
|
-
@msg ==
|
96
|
+
def ==(other)
|
97
|
+
other.is_a?(Message) && @msg == other.instance_variable_get(:@msg)
|
87
98
|
end
|
88
99
|
|
89
100
|
def hash
|
@@ -98,11 +109,14 @@ module Heroic
|
|
98
109
|
string << ">"
|
99
110
|
end
|
100
111
|
|
112
|
+
# Returns a JSON serialization of the message. Note that it may not be
|
113
|
+
# identical to the serialization that was retrieved from the network.
|
101
114
|
def to_json
|
102
115
|
@msg.to_json
|
103
116
|
end
|
104
117
|
|
105
|
-
# Verifies the message signature. Raises
|
118
|
+
# Verifies the message signature. Raises Error if it is not valid.
|
119
|
+
#
|
106
120
|
# See: http://docs.aws.amazon.com/sns/latest/gsg/SendMessageToHttp.verify.signature.html
|
107
121
|
def verify!
|
108
122
|
age = Time.now - timestamp
|
data/lib/heroic/sns/version.rb
CHANGED
data/test/test_lru_cache.rb
CHANGED
@@ -14,12 +14,12 @@ class LRUCacheTest < Test::Unit::TestCase
|
|
14
14
|
|
15
15
|
def test_get_put
|
16
16
|
cache = Heroic::LRUCache.new(1)
|
17
|
-
assert_nil cache.get(:
|
18
|
-
cache.put(:
|
19
|
-
assert_equal :
|
20
|
-
cache.put(:
|
21
|
-
assert_equal
|
22
|
-
assert_nil cache.get(:
|
17
|
+
assert_nil cache.get(:casey)
|
18
|
+
cache.put(:casey, :jones)
|
19
|
+
assert_equal :jones, cache.get(:casey)
|
20
|
+
cache.put(:april, :oneil)
|
21
|
+
assert_equal :oneil, cache.get(:april)
|
22
|
+
assert_nil cache.get(:casey)
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_exceptions
|
@@ -32,41 +32,41 @@ class LRUCacheTest < Test::Unit::TestCase
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
assert_raises RuntimeError do
|
35
|
-
cache.get(:
|
35
|
+
cache.get(:ooze)
|
36
36
|
end
|
37
37
|
@should_throw = false
|
38
|
-
assert_equal 4, cache.get(:
|
38
|
+
assert_equal 4, cache.get(:ooze)
|
39
39
|
end
|
40
40
|
|
41
41
|
def test_dynamic
|
42
42
|
@counter = 0
|
43
|
-
cache = Heroic::LRUCache.new(3) { |k| @counter += 1;
|
43
|
+
cache = Heroic::LRUCache.new(3) { |k| @counter += 1; k.to_s.length }
|
44
44
|
assert_equal 0, @counter
|
45
45
|
cache.verify!
|
46
|
-
assert_equal
|
46
|
+
assert_equal 3, cache.get(:leo); assert_equal 1, @counter
|
47
47
|
cache.verify!
|
48
|
-
assert_equal
|
48
|
+
assert_equal 3, cache.get(:leo); assert_equal 1, @counter
|
49
49
|
cache.verify!
|
50
|
-
assert_equal
|
50
|
+
assert_equal 6, cache.get(:donnie); assert_equal 2, @counter
|
51
51
|
cache.verify!
|
52
|
-
assert_equal
|
52
|
+
assert_equal 6, cache.get(:donnie); assert_equal 2, @counter
|
53
53
|
cache.verify!
|
54
|
-
assert_equal
|
54
|
+
assert_equal 5, cache.get(:mikey); assert_equal 3, @counter
|
55
55
|
cache.verify!
|
56
|
-
assert_equal
|
56
|
+
assert_equal 5, cache.get(:mikey); assert_equal 3, @counter
|
57
57
|
cache.verify!
|
58
58
|
# raph will push leo out of cache
|
59
|
-
assert_equal
|
59
|
+
assert_equal 4, cache.get(:raph); assert_equal 4, @counter
|
60
60
|
cache.verify!
|
61
|
-
assert_equal
|
61
|
+
assert_equal 4, cache.get(:raph); assert_equal 4, @counter
|
62
62
|
cache.verify!
|
63
63
|
# mikey and donnie remain in cache
|
64
|
-
assert_equal
|
64
|
+
assert_equal 5, cache.get(:mikey); assert_equal 4, @counter
|
65
65
|
cache.verify!
|
66
|
-
assert_equal
|
66
|
+
assert_equal 6, cache.get(:donnie); assert_equal 4, @counter
|
67
67
|
cache.verify!
|
68
68
|
# leo will have to be refetched
|
69
|
-
assert_equal
|
69
|
+
assert_equal 3, cache.get(:leo); assert_equal 5, @counter
|
70
70
|
cache.verify!
|
71
71
|
end
|
72
72
|
|
@@ -76,35 +76,34 @@ class LRUCacheTest < Test::Unit::TestCase
|
|
76
76
|
cache = Heroic::LRUCache.new(100) do |k|
|
77
77
|
sleep 1 # simulate slow generation, such as network I/O
|
78
78
|
@lock.synchronize { @counter += 1 }
|
79
|
-
k.to_s
|
79
|
+
k.to_s.length
|
80
80
|
end
|
81
81
|
# load the cache with things to read
|
82
|
-
cache.put(:
|
83
|
-
cache.put(:
|
84
|
-
cache.put(:c, 'c')
|
82
|
+
cache.put(:leo, 0)
|
83
|
+
cache.put(:donnie, 0)
|
85
84
|
start_time = Time.now
|
86
|
-
#
|
87
|
-
|
88
|
-
|
85
|
+
# Start threads to fetch in background. :leo and :donnie should return
|
86
|
+
# immediately, because the values are in the cache; :mikey and :raph should
|
87
|
+
# run concurrently; the second :raph should wait on the first :raph to
|
88
|
+
# complete.
|
89
|
+
threads = [:leo, :donnie, :mikey, :raph, :raph].map do |k|
|
90
|
+
Thread.new { cache.get(k) }
|
89
91
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# while background threads fetch, reading other keys should not be blocked
|
94
|
-
assert_equal 'a', cache.get(:a)
|
95
|
-
assert_equal 'b', cache.get(:b)
|
96
|
-
assert_equal 'c', cache.get(:c)
|
92
|
+
# Fetching values already in the cache should not block.
|
93
|
+
assert_equal 0, cache.get(:leo)
|
94
|
+
assert_equal 0, cache.get(:donnie)
|
97
95
|
assert_equal 0, @lock.synchronize { @counter }
|
98
|
-
#
|
99
|
-
assert_equal
|
100
|
-
assert_equal
|
96
|
+
# Fetching values being computed now will block until somebody computes them.
|
97
|
+
assert_equal 5, cache.get(:mikey)
|
98
|
+
assert_equal 4, cache.get(:raph)
|
101
99
|
assert_equal 2, @lock.synchronize { @counter }
|
102
|
-
#
|
103
|
-
assert_equal
|
104
|
-
assert_equal
|
100
|
+
# Fetching those same values should not trigger a recompute.
|
101
|
+
assert_equal 5, cache.get(:mikey)
|
102
|
+
assert_equal 4, cache.get(:raph)
|
105
103
|
assert_equal 2, @lock.synchronize { @counter }
|
106
|
-
#
|
107
|
-
|
104
|
+
# Let's wait for all threads to finish, then check the clock to make sure we
|
105
|
+
# only slept for a second.
|
106
|
+
threads.each { |t| t.join }
|
108
107
|
time_elapsed = (Time.now - start_time)
|
109
108
|
assert_in_delta time_elapsed, 1.0, 0.01
|
110
109
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heroic-sns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Ragheb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rack
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -30,29 +44,21 @@ dependencies:
|
|
30
44
|
requirements:
|
31
45
|
- - ~>
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.7
|
47
|
+
version: '1.7'
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - ~>
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.7
|
41
|
-
description:
|
42
|
-
Service (SNS)
|
43
|
-
|
54
|
+
version: '1.7'
|
55
|
+
description: |
|
56
|
+
Secure, lightweight Rack middleware for Amazon Simple Notification Service (SNS)
|
44
57
|
endpoints. SNS messages are intercepted, parsed, verified, and then passed along
|
45
|
-
|
46
|
-
to the web application via the ''sns.message'' environment key. Heroic::SNS has
|
47
|
-
no
|
48
|
-
|
58
|
+
to the web application via the 'sns.message' environment key. Heroic::SNS has no
|
49
59
|
dependencies besides Rack (specifically, the aws-sdk gem is not needed).
|
50
|
-
|
51
60
|
SNS message signatures are verified in order to reject forgeries and replay
|
52
|
-
|
53
61
|
attacks.
|
54
|
-
|
55
|
-
'
|
56
62
|
email: ben@benzado.com
|
57
63
|
executables: []
|
58
64
|
extensions: []
|
@@ -60,7 +66,7 @@ extra_rdoc_files: []
|
|
60
66
|
files:
|
61
67
|
- .gitignore
|
62
68
|
- .travis.yml
|
63
|
-
- CHANGELOG
|
69
|
+
- CHANGELOG.md
|
64
70
|
- Gemfile
|
65
71
|
- LICENSE
|
66
72
|
- README.md
|
@@ -93,12 +99,12 @@ require_paths:
|
|
93
99
|
- lib
|
94
100
|
required_ruby_version: !ruby/object:Gem::Requirement
|
95
101
|
requirements:
|
96
|
-
- -
|
102
|
+
- - '>='
|
97
103
|
- !ruby/object:Gem::Version
|
98
104
|
version: 1.8.7
|
99
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
106
|
requirements:
|
101
|
-
- -
|
107
|
+
- - '>='
|
102
108
|
- !ruby/object:Gem::Version
|
103
109
|
version: '0'
|
104
110
|
requirements: []
|
data/CHANGELOG
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
### 1.1.0 (June 20, 2013)
|
2
|
-
|
3
|
-
* Rework Cert logic - [@sbeckeriv]
|
4
|
-
|
5
|
-
### 1.0.1 (May 17, 2013)
|
6
|
-
|
7
|
-
* Gem housekeeping, based on [advice by dblock](http://code.dblock.org/your-first-ruby-gem)
|
8
|
-
|
9
|
-
### 1.0.0 (May 5, 2013)
|
10
|
-
|
11
|
-
* Initial public release - [@benzado]
|
12
|
-
|
13
|
-
[@benzado]: http://github.com/benzado
|