rack-superfeedr 0.2.1 → 0.3.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.
- data/Gemfile +6 -6
- data/Gemfile.lock +43 -43
- data/README.rdoc +5 -5
- data/VERSION +1 -1
- data/examples/sinatra_app.rb +26 -12
- data/lib/rack-superfeedr.rb +168 -156
- data/rack-superfeedr.gemspec +9 -6
- data/test/helper.rb +0 -3
- data/test/test_rack-superfeedr.rb +128 -4
- metadata +25 -9
data/Gemfile
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
source
|
1
|
+
source 'http://rubygems.org'
|
2
2
|
# Add dependencies required to use your gem here.
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
5
5
|
gem 'rack'
|
6
6
|
gem 'nokogiri', ">=1.6.1"
|
7
|
-
gem 'typhoeus', ">=0.6.7"
|
8
|
-
|
9
7
|
|
10
8
|
# Add dependencies to develop your gem here.
|
11
9
|
# Include everything needed to run rake, tests, features, etc.
|
12
10
|
group :development do
|
13
|
-
gem
|
14
|
-
gem
|
15
|
-
gem
|
11
|
+
gem 'shoulda', '>= 0'
|
12
|
+
gem 'turn'
|
13
|
+
gem 'bundler', '>= 1.0.0'
|
14
|
+
gem 'jeweler', '>= 1.6.4'
|
15
|
+
gem 'thin'
|
16
16
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,36 +1,34 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (4.0.
|
5
|
-
i18n (~> 0.6, >= 0.6.
|
4
|
+
activesupport (4.0.8)
|
5
|
+
i18n (~> 0.6, >= 0.6.9)
|
6
6
|
minitest (~> 4.2)
|
7
7
|
multi_json (~> 1.3)
|
8
8
|
thread_safe (~> 0.1)
|
9
9
|
tzinfo (~> 0.3.37)
|
10
|
-
addressable (2.3.
|
11
|
-
|
10
|
+
addressable (2.3.6)
|
11
|
+
ansi (1.4.3)
|
12
12
|
builder (3.2.2)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
faraday (0.
|
18
|
-
multipart-post (
|
19
|
-
|
20
|
-
|
21
|
-
github_api (0.11.1)
|
13
|
+
daemons (1.1.9)
|
14
|
+
descendants_tracker (0.0.4)
|
15
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
16
|
+
eventmachine (1.0.3)
|
17
|
+
faraday (0.9.0)
|
18
|
+
multipart-post (>= 1.2, < 3)
|
19
|
+
git (1.2.7)
|
20
|
+
github_api (0.12.0)
|
22
21
|
addressable (~> 2.3)
|
23
|
-
descendants_tracker (~> 0.0.
|
22
|
+
descendants_tracker (~> 0.0.4)
|
24
23
|
faraday (~> 0.8, < 0.10)
|
25
|
-
hashie (>=
|
24
|
+
hashie (>= 3.2)
|
26
25
|
multi_json (>= 1.7.5, < 2.0)
|
27
|
-
nokogiri (~> 1.6.
|
26
|
+
nokogiri (~> 1.6.3)
|
28
27
|
oauth2
|
29
|
-
hashie (2.0
|
30
|
-
highline (1.6.
|
31
|
-
|
32
|
-
|
33
|
-
jeweler (2.0.0)
|
28
|
+
hashie (3.2.0)
|
29
|
+
highline (1.6.21)
|
30
|
+
i18n (0.6.11)
|
31
|
+
jeweler (2.0.1)
|
34
32
|
builder
|
35
33
|
bundler (>= 1.0)
|
36
34
|
git (>= 1.2.5)
|
@@ -40,38 +38,39 @@ GEM
|
|
40
38
|
rake
|
41
39
|
rdoc
|
42
40
|
json (1.8.1)
|
43
|
-
jwt (0.
|
44
|
-
|
45
|
-
mime-types (1.25.1)
|
46
|
-
mini_portile (0.5.2)
|
41
|
+
jwt (1.0.0)
|
42
|
+
mini_portile (0.6.0)
|
47
43
|
minitest (4.7.5)
|
48
|
-
multi_json (1.
|
44
|
+
multi_json (1.10.1)
|
49
45
|
multi_xml (0.5.5)
|
50
|
-
multipart-post (
|
51
|
-
nokogiri (1.6.1)
|
52
|
-
mini_portile (
|
53
|
-
oauth2 (0.
|
54
|
-
faraday (
|
55
|
-
|
56
|
-
|
57
|
-
multi_json (~> 1.0)
|
46
|
+
multipart-post (2.0.0)
|
47
|
+
nokogiri (1.6.3.1)
|
48
|
+
mini_portile (= 0.6.0)
|
49
|
+
oauth2 (1.0.0)
|
50
|
+
faraday (>= 0.8, < 0.10)
|
51
|
+
jwt (~> 1.0)
|
52
|
+
multi_json (~> 1.3)
|
58
53
|
multi_xml (~> 0.5)
|
59
54
|
rack (~> 1.2)
|
60
55
|
rack (1.5.2)
|
61
|
-
rake (10.
|
56
|
+
rake (10.3.2)
|
62
57
|
rdoc (4.1.1)
|
63
58
|
json (~> 1.4)
|
64
59
|
shoulda (3.5.0)
|
65
60
|
shoulda-context (~> 1.0, >= 1.0.1)
|
66
61
|
shoulda-matchers (>= 1.4.1, < 3.0)
|
67
|
-
shoulda-context (1.1
|
68
|
-
shoulda-matchers (2.
|
62
|
+
shoulda-context (1.2.1)
|
63
|
+
shoulda-matchers (2.6.2)
|
69
64
|
activesupport (>= 3.0.0)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
65
|
+
thin (1.6.2)
|
66
|
+
daemons (>= 1.0.9)
|
67
|
+
eventmachine (>= 1.0.0)
|
68
|
+
rack (>= 1.0.0)
|
69
|
+
thread_safe (0.3.4)
|
70
|
+
turn (0.9.7)
|
71
|
+
ansi
|
72
|
+
minitest (~> 4)
|
73
|
+
tzinfo (0.3.40)
|
75
74
|
|
76
75
|
PLATFORMS
|
77
76
|
ruby
|
@@ -82,4 +81,5 @@ DEPENDENCIES
|
|
82
81
|
nokogiri (>= 1.6.1)
|
83
82
|
rack
|
84
83
|
shoulda
|
85
|
-
|
84
|
+
thin
|
85
|
+
turn
|
data/README.rdoc
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
A gem that provides a rack middleware to interact with {Superfeedr}[http://superfeedr.com/]'s PubSubHubbub API. It let you *subscribe*, *unsubscribe* and *receive* incoming feed notifications.
|
4
4
|
This should work with any Rack-compatible framework, as well as fully managed platforms like {Heroku}[http://www.heroku.com/].
|
5
5
|
|
6
|
+
Warning : version 0.3 (July 2014) introduces a lot of breaking changes compared to previous versions. Upgrade carefully!
|
7
|
+
|
6
8
|
== Installing
|
7
9
|
|
8
10
|
gem install rack-superfeedr
|
@@ -11,7 +13,7 @@ This should work with any Rack-compatible framework, as well as fully managed pl
|
|
11
13
|
|
12
14
|
You first need a {subscriber account}[http://superfeedr.com/subscriber] with Superfeedr.
|
13
15
|
|
14
|
-
*Warning* : your web application needs to be accessible for Superfeedr to send notifications. If you want to test it locally, we suggest you use it with a tool like {
|
16
|
+
*Warning* : your web application needs to be accessible for Superfeedr to send notifications. If you want to test it locally, we suggest you use it with a tool like {Passsageway}[https://www.runscope.com/docs/passageway] which will make your local port accessible from the outside world.
|
15
17
|
|
16
18
|
=== Sinatra
|
17
19
|
|
@@ -23,12 +25,10 @@ Tested with Rails 3.2, but should work with any Rails version that supports Rack
|
|
23
25
|
|
24
26
|
Install the gem (using bundle), and then in <code>config/application.rb</code>, inside the <code>class Application < Rails::Application</code> block, just add the following line.
|
25
27
|
|
26
|
-
config.middleware.use Rack::Superfeedr
|
27
|
-
|
28
|
+
config.middleware.use Rack::Superfeedr do |superfeedr|
|
29
|
+
...
|
28
30
|
end
|
29
31
|
|
30
|
-
You can then access the Superfeedr object anywhere in your rails application with <code>YOUR_APP::Application::Superfeedr</code>.
|
31
|
-
|
32
32
|
|
33
33
|
== Contributing
|
34
34
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/examples/sinatra_app.rb
CHANGED
@@ -1,30 +1,44 @@
|
|
1
1
|
require 'sinatra'
|
2
|
-
require(File.join(File.dirname(__FILE__), '..', 'lib', 'rack-superfeedr.rb'))
|
2
|
+
require (File.join(File.dirname(__FILE__), '..', 'lib', 'rack-superfeedr.rb'))
|
3
3
|
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# Configure the middleware
|
6
|
+
Rack::Superfeedr.host = "37cb3f2fe113.a.passageway.io"
|
7
|
+
Rack::Superfeedr.login = "demo"
|
8
|
+
Rack::Superfeedr.password = "8ac38a53cc32f71a6445e880f76fc865"
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
|
11
|
+
use Rack::Superfeedr do |superfeedr|
|
12
|
+
|
13
|
+
superfeedr.on_notification do |feed_id, body, url, request|
|
14
|
+
puts "------"
|
15
|
+
puts feed_id # You need to have supplied one upon subscription
|
16
|
+
puts "------"
|
17
|
+
puts body # The body of the notification, a JSON or ATOM string, based on the subscription. Use the Rack::Request object for details
|
18
|
+
puts "------"
|
19
|
+
puts url # The feed url
|
20
|
+
puts "------"
|
10
21
|
end
|
11
22
|
|
12
23
|
end
|
13
24
|
|
25
|
+
# Maybe serve the data you saved from Superfeedr's handler.
|
14
26
|
get '/hi' do
|
15
|
-
"Hello World!"
|
27
|
+
"Hello World!"
|
16
28
|
end
|
17
29
|
|
30
|
+
# Subscription
|
31
|
+
# Will subscribe to "http://push-pub.appspot.com/feed" and retrieve past items
|
32
|
+
# The block will yield the result: its body (useful for error or when retrieveing, a success flag and the Net::HTTP::Post)
|
18
33
|
get '/subscribe' do
|
19
|
-
|
20
|
-
|
21
|
-
settings.superfeedr.error
|
22
|
-
else
|
23
|
-
'Subscribed'
|
34
|
+
Rack::Superfeedr.subscribe("http://push-pub.appspot.com/feed", 9999, {retrieve: true }) do |body, success, response|
|
35
|
+
body
|
24
36
|
end
|
25
37
|
end
|
26
38
|
|
39
|
+
# Unsubscription
|
40
|
+
# Wull unsubscribe
|
27
41
|
get '/unsubscribe' do
|
28
|
-
|
42
|
+
Rack::Superfeedr.unsubscribe("http://push-pub.appspot.com/feed")
|
29
43
|
end
|
30
44
|
|
data/lib/rack-superfeedr.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'base64'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'nokogiri'
|
5
|
-
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
6
4
|
|
7
5
|
module Rack
|
8
6
|
|
@@ -12,85 +10,79 @@ module Rack
|
|
12
10
|
# using the PubSubHubbub API.
|
13
11
|
class Superfeedr
|
14
12
|
|
15
|
-
|
13
|
+
@@superfeedr_endpoint = "https://push.superfeedr.com/"
|
14
|
+
@@port = 80
|
15
|
+
@@host = 'my-app.com'
|
16
|
+
@@base_path = '/superfeedr/feed/'
|
17
|
+
@@scheme = 'http'
|
18
|
+
@@login = nil
|
19
|
+
@@password = nil
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
# Useful when a subscription or unsubscription request fails.
|
20
|
-
def error
|
21
|
-
@error
|
21
|
+
def self.superfeedr_endpoint= _superfeedr_endpoint
|
22
|
+
@@superfeedr_endpoint = _superfeedr_endpoint
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# The optional block will be called to let you confirm the subscription (or not).
|
28
|
-
# It returns true if the subscription was successful (or will be confirmed if you used async => true in the options), false otherwise.
|
29
|
-
# You can also pass an opts third argument that will be merged with the options used in Typhoeus's Request (https://github.com/dbalatero/typhoeus)
|
30
|
-
# A useful option is :verbose => true for example.
|
31
|
-
# If you supply a retrieve option, the content of the feed will be retrieved from Superfeedr as well and your on_notification callback will
|
32
|
-
# will be called with the content of the feed.
|
33
|
-
def subscribe(url, id = nil, opts = {}, &block)
|
34
|
-
feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
|
35
|
-
if block
|
36
|
-
@verifications[feed_id] ||= {}
|
37
|
-
@verifications[feed_id]['subscribe'] = block
|
38
|
-
end
|
39
|
-
endpoint = opts[:hub] || SUPERFEEDR_ENDPOINT
|
40
|
-
opts.delete(:hub)
|
25
|
+
def self.port= _port
|
26
|
+
@@port = _port
|
27
|
+
end
|
41
28
|
|
42
|
-
|
43
|
-
|
29
|
+
def self.host= _host
|
30
|
+
@@host = _host
|
31
|
+
end
|
44
32
|
|
33
|
+
def self.base_path= _base_path
|
34
|
+
@@base_path = _base_path
|
35
|
+
end
|
45
36
|
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
def self.scheme= _scheme
|
38
|
+
@@scheme = _scheme
|
39
|
+
end
|
49
40
|
|
41
|
+
def self.login= _login
|
42
|
+
@@login = _login
|
43
|
+
end
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
:'hub.topic' => url,
|
55
|
-
:'hub.callback' => generate_callback(url, feed_id)
|
56
|
-
},
|
57
|
-
:headers => {
|
58
|
-
:Accept => @params[:format] == "json" ? "application/json" : "application/atom+xml"
|
59
|
-
}
|
60
|
-
})
|
45
|
+
def self.password= _password
|
46
|
+
@@password = _password
|
47
|
+
end
|
61
48
|
|
62
|
-
|
63
|
-
|
49
|
+
##
|
50
|
+
# Subscribe you to a url. id is optional but strongly recommanded has a unique identifier for this url. It will be used to help you identify which feed
|
51
|
+
# is concerned by a notification.
|
52
|
+
# A 3rd options argument can be supplied with
|
53
|
+
# - retrive => true if you want to retrieve the previous items in the feed
|
54
|
+
# - format => 'json' or 'atom' to specify the format of the notifications, defaults to atom
|
55
|
+
# - secret => a secret string used to compyte HMAC signatures so you can check that the data is coming from Superfeedr
|
56
|
+
# - sync => true (defaults to false) if you want to perfrom a verification of intent syncrhonously
|
57
|
+
# - async => true (defaults to false) if you want to perfrom a verification of intent asyncrhonously
|
58
|
+
# - hub => if you want to use an explicit hub, defaults to Superfeedr's http://push.superfeedr.com
|
59
|
+
# It yields 3 arguments to a block:
|
60
|
+
# - body of the response (useful if you used the retrieve option)
|
61
|
+
# - success flag
|
62
|
+
# - response (useful to debug failed requests mostly)
|
63
|
+
def self.subscribe(url, id = nil, opts = {}, &blk)
|
64
|
+
endpoint = opts[:hub] || @@superfeedr_endpoint
|
65
|
+
request = prep_request(url, id, endpoint, opts)
|
66
|
+
|
67
|
+
if opts[:retrieve]
|
68
|
+
request['retrieve'] = true
|
69
|
+
end
|
70
|
+
|
71
|
+
if opts[:format] == "json"
|
72
|
+
request['format'] = "json"
|
64
73
|
end
|
65
74
|
|
66
|
-
opts[:
|
67
|
-
|
68
|
-
response = ::Typhoeus::Request.post(endpoint, opts)
|
69
|
-
|
70
|
-
@error = response.body
|
71
|
-
if !retrieve
|
72
|
-
@params[:async] && response.code == 202 || response.code == 204 # We return true to indicate the status.
|
75
|
+
if opts[:secret]
|
76
|
+
request['hub.secret'] = opts[:secret]
|
73
77
|
else
|
74
|
-
|
75
|
-
if response.code == 200
|
76
|
-
|
77
|
-
if @params[:format] != "json"
|
78
|
-
content = Nokogiri.XML(response.body)
|
79
|
-
else
|
80
|
-
content = JSON.parse(response.body)
|
81
|
-
end
|
82
|
-
# Let's now send that data back to the user.
|
83
|
-
if defined? Hashie::Mash
|
84
|
-
info = Hashie::Mash.new(req: req, body: body)
|
85
|
-
end
|
86
|
-
if !@callback.call(content, feed_id, info)
|
87
|
-
# We need to unsubscribe the user
|
88
|
-
end
|
89
|
-
true
|
90
|
-
else
|
91
|
-
false
|
92
|
-
end
|
78
|
+
request['hub.secret'] = "WHAT DO WE PICK? A UNIQUE SCRET THE CALLBACK? SO WE CAN USE THAT ON NOTIFS?"
|
93
79
|
end
|
80
|
+
|
81
|
+
request['hub.mode'] = 'subscribe'
|
82
|
+
|
83
|
+
response = http_post(endpoint, request)
|
84
|
+
|
85
|
+
blk.call(response.body, opts[:async] && Integer(response.code) == 202 || Integer(response.code) == 204 || opts[:retrieve] && Integer(response.code) == 200, response) if blk
|
94
86
|
end
|
95
87
|
|
96
88
|
##
|
@@ -98,108 +90,94 @@ module Rack
|
|
98
90
|
# The optional block will be called to let you confirm the subscription (or not). This is not applicable for if you use params[:async] => true
|
99
91
|
# It returns true if the unsubscription was successful (or will be confirmed if you used async => true in the options), false otherwise
|
100
92
|
# You can also pass an opts third argument that will be merged with the options used in Typhoeus's Request (https://github.com/dbalatero/typhoeus)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
93
|
+
|
94
|
+
##
|
95
|
+
# Subscribe you to a url. id needs to match the id you used to subscribe.
|
96
|
+
# A 3rd options argument can be supplied with
|
97
|
+
# - sync => true (defaults to false) if you want to perfrom a verification of intent syncrhonously
|
98
|
+
# - async => true (defaults to false) if you want to perfrom a verification of intent asyncrhonously
|
99
|
+
# - hub => if you want to use an explicit hub, defaults to Superfeedr's http://push.superfeedr.com
|
100
|
+
# It yields 3 arguments to a block:
|
101
|
+
# - body of the response (useful to debug failed notifications)
|
102
|
+
# - success flag
|
103
|
+
# - response (useful to debug failed requests mostly)
|
104
|
+
def self.unsubscribe(url, id = nil, opts = {}, &blk)
|
105
|
+
endpoint = opts[:hub] || @@superfeedr_endpoint
|
106
|
+
request = prep_request(url, id, endpoint, opts)
|
107
|
+
|
108
|
+
request['hub.mode'] = 'unsubscribe'
|
109
|
+
|
110
|
+
response = http_post(endpoint, request)
|
111
|
+
|
112
|
+
blk.call(response.body, opts[:async] && Integer(response.code) == 202 || Integer(response.code) == 204, response) if blk
|
120
113
|
end
|
121
114
|
|
122
115
|
##
|
123
|
-
# This allows you to define what happens with the notifications. The block passed in argument is called for each notification, with
|
124
|
-
# -
|
125
|
-
# -
|
116
|
+
# This allows you to define what happens with the notifications. The block passed in argument is called for each notification, with 4 arguments
|
117
|
+
# - feed_id (used in subscriptions)
|
118
|
+
# - body (Atom or JSON) based on subscription
|
119
|
+
# - url (optional... if the hub supports that, Superfeedr does)
|
120
|
+
# - Rack::Request object. Useful for debugging and checking signatures
|
126
121
|
def on_notification(&block)
|
127
122
|
@callback = block
|
128
123
|
end
|
129
124
|
|
130
125
|
##
|
131
|
-
#
|
132
|
-
#
|
133
|
-
# - :
|
134
|
-
# -
|
135
|
-
# -
|
136
|
-
# -
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
126
|
+
# This allows you to define what happens with verification of intents
|
127
|
+
# It's a block called with
|
128
|
+
# - mode: subscribe|unsubscribe
|
129
|
+
# - Feed id (if available/supplied upon subscription)
|
130
|
+
# - Feed url
|
131
|
+
# - request (the Rack::Request object, should probably not be used, except for debugging)
|
132
|
+
# If the block returns true, subscription will be confirmed
|
133
|
+
# If it returns false, it will be denied
|
134
|
+
def on_verification(&block)
|
135
|
+
@verification = block
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Initializes the Rack Middleware
|
140
|
+
# Make sure you define the following class attribues before that:
|
141
|
+
# Rack::Superfeedr.superfeedr_endpoint => https://push.superfeedr.com, defaults (do not change!)
|
142
|
+
# Rack::Superfeedr.host => Host for your application, used to build callback urls
|
143
|
+
# Rack::Superfeedr.port => Port for your application, used to build callback urls, defaults to
|
144
|
+
# Rack::Superfeedr.base_path => Base path for callback urls. Defauls to '/superfeedr/feed/'
|
145
|
+
# Rack::Superfeedr.scheme => Scheme to build callback urls, defaults to 'http'
|
146
|
+
# Rack::Superfeedr.login => Superfeedr login
|
147
|
+
# Rack::Superfeedr.password => Superfeedr password
|
148
|
+
def initialize(app, &block)
|
147
149
|
@app = app
|
148
|
-
|
150
|
+
reset
|
149
151
|
block.call(self)
|
150
152
|
self
|
151
153
|
end
|
152
154
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
155
|
+
##
|
156
|
+
# Resets.
|
157
|
+
def reset
|
158
|
+
@callback = Proc.new { |feed_id, body, url, request|
|
159
|
+
# Nothing to do by default!
|
160
|
+
}
|
161
|
+
@verification = Proc.new { |mode, feed_id, url, request|
|
162
|
+
true # Accept all by default!
|
163
|
+
}
|
162
164
|
end
|
163
165
|
|
166
|
+
##
|
167
|
+
# Called by Rack!
|
164
168
|
def call(env)
|
165
169
|
req = Rack::Request.new(env)
|
166
|
-
if env['REQUEST_METHOD'] == 'GET' && feed_id = env['PATH_INFO'].match(/#{
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
Rack::Response.new(req.params['hub.challenge'], 200).finish
|
172
|
-
else
|
173
|
-
Rack::Response.new("not valid", 404).finish
|
174
|
-
end
|
175
|
-
else
|
176
|
-
# By default, we accept all
|
170
|
+
if env['REQUEST_METHOD'] == 'GET' && feed_id = env['PATH_INFO'].match(/#{@@base_path}(.*)/)
|
171
|
+
puts "----"
|
172
|
+
puts req.params['hub.mode'], feed_id[1], req.params['hub.topic']
|
173
|
+
puts "----"
|
174
|
+
if @verification.call(req.params['hub.mode'], feed_id[1], req.params['hub.topic'], req)
|
177
175
|
Rack::Response.new(req.params['hub.challenge'], 200).finish
|
178
|
-
end
|
179
|
-
elsif env['REQUEST_METHOD'] == 'POST' && feed_id = env['PATH_INFO'].match(/#{@base_path}(.*)/)
|
180
|
-
# Notification
|
181
|
-
content = nil
|
182
|
-
# Body is a stream, not a string, so capture it once and save it
|
183
|
-
body = req.body.read
|
184
|
-
if env["CONTENT_TYPE"]
|
185
|
-
content_type = env["CONTENT_TYPE"].split(";").first
|
186
176
|
else
|
187
|
-
|
188
|
-
end
|
189
|
-
if content_type == "application/json"
|
190
|
-
# Let's parse the body as JSON
|
191
|
-
content = JSON.parse(body)
|
192
|
-
elsif content_type == "application/atom+xml"
|
193
|
-
# Let's parse the body as ATOM using nokogiri
|
194
|
-
content = Nokogiri.XML(body)
|
195
|
-
end
|
196
|
-
# Let's now send that data back to the user.
|
197
|
-
if defined? Hashie::Mash
|
198
|
-
info = Hashie::Mash.new(req: req, body: body)
|
199
|
-
end
|
200
|
-
if !@callback.call(content, feed_id[1], info)
|
201
|
-
# We need to unsubscribe the user
|
177
|
+
Rack::Response.new("not valid", 404).finish
|
202
178
|
end
|
179
|
+
elsif env['REQUEST_METHOD'] == 'POST' && feed_id = env['PATH_INFO'].match(/#{@@base_path}(.*)/)
|
180
|
+
@callback.call(feed_id[1], req.body.read, req.env['HTTP_X_PUBSUBHUBBUB_TOPIC'], req)
|
203
181
|
Rack::Response.new("Thanks!", 200).finish
|
204
182
|
else
|
205
183
|
@app.call(env)
|
@@ -208,11 +186,45 @@ module Rack
|
|
208
186
|
|
209
187
|
protected
|
210
188
|
|
211
|
-
def
|
212
|
-
|
213
|
-
|
189
|
+
def self.prep_request(url, id, endpoint, opts)
|
190
|
+
feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
|
191
|
+
|
192
|
+
request = {
|
193
|
+
'hub.topic' => url,
|
194
|
+
'hub.callback' => generate_callback(url, feed_id)
|
195
|
+
}
|
196
|
+
|
197
|
+
if endpoint == @@superfeedr_endpoint && @@login && @@password
|
198
|
+
request['authorization'] = Base64.encode64( "#{@@login}:#{@@password}" ).chomp
|
199
|
+
end
|
200
|
+
|
201
|
+
if opts[:async]
|
202
|
+
request['hub.verify'] = 'async'
|
203
|
+
end
|
204
|
+
|
205
|
+
if opts[:sync]
|
206
|
+
request['hub.verify'] = 'sync'
|
207
|
+
end
|
208
|
+
|
209
|
+
request
|
214
210
|
end
|
215
211
|
|
216
|
-
|
212
|
+
def self.http_post(url, opts)
|
213
|
+
uri = URI.parse URI.encode(url)
|
214
|
+
uri.path=='/' if uri.path.empty?
|
215
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
216
|
+
http.use_ssl = true
|
217
|
+
request = Net::HTTP::Post.new uri.request_uri
|
218
|
+
request.set_form_data (opts||{})
|
219
|
+
http.request(request)
|
220
|
+
end
|
217
221
|
|
222
|
+
def self.generate_callback(url, feed_id)
|
223
|
+
if @@scheme == "https"
|
224
|
+
URI::HTTPS.build({:scheme => @@scheme, :host => @@host, :path => "#{@@base_path}#{feed_id}", :port => @@port }).to_s
|
225
|
+
else
|
226
|
+
URI::HTTP.build({:scheme => @@scheme, :host => @@host, :path => "#{@@base_path}#{feed_id}", :port => @@port }).to_s
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
218
230
|
end
|
data/rack-superfeedr.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "rack-superfeedr"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["julien51"]
|
12
|
-
s.date = "2014-
|
12
|
+
s.date = "2014-07-29"
|
13
13
|
s.description = "A gem that provides a rack middleware to interract with Superfeedr's API. "
|
14
14
|
s.email = "julien@superfeedr.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |s|
|
|
33
33
|
s.homepage = "http://rubygems.org/gems/rack-superfeedr"
|
34
34
|
s.licenses = ["MIT"]
|
35
35
|
s.require_paths = ["lib"]
|
36
|
-
s.rubygems_version = "1.8.
|
36
|
+
s.rubygems_version = "1.8.23.2"
|
37
37
|
s.summary = "A gem that provides a rack middleware to interract with Superfeedr's API."
|
38
38
|
|
39
39
|
if s.respond_to? :specification_version then
|
@@ -42,25 +42,28 @@ Gem::Specification.new do |s|
|
|
42
42
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
43
43
|
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
44
44
|
s.add_runtime_dependency(%q<nokogiri>, [">= 1.6.1"])
|
45
|
-
s.add_runtime_dependency(%q<typhoeus>, [">= 0.6.7"])
|
46
45
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
46
|
+
s.add_development_dependency(%q<turn>, [">= 0"])
|
47
47
|
s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
|
48
48
|
s.add_development_dependency(%q<jeweler>, [">= 1.6.4"])
|
49
|
+
s.add_development_dependency(%q<thin>, [">= 0"])
|
49
50
|
else
|
50
51
|
s.add_dependency(%q<rack>, [">= 0"])
|
51
52
|
s.add_dependency(%q<nokogiri>, [">= 1.6.1"])
|
52
|
-
s.add_dependency(%q<typhoeus>, [">= 0.6.7"])
|
53
53
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
54
|
+
s.add_dependency(%q<turn>, [">= 0"])
|
54
55
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
55
56
|
s.add_dependency(%q<jeweler>, [">= 1.6.4"])
|
57
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
56
58
|
end
|
57
59
|
else
|
58
60
|
s.add_dependency(%q<rack>, [">= 0"])
|
59
61
|
s.add_dependency(%q<nokogiri>, [">= 1.6.1"])
|
60
|
-
s.add_dependency(%q<typhoeus>, [">= 0.6.7"])
|
61
62
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
63
|
+
s.add_dependency(%q<turn>, [">= 0"])
|
62
64
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
63
65
|
s.add_dependency(%q<jeweler>, [">= 1.6.4"])
|
66
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
64
67
|
end
|
65
68
|
end
|
66
69
|
|
data/test/helper.rb
CHANGED
@@ -1,7 +1,131 @@
|
|
1
|
-
require '
|
1
|
+
require 'rack'
|
2
|
+
require_relative 'helper.rb'
|
3
|
+
|
4
|
+
# To run tests locally, we're using runscope's passageway which proxies requests inside the firewall. (make sure you bind to port 4567)
|
5
|
+
HOST = '37cb3f2fe113.a.passageway.io'
|
6
|
+
PORT = 80
|
7
|
+
# Also, we need superfeedr credentials.
|
8
|
+
LOGIN = 'demo'
|
9
|
+
PASSWORD = '8ac38a53cc32f71a6445e880f76fc865'
|
10
|
+
|
11
|
+
|
12
|
+
class MyRackApp
|
13
|
+
def call(env)
|
14
|
+
puts env.inspect
|
15
|
+
[ 200, {'Content-Type' => 'text/plain'}, ['hello world'] ]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def notified(url, feed_id, details)
|
20
|
+
puts url, feed_id, details
|
21
|
+
end
|
22
|
+
|
23
|
+
# Run an app in a thread
|
24
|
+
Thread.new do
|
25
|
+
Rack::Handler::WEBrick.run(Rack::Superfeedr.new(MyRackApp.new) do |superfeedr|
|
26
|
+
|
27
|
+
superfeedr.on_verification do |mode, feed_id, url, request|
|
28
|
+
if mode == 'subscribe' && feed_id == 'accept-subscribe'
|
29
|
+
true
|
30
|
+
elsif mode == 'unsubscribe' && feed_id == 'accept-unsubscribe'
|
31
|
+
true
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
superfeedr.on_notification do |url, feed_id, details|
|
38
|
+
notified(url, feed_id, details)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end, :Port => 4567)
|
43
|
+
end
|
44
|
+
sleep 3
|
45
|
+
|
46
|
+
# Configure the middleware
|
47
|
+
Rack::Superfeedr.host = HOST
|
48
|
+
Rack::Superfeedr.port = PORT
|
49
|
+
Rack::Superfeedr.login = LOGIN
|
50
|
+
Rack::Superfeedr.password = PASSWORD
|
2
51
|
|
3
52
|
class TestRackSuperfeedr < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
6
|
-
|
53
|
+
|
54
|
+
context "Subscribing" do
|
55
|
+
|
56
|
+
should "yield true with a simple subscribe" do
|
57
|
+
Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', '12345') do |body, success, response|
|
58
|
+
success || flunk("Fail")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should "support sync mode and call the verification callback before yielding true" do
|
63
|
+
Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', 'accept-subscribe', {:sync => true}) do |body, success, response|
|
64
|
+
puts body
|
65
|
+
success || flunk("Fail")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
should "support sync mode and call the verification callback before yielding false if verification fails" do
|
70
|
+
Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', 'refuse-subscribe', {:sync => true}) do |body, success, response|
|
71
|
+
!success || flunk('Fail')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
should "support async mode and yield true" do
|
76
|
+
Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', 'refuse-subscribe', {:async => true}) do |body, success, response|
|
77
|
+
success || flunk("Fail")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
should "return the content of Atom subscriptions with retrieve" do
|
82
|
+
Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', '1234', {:retrieve => true}) do |body, success, response|
|
83
|
+
assert_equal "application/atom+xml", response['Content-Type']
|
84
|
+
success || flunk("Fail")
|
85
|
+
assert body # Some XML
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
should "return the content of Json subscriptions with retrieve" do
|
90
|
+
response = Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', '1234', {:format => "json", :retrieve => true}) do |body, success, response|
|
91
|
+
assert_equal "application/json; charset=utf-8", response['Content-Type']
|
92
|
+
success || flunk("Fail")
|
93
|
+
assert body
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
context "Unsubscribing" do
|
100
|
+
should 'successfully unsubscribe with 204 when not using sync nor asyc' do
|
101
|
+
Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', '12345') do |body, success, response|
|
102
|
+
success || flunk("Fail")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
should 'successfully unsubscribe with 204 when using sync when verification yields true' do
|
107
|
+
Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', 'accept-unsubscribe', {:sync => true}) do |body, success, response|
|
108
|
+
success || flunk("Fail")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
should 'fail to unsubscribe with 204 when using sync when verification yields false' do
|
113
|
+
Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', 'refuse-unsubscribe', {:sync => true}) do |body, success, response|
|
114
|
+
!success || flunk('Fail')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
should 'return 202 when using async' do
|
119
|
+
Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', 'accept-unsubscribe', {:async => true}) do |body, success, response|
|
120
|
+
success || flunk("Fail")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "Notifications" do
|
126
|
+
should 'handle json notifications'
|
127
|
+
should 'handle atom notifications'
|
128
|
+
should 'handle error notification (with no entry in them)'
|
129
|
+
end
|
130
|
+
|
7
131
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-superfeedr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -44,23 +44,23 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 1.6.1
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: shoulda
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
51
51
|
- - ! '>='
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0
|
54
|
-
type: :
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
none: false
|
58
58
|
requirements:
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0
|
61
|
+
version: '0'
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
63
|
+
name: turn
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
66
66
|
requirements:
|
@@ -107,6 +107,22 @@ dependencies:
|
|
107
107
|
- - ! '>='
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: 1.6.4
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: thin
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
110
126
|
description: ! 'A gem that provides a rack middleware to interract with Superfeedr''s
|
111
127
|
API. '
|
112
128
|
email: julien@superfeedr.com
|
@@ -143,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
143
159
|
version: '0'
|
144
160
|
segments:
|
145
161
|
- 0
|
146
|
-
hash:
|
162
|
+
hash: 1778305000862886852
|
147
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
164
|
none: false
|
149
165
|
requirements:
|
@@ -152,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
168
|
version: '0'
|
153
169
|
requirements: []
|
154
170
|
rubyforge_project:
|
155
|
-
rubygems_version: 1.8.
|
171
|
+
rubygems_version: 1.8.23.2
|
156
172
|
signing_key:
|
157
173
|
specification_version: 3
|
158
174
|
summary: A gem that provides a rack middleware to interract with Superfeedr's API.
|