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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZGJmMTYxNjFmNmJlZGFkM2E4ZDMzN2I0ODY1ODJmYzc5ZWMxNTAxZA==
5
- data.tar.gz: !binary |-
6
- ODU4NTFjMTM2MTBjZTY0MWMzNDNjZGZhYTQ1YjdhZTE0OWEwODAzOQ==
7
- !binary "U0hBNTEy":
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/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  /capture
2
+ /html
2
3
  /pkg
3
4
  /tmp
4
5
  /Gemfile.lock
@@ -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 intercepted,
7
- parsed, verified, and then passed along via the `sns.message` environment key.
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 `sns.error`
10
- environment key. `Heroic::SNS::Endpoint` does not log any messages itself.
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 (to avoid
13
- forgeries) and stale messages are rejected (to avoid replay attacks).
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 there are
16
- no dependencies beside [rack][]. Specifically, Heroic::SNS *does not* depend on [aws-sdk][]. They will be friendly to each other, however, if you include both
17
- in a project.
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
- [aws-sdk]: https://github.com/aws/aws-sdk-ruby
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 `config.ru`
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 these
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 are
45
- handled by this endpoint. **A message is considered either "on-topic" or
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
- - a `Proc` which accepts an ARN as an argument and returns `true` or `false` for
52
- on-topic and off-topic ARNs, respectively.
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 `SubscribeURL`
59
- field of the SNS message, and your app is not notified.
60
- - If `false`, they are ignored; your app is also not notified.
61
- - If `nil`, there is no special handling and the message is passed along to your
62
- app.
63
-
64
- The default is `true`.
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 `true`, they topic is automatically re-subscribed by retrieving the URL in
69
- the `SubscribeURL` field of the SNS message, and your app is not notified.
70
- - If `false`, they are ignored and your app is also not notified.
71
- - If `nil`, there is no special handling and the message is passed along to your
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
- The default is `false`.
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 these
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 pass them
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 in your
85
- Rack environment under the `sns.message` key. If you are using Rails, your
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 POST to the
102
- controller action. Be careful not to disable it for more actions than you need.
103
- Be sure to disable any authentication checks for that action, too.
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, you
108
- should only include one instance of the Endpoint in your middleware stack, and
109
- ensure that its topic filter allows all the notifications you are interested in
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. Any
118
- notifications that do not match this filter are not passed along to your
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 a
122
- regular notification (meaning your app has an active subscription) *and* the
123
- message can be verified as authentic (by checking its signature), `Endpoint`
124
- will cancel the subscription by visiting the URL in the `UnsubscribeURL` field
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 notifications in
128
- your own code, simply pass `Proc.new { true }` as the topic filter, and all
129
- messages will be treated as on topic. Be aware that it is dangerous to leave
130
- `:auto_confirm` enabled with a permissive topic filter, as this will allow
131
- anyone to subscribe your web app to any SNS notification.
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
@@ -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.7'
31
+ s.add_runtime_dependency 'json', '~> 1.7'
31
32
  end
@@ -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
- def empty!
67
- @lock.synchronize do
68
- @store.empty!
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]
@@ -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 }
@@ -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 :subscribe_url, you can ignore this field.
91
+ # the +subscribe_url+, you can ignore this field.
81
92
  def token
82
93
  @msg['Token']
83
94
  end
84
95
 
85
- def ==(other_message)
86
- @msg == other_message.instance_variable_get(:@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 an exception if it is not valid.
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
@@ -1,5 +1,5 @@
1
1
  module Heroic
2
2
  module SNS
3
- VERSION = '1.1.0'
3
+ VERSION = '1.1.1'
4
4
  end
5
5
  end
@@ -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(:foo)
18
- cache.put(:foo, :bar)
19
- assert_equal :bar, cache.get(:foo)
20
- cache.put(:answer, 42)
21
- assert_equal 42, cache.get(:answer)
22
- assert_nil cache.get(:foo)
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(:foo)
35
+ cache.get(:ooze)
36
36
  end
37
37
  @should_throw = false
38
- assert_equal 4, cache.get(:foo)
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; "hello, #{k}." }
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 "hello, leo.", cache.get(:leo); assert_equal 1, @counter
46
+ assert_equal 3, cache.get(:leo); assert_equal 1, @counter
47
47
  cache.verify!
48
- assert_equal "hello, leo.", cache.get(:leo); assert_equal 1, @counter
48
+ assert_equal 3, cache.get(:leo); assert_equal 1, @counter
49
49
  cache.verify!
50
- assert_equal "hello, donnie.", cache.get(:donnie); assert_equal 2, @counter
50
+ assert_equal 6, cache.get(:donnie); assert_equal 2, @counter
51
51
  cache.verify!
52
- assert_equal "hello, donnie.", cache.get(:donnie); assert_equal 2, @counter
52
+ assert_equal 6, cache.get(:donnie); assert_equal 2, @counter
53
53
  cache.verify!
54
- assert_equal "hello, mikey.", cache.get(:mikey); assert_equal 3, @counter
54
+ assert_equal 5, cache.get(:mikey); assert_equal 3, @counter
55
55
  cache.verify!
56
- assert_equal "hello, mikey.", cache.get(:mikey); assert_equal 3, @counter
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 "hello, raph.", cache.get(:raph); assert_equal 4, @counter
59
+ assert_equal 4, cache.get(:raph); assert_equal 4, @counter
60
60
  cache.verify!
61
- assert_equal "hello, raph.", cache.get(:raph); assert_equal 4, @counter
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 "hello, mikey.", cache.get(:mikey); assert_equal 4, @counter
64
+ assert_equal 5, cache.get(:mikey); assert_equal 4, @counter
65
65
  cache.verify!
66
- assert_equal "hello, donnie.", cache.get(:donnie); assert_equal 4, @counter
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 "hello, leo.", cache.get(:leo); assert_equal 5, @counter
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(:a, 'a')
83
- cache.put(:b, 'b')
84
- cache.put(:c, 'c')
82
+ cache.put(:leo, 0)
83
+ cache.put(:donnie, 0)
85
84
  start_time = Time.now
86
- # start fetch in background
87
- td = Thread.new do
88
- cache.get(:d)
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
- te = Thread.new do
91
- cache.get(:e)
92
- end
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
- # now main thread should block until background items are fetched
99
- assert_equal "d", cache.get(:d)
100
- assert_equal "e", cache.get(:e)
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
- # reading the same values should be fast (they are cached)
103
- assert_equal "d", cache.get(:d)
104
- assert_equal "e", cache.get(:e)
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
- # look at time elapsed to make sure we didn't sleep more than once
107
- [td, te].each { |t| t.join }
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.0
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-06-25 00:00:00.000000000 Z
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.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.7
41
- description: ! 'Secure, lightweight Rack middleware for Amazon Simple Notification
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