pachube-stream 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +15 -0
- data/README.rdoc +109 -0
- data/Rakefile +2 -0
- data/autotest/discover.rb +1 -0
- data/examples/subscribe.rb +32 -0
- data/lib/pachube-stream/client.rb +80 -0
- data/lib/pachube-stream/connection.rb +117 -0
- data/lib/pachube-stream/html_request.rb +15 -0
- data/lib/pachube-stream/methods.rb +29 -0
- data/lib/pachube-stream/request.rb +51 -0
- data/lib/pachube-stream/version.rb +3 -0
- data/lib/pachube-stream.rb +15 -0
- data/pachube-stream.gemspec +27 -0
- data/spec/fixtures/pachube/not_authorized.json +6 -0
- data/spec/fixtures/pachube/subscribe.json +5 -0
- data/spec/fixtures/pachube/subscribe_data_stream.json +64 -0
- data/spec/fixtures/pachube/subscribe_request.json +9 -0
- data/spec/pachube_spec.rb +106 -0
- data/spec/spec_helper.rb +51 -0
- metadata +165 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
== PachubeStream
|
2
|
+
|
3
|
+
PachubeStream gives you an API for the Pachube TCP Stream using EventMachine
|
4
|
+
|
5
|
+
http://api.pachube.com/v2/beta/#tcp-socket-and-websocket-connections
|
6
|
+
|
7
|
+
== Quickstart
|
8
|
+
|
9
|
+
require "pachube-stream"
|
10
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
11
|
+
|
12
|
+
Look at the examples directory for exactly that!
|
13
|
+
|
14
|
+
== Connection Methods
|
15
|
+
|
16
|
+
The methods on the connection are what Pachube look for when making a request; this in-turn will
|
17
|
+
generate the correct method value in the 'http json'
|
18
|
+
|
19
|
+
=== Example
|
20
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
21
|
+
request = connection.subscribe("/feeds/504")
|
22
|
+
|
23
|
+
Results In the following JSON request
|
24
|
+
|
25
|
+
{
|
26
|
+
"method" : "subscribe",
|
27
|
+
"resource" : "/feeds/504",
|
28
|
+
"headers" :
|
29
|
+
{
|
30
|
+
"X-PachubeApiKey" : "API_KEY"
|
31
|
+
},
|
32
|
+
"token" : "subscribe"
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
=== Subscribe
|
37
|
+
|
38
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
39
|
+
request = connection.subscribe("/feeds/504")
|
40
|
+
request.on_datastream do |response|
|
41
|
+
puts response
|
42
|
+
end
|
43
|
+
|
44
|
+
=== Unsubscribe
|
45
|
+
|
46
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
47
|
+
request = connection.unsubscribe("/feeds/504")
|
48
|
+
request.on_compelete do |response|
|
49
|
+
puts response
|
50
|
+
end
|
51
|
+
|
52
|
+
=== Get
|
53
|
+
|
54
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
55
|
+
request = connection.get("/feeds/504")
|
56
|
+
request.on_get do |response|
|
57
|
+
puts response
|
58
|
+
end
|
59
|
+
|
60
|
+
=== Put
|
61
|
+
|
62
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
63
|
+
request = connection.put("/feeds/504")
|
64
|
+
request.on_complete do |response|
|
65
|
+
puts response
|
66
|
+
end
|
67
|
+
|
68
|
+
=== Delete
|
69
|
+
|
70
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
71
|
+
request = connection.delete("/feeds/504")
|
72
|
+
request.on_complete do |response|
|
73
|
+
puts response
|
74
|
+
end
|
75
|
+
|
76
|
+
=== Post
|
77
|
+
|
78
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
79
|
+
request = connection.post("/feeds/504")
|
80
|
+
request.on_complete do |response|
|
81
|
+
puts response
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
The way we make a request is to send json in the format of a HTTP request; you only have
|
86
|
+
to give extra options when you want to provide your own headers, params and body etc;
|
87
|
+
|
88
|
+
Therefore when using the connection Methods
|
89
|
+
|
90
|
+
When can create this 'http json request' with PachubeStream::HttpRequest passing in a hash
|
91
|
+
|
92
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
93
|
+
request = connection.put("/feeds/504", PachubeStream::HttpRequest.new(:body => {}, :params => {}, :headers => {}))
|
94
|
+
|
95
|
+
request.on_complete do |response|
|
96
|
+
puts response
|
97
|
+
end
|
98
|
+
|
99
|
+
== Defaults:
|
100
|
+
|
101
|
+
When creating a connection you can specify the host and port these have defaults
|
102
|
+
|
103
|
+
host # => beta.pachube.com
|
104
|
+
port # => 8081
|
105
|
+
|
106
|
+
|
107
|
+
== Respect
|
108
|
+
|
109
|
+
Respect goes out to the twitter-stream Gem as one took some concepts form that; nice!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { ["rspec2"] }
|
@@ -0,0 +1,32 @@
|
|
1
|
+
$: << 'lib' << '../lib'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'eventmachine'
|
4
|
+
require '../lib/pachube-stream'
|
5
|
+
|
6
|
+
EM.run do
|
7
|
+
connection = PachubeStream::Connection.connect(:api_key => ENV["PACHUBE_API_KEY"])
|
8
|
+
|
9
|
+
connection.on_reconnect do |timeout, reconnect_retries|
|
10
|
+
puts timeout
|
11
|
+
puts reconnect_retries
|
12
|
+
end
|
13
|
+
|
14
|
+
connection.on_max_reconnects do |timeout, reconnect_retries|
|
15
|
+
puts timeout
|
16
|
+
puts reconnect_retries
|
17
|
+
end
|
18
|
+
|
19
|
+
feed = connection.subscribe("/feeds/6643") # random Feed
|
20
|
+
|
21
|
+
feed.on_datastream do |response|
|
22
|
+
puts response
|
23
|
+
end
|
24
|
+
|
25
|
+
feed.on_complete do |response|
|
26
|
+
puts response
|
27
|
+
end
|
28
|
+
|
29
|
+
feed.on_error do |response|
|
30
|
+
puts response
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module PachubeStream
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# @param [EventMachine::Connection] conn
|
5
|
+
# @param [String] api_key
|
6
|
+
# @param [Hash] options defaults {}
|
7
|
+
def initialize(conn, api_key, options = {})
|
8
|
+
@conn = conn
|
9
|
+
@api_key = api_key
|
10
|
+
@options = options
|
11
|
+
@requests = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] response
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
# @todo refactor ugly as Sin
|
18
|
+
def process_data(response)
|
19
|
+
parsed_response = parse_response(response)
|
20
|
+
status_ok = parsed_response["status"] && parsed_response["status"] != 200
|
21
|
+
if request = @requests[parsed_response["token"]]
|
22
|
+
if status_ok
|
23
|
+
call_block_for_request(request, parsed_response)
|
24
|
+
else
|
25
|
+
call_error_for_request_block(request, parsed_response)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
if status_ok
|
29
|
+
@conn.on_response_block.call(parsed_response) if @conn.on_response_block
|
30
|
+
else
|
31
|
+
receive_error(parsed_response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] response
|
37
|
+
#
|
38
|
+
# @return [Hash]
|
39
|
+
def parse_response(response)
|
40
|
+
begin
|
41
|
+
Yajl::Parser.parse(response)
|
42
|
+
rescue Exception => e
|
43
|
+
receive_error("#{e.class}: " + [e.message, e.backtrace].flatten.join("\n\t"))
|
44
|
+
@conn.close_connection
|
45
|
+
return
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def receive_error(error)
|
50
|
+
@conn.on_error_block.call(error) if @conn.on_error_block
|
51
|
+
end
|
52
|
+
|
53
|
+
# we send the request and also keep the request with the token
|
54
|
+
# as its key so we can attach callback to requests
|
55
|
+
def send_request(method, resource, html_request = {}, token = nil, &block)
|
56
|
+
@request = Request.new(@api_key, method, resource, html_request, token)
|
57
|
+
@requests[@request.token] = @request
|
58
|
+
@conn.send_data(@request.to_json)
|
59
|
+
@request
|
60
|
+
end
|
61
|
+
|
62
|
+
# finds the correct callback based on the token which
|
63
|
+
# has the method call in its sig
|
64
|
+
def call_block_for_request(request, parsed_response)
|
65
|
+
case request.token.gsub(/:.*/, "")
|
66
|
+
when "subscribe" && !parsed_response["body"].nil?
|
67
|
+
request.on_datastream_block.call(parsed_response) if request.on_datastream_block
|
68
|
+
when "get" && !parsed_response["body"].nil?
|
69
|
+
request.on_get_block.call(parsed_response) if request.on_get_block
|
70
|
+
else
|
71
|
+
request.on_complete_block.call(parsed_response) if request.on_complete_block
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_error_for_request_block(request, parsed_response)
|
76
|
+
request.on_error_block.call(parsed_response) if request.on_error_block
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module PachubeStream
|
2
|
+
class Connection < EventMachine::Connection
|
3
|
+
include RequestMethods
|
4
|
+
|
5
|
+
NF_RECONNECT_START = 0.25
|
6
|
+
NF_RECONNECT_ADD = 0.25
|
7
|
+
NF_RECONNECT_MAX = 16
|
8
|
+
RECONNECT_MAX = 320
|
9
|
+
RETRIES_MAX = 10
|
10
|
+
|
11
|
+
attr_accessor :options, :on_init_callback, :api_key, :on_response_block, :on_error_block
|
12
|
+
attr_accessor :reconnect_callback, :max_reconnects_callback, :nf_last_reconnect, :reconnect_retries
|
13
|
+
|
14
|
+
# @todo tidy as crap
|
15
|
+
def self.connect(options = {})
|
16
|
+
api_key = options[:api_key]
|
17
|
+
raise ArgumentError.new("You need to supply an API Key") unless api_key
|
18
|
+
host = options[:host] || "beta.pachube.com"
|
19
|
+
port = options[:port] || 8081
|
20
|
+
EventMachine.connect host, port, self, options
|
21
|
+
rescue EventMachine::ConnectionError => e
|
22
|
+
conn = EventMachine::FailedConnection.new(req)
|
23
|
+
conn.error = e.message
|
24
|
+
conn.fail
|
25
|
+
conn
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(options)
|
29
|
+
@options = options
|
30
|
+
@api_key = options[:api_key]
|
31
|
+
@timeout = options[:timeout] || 0
|
32
|
+
@reconnect_retries = 0
|
33
|
+
@immediate_reconnect = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def client
|
37
|
+
@client ||= PachubeStream::Client.new(self, @api_key, @options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_init
|
41
|
+
set_comm_inactivity_timeout @timeout if @timeout > 0
|
42
|
+
@on_inited_callback.call if @on_inited_callback
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_reconnect(&block)
|
46
|
+
@reconnect_callback = block
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_max_reconnects(&block)
|
50
|
+
@max_reconnects_callback = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_response_block(&block)
|
54
|
+
@on_response_block = block
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_error_block(&block)
|
58
|
+
@on_error_block = block
|
59
|
+
end
|
60
|
+
|
61
|
+
def stop
|
62
|
+
@gracefully_closed = true
|
63
|
+
close_connection
|
64
|
+
end
|
65
|
+
|
66
|
+
def immediate_reconnect
|
67
|
+
@immediate_reconnect = true
|
68
|
+
@gracefully_closed = false
|
69
|
+
close_connection
|
70
|
+
end
|
71
|
+
|
72
|
+
def unbind
|
73
|
+
schedule_reconnect unless @gracefully_closed
|
74
|
+
end
|
75
|
+
|
76
|
+
def receive_data(response)
|
77
|
+
client.process_data(response)
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
def schedule_reconnect
|
82
|
+
timeout = reconnect_timeout
|
83
|
+
@reconnect_retries += 1
|
84
|
+
if (timeout <= RECONNECT_MAX) && (@reconnect_retries <= RETRIES_MAX)
|
85
|
+
reconnect_after(timeout)
|
86
|
+
else
|
87
|
+
@max_reconnects_callback.call(timeout, @reconnect_retries) if @max_reconnects_callback
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def reconnect_after(timeout)
|
92
|
+
@reconnect_callback.call(timeout, @reconnect_retries) if @reconnect_callback
|
93
|
+
|
94
|
+
if timeout == 0
|
95
|
+
reconnect @options[:host], @options[:port]
|
96
|
+
else
|
97
|
+
EventMachine.add_timer(timeout) do
|
98
|
+
reconnect @options[:host], @options[:port]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def reconnect_timeout
|
104
|
+
if @immediate_reconnect
|
105
|
+
@immediate_reconnect = false
|
106
|
+
return 0
|
107
|
+
end
|
108
|
+
if @nf_last_reconnect
|
109
|
+
@nf_last_reconnect += NF_RECONNECT_ADD
|
110
|
+
else
|
111
|
+
@nf_last_reconnect = NF_RECONNECT_START
|
112
|
+
end
|
113
|
+
[@nf_last_reconnect, NF_RECONNECT_MAX].min
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PachubeStream
|
2
|
+
class HtmlRequest < Hashie::Dash
|
3
|
+
|
4
|
+
property :resource
|
5
|
+
property :method
|
6
|
+
property :headers, :default => {}
|
7
|
+
property :token
|
8
|
+
property :body
|
9
|
+
property :params
|
10
|
+
|
11
|
+
def api_key=(api_key)
|
12
|
+
headers["X-PachubeApiKey"] = api_key
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PachubeStream
|
2
|
+
module RequestMethods
|
3
|
+
|
4
|
+
def get(resource, html_request = {}, token = nil, &block)
|
5
|
+
client.send_request(:get, resource, html_request = {}, token, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def post(resource, html_request = {}, token = nil, &block)
|
9
|
+
client.send_request(:post, resource, html_request = {}, token, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def put(resource, html_request = {}, token = nil, &block)
|
13
|
+
client.send_request(:put, resource, html_request = {}, token, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(resource,html_request = {}, token= nil, &block)
|
17
|
+
client.send_request(:delete, resource, html_request = {}, token, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def subscribe(resource, html_request = {}, token = nil, &block)
|
21
|
+
client.send_request(:subscribe, resource, html_request = {}, token, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unsubsribe(resource, html_request = {}, token = nil, &block)
|
25
|
+
client.send_request(:unsubsribe, resource, html_request = {}, token, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module PachubeStream
|
2
|
+
class Request
|
3
|
+
|
4
|
+
attr_accessor :on_complete_block, :on_datastream_block, :request, :token, :method, :on_error_block, :on_get_block
|
5
|
+
|
6
|
+
# @param[String] method
|
7
|
+
# @param[String] html this means headers and body and resource and params
|
8
|
+
# @param[String] token if you wanted to send a specific token
|
9
|
+
def initialize(api_key, method, resource, html_request, token = nil)
|
10
|
+
@api_key = api_key
|
11
|
+
@method = method
|
12
|
+
@resource = resource
|
13
|
+
@html_request = html_request
|
14
|
+
@token = token || generate_token(method)
|
15
|
+
generate_html_request(api_key, method, resource, html_request, @token)
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_complete(&block)
|
19
|
+
@on_complete_block = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_datastream(&block)
|
23
|
+
@on_datastream_block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_error(&block)
|
27
|
+
@on_error_block = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_get(&block)
|
31
|
+
@on_get_block = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json
|
35
|
+
@request.to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def generate_token(method)
|
40
|
+
"#{method}:#{UUID.generate}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_html_request(api_key, method, resource, html_request, token)
|
44
|
+
@request = HtmlRequest.new(html_request)
|
45
|
+
@request.api_key = api_key
|
46
|
+
@request[:method] = method
|
47
|
+
@request[:resource] = resource
|
48
|
+
@request[:token] = token
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PachubeStream
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'addressable/uri'
|
6
|
+
require "hashie"
|
7
|
+
require 'yajl'
|
8
|
+
require 'json'
|
9
|
+
require 'uuid'
|
10
|
+
require 'pachube-stream/methods'
|
11
|
+
require 'pachube-stream/connection'
|
12
|
+
require 'pachube-stream/client'
|
13
|
+
require 'pachube-stream/request'
|
14
|
+
require 'pachube-stream/html_request'
|
15
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "pachube-stream/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pachube-stream"
|
7
|
+
s.version = PachubeStream::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Hookercookerman"]
|
10
|
+
s.email = ["hookercookerman@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Pachube TCP streaming API}
|
13
|
+
s.description = %q{Simple Ruby client library for pachube TCP streaming API. Uses EventMachine for connection handling.JSON format only.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "pachube-stream"
|
16
|
+
|
17
|
+
s.add_dependency "eventmachine", ">= 1.0.0.beta.3"
|
18
|
+
s.add_dependency "addressable", ">= 2.2.3"
|
19
|
+
s.add_dependency "hashie", ">= 0.5.1"
|
20
|
+
s.add_dependency 'yajl-ruby', '~> 0.8.2'
|
21
|
+
s.add_dependency 'uuid', '~> 2.3.2'
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
{
|
2
|
+
"body":
|
3
|
+
{
|
4
|
+
"location":
|
5
|
+
{
|
6
|
+
"lon":-0.0215005874633789,
|
7
|
+
"domain":"physical",
|
8
|
+
"disposition":"fixed",
|
9
|
+
"exposure":"indoor",
|
10
|
+
"lat":51.4722148395794
|
11
|
+
},
|
12
|
+
"title":"CurrentCost meter",
|
13
|
+
"datastreams":
|
14
|
+
[
|
15
|
+
{
|
16
|
+
"current_value":"32.2",
|
17
|
+
"max_value":"32.3",
|
18
|
+
"min_value":"9.3",
|
19
|
+
"id":"0",
|
20
|
+
"tags":
|
21
|
+
[
|
22
|
+
"celsius",
|
23
|
+
"degrees",
|
24
|
+
"temperature"
|
25
|
+
],
|
26
|
+
"unit":
|
27
|
+
{
|
28
|
+
"label":"Celsius",
|
29
|
+
"symbol":"C",
|
30
|
+
"type":"basicSI"
|
31
|
+
},
|
32
|
+
"at":"2011-04-20T12:35:42.653484Z"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"current_value":"309.0",
|
36
|
+
"max_value":"12675.0",
|
37
|
+
"min_value":"24.0",
|
38
|
+
"id":"1",
|
39
|
+
"tags":
|
40
|
+
[
|
41
|
+
"electricity",
|
42
|
+
"power",
|
43
|
+
"watts"
|
44
|
+
],
|
45
|
+
"unit":
|
46
|
+
{
|
47
|
+
"label":"Watts",
|
48
|
+
"symbol":"W",
|
49
|
+
"type":"derivedSI"
|
50
|
+
},
|
51
|
+
"at":"2011-04-20T12:35:42.653484Z"
|
52
|
+
}
|
53
|
+
],
|
54
|
+
"creator":"http://www.pachube.com/users/sc84647",
|
55
|
+
"private":"false",
|
56
|
+
"id":6643,
|
57
|
+
"version":"1.0.0",
|
58
|
+
"updated":"2011-04-20T12:35:42.653484Z",
|
59
|
+
"status":"live",
|
60
|
+
"feed":"http://api.pachube.com/v2/feeds/6643.json"
|
61
|
+
},
|
62
|
+
"resource":"/feeds/6643",
|
63
|
+
"token":"subscribe:a06d6f30-4d78-012e-e33d-002332cf7bbe"
|
64
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path("../spec_helper", __FILE__)
|
3
|
+
|
4
|
+
describe "Pachube" do
|
5
|
+
context "on connection" do
|
6
|
+
it "should return stream" do
|
7
|
+
EM.should_receive(:connect).and_return('TESTING CONNECT')
|
8
|
+
stream = PachubeStream::Connection.connect(:api_key => "Testing")
|
9
|
+
stream.should == 'TESTING CONNECT'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should define default properties" do
|
13
|
+
EM.should_receive(:connect).with do |host, port, handler, opts|
|
14
|
+
host.should == 'beta.pachube.com'
|
15
|
+
port.should == 8081
|
16
|
+
end
|
17
|
+
stream = PachubeStream::Connection.connect(:api_key => "Testing")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "Connection #subscribe" do
|
22
|
+
attr_reader :stream
|
23
|
+
before(:each) do
|
24
|
+
$data_to_send = read_fixture('pachube/subscribe.json')
|
25
|
+
$recieved_data = ''
|
26
|
+
$close_connection = false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should send the 'http_request json request' with the method subscribe'" do
|
30
|
+
connect_stream do |connection|
|
31
|
+
connection.subscribe("/feeds/100", {}, "token")
|
32
|
+
end
|
33
|
+
Yajl::Parser.parse($recieved_data).should == Yajl::Parser.parse(read_fixture('pachube/subscribe_request.json'))
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should capture response to the on_compelete as that is not a datastream response" do
|
37
|
+
connect_stream do |connection|
|
38
|
+
subscription = connection.subscribe("/feeds/100", {}, "subscribe:a06d6f30-4d78-012e-e33d-002332cf7bbe")
|
39
|
+
subscription.on_complete do |response|
|
40
|
+
$data_to_send = read_fixture('pachube/subscribe_data_stream.json')
|
41
|
+
response.should eq(Yajl::Parser.parse(read_fixture('pachube/subscribe.json')))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should send capture response on_datastream for datastream" do
|
47
|
+
$data_to_send = read_fixture('pachube/subscribe_data_stream.json')
|
48
|
+
connect_stream do |connection|
|
49
|
+
subscription = connection.subscribe("/feeds/100", {}, "subscribe:a06d6f30-4d78-012e-e33d-002332cf7bbe")
|
50
|
+
subscription.on_datastream do |response|
|
51
|
+
response.should eq(Yajl::Parser.parse(read_fixture('pachube/subscribe_data_stream.json')))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should capture to the on_error for the request when status code other then 200" do
|
57
|
+
$data_to_send = read_fixture('pachube/not_authorized.json')
|
58
|
+
connect_stream do |connection|
|
59
|
+
subscription = connection.subscribe("/feeds/100", {}, "subscribe:a06d6f30-4d78-012e-e33d-002332cf7bbe")
|
60
|
+
subscription.on_error do |response|
|
61
|
+
response.should eq(Yajl::Parser.parse(read_fixture('pachube/not_authorized.json')))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "network failure" do
|
68
|
+
before(:each) do
|
69
|
+
$close_connection = true
|
70
|
+
$data_to_send = ''
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should reconnect with 0.25 at base" do
|
74
|
+
connect_stream do |connection|
|
75
|
+
connection.should_receive(:reconnect_after).with(0.25)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should reconnect with linear timeout" do
|
80
|
+
connect_stream do |connection|
|
81
|
+
connection.nf_last_reconnect = 1
|
82
|
+
connection.should_receive(:reconnect_after).with(1.25)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should stop reconnecting after 100 times" do
|
87
|
+
connect_stream do |connection|
|
88
|
+
connection.reconnect_retries = 100
|
89
|
+
connection.should_not_receive(:reconnect_after)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should notify after reconnect limit is reached" do
|
94
|
+
timeout, retries = nil, nil
|
95
|
+
connect_stream do |connection|
|
96
|
+
connection.on_max_reconnects do |t, r|
|
97
|
+
timeout, retries = t, r
|
98
|
+
end
|
99
|
+
connection.reconnect_retries = 100
|
100
|
+
end
|
101
|
+
timeout.should == 0.25
|
102
|
+
retries.should == 101
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "bundler"
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require "pachube-stream"
|
6
|
+
Bundler.require(:test)
|
7
|
+
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
|
11
|
+
def fixture_path(path)
|
12
|
+
File.join(File.dirname(__FILE__), 'fixtures', path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def read_fixture(path)
|
16
|
+
File.read(fixture_path(path))
|
17
|
+
end
|
18
|
+
|
19
|
+
Host = "127.0.0.1"
|
20
|
+
Port = 9550
|
21
|
+
|
22
|
+
# == What would be awsome to capture the response tcpdump or something and the replay
|
23
|
+
class PachubeServer < EM::Connection
|
24
|
+
attr_accessor :data
|
25
|
+
def receive_data data
|
26
|
+
$recieved_data = data
|
27
|
+
send_data $data_to_send
|
28
|
+
EventMachine.next_tick {
|
29
|
+
close_connection if $close_connection
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect_stream(opts={}, &blk)
|
35
|
+
EM.run {
|
36
|
+
opts.merge!(:host => Host, :port => Port)
|
37
|
+
stop_in = opts.delete(:stop_in) || 0.5
|
38
|
+
unless opts[:start_server] == false
|
39
|
+
EM.start_server Host, Port, PachubeServer
|
40
|
+
end
|
41
|
+
@stream = PachubeStream::Connection.connect(:api_key => "testing", :host => Host, :port => Port)
|
42
|
+
blk.call(@stream) if blk
|
43
|
+
EM.add_timer(stop_in){ EM.stop }
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
Rspec.configure do |config|
|
48
|
+
config.mock_with :rspec
|
49
|
+
config.filter_run :focus => true
|
50
|
+
config.run_all_when_everything_filtered = true
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pachube-stream
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Hookercookerman
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-04-20 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: eventmachine
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
- beta
|
33
|
+
- 3
|
34
|
+
version: 1.0.0.beta.3
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: addressable
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 2
|
48
|
+
- 3
|
49
|
+
version: 2.2.3
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: hashie
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
- 5
|
63
|
+
- 1
|
64
|
+
version: 0.5.1
|
65
|
+
type: :runtime
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: yajl-ruby
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
- 8
|
78
|
+
- 2
|
79
|
+
version: 0.8.2
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: uuid
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ~>
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 2
|
92
|
+
- 3
|
93
|
+
- 2
|
94
|
+
version: 2.3.2
|
95
|
+
type: :runtime
|
96
|
+
version_requirements: *id005
|
97
|
+
description: Simple Ruby client library for pachube TCP streaming API. Uses EventMachine for connection handling.JSON format only.
|
98
|
+
email:
|
99
|
+
- hookercookerman@gmail.com
|
100
|
+
executables: []
|
101
|
+
|
102
|
+
extensions: []
|
103
|
+
|
104
|
+
extra_rdoc_files: []
|
105
|
+
|
106
|
+
files:
|
107
|
+
- .gitignore
|
108
|
+
- Gemfile
|
109
|
+
- README.rdoc
|
110
|
+
- Rakefile
|
111
|
+
- autotest/discover.rb
|
112
|
+
- examples/subscribe.rb
|
113
|
+
- lib/pachube-stream.rb
|
114
|
+
- lib/pachube-stream/client.rb
|
115
|
+
- lib/pachube-stream/connection.rb
|
116
|
+
- lib/pachube-stream/html_request.rb
|
117
|
+
- lib/pachube-stream/methods.rb
|
118
|
+
- lib/pachube-stream/request.rb
|
119
|
+
- lib/pachube-stream/version.rb
|
120
|
+
- pachube-stream.gemspec
|
121
|
+
- spec/fixtures/pachube/not_authorized.json
|
122
|
+
- spec/fixtures/pachube/subscribe.json
|
123
|
+
- spec/fixtures/pachube/subscribe_data_stream.json
|
124
|
+
- spec/fixtures/pachube/subscribe_request.json
|
125
|
+
- spec/pachube_spec.rb
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
has_rdoc: true
|
128
|
+
homepage: ""
|
129
|
+
licenses: []
|
130
|
+
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
segments:
|
150
|
+
- 0
|
151
|
+
version: "0"
|
152
|
+
requirements: []
|
153
|
+
|
154
|
+
rubyforge_project: pachube-stream
|
155
|
+
rubygems_version: 1.3.7
|
156
|
+
signing_key:
|
157
|
+
specification_version: 3
|
158
|
+
summary: Pachube TCP streaming API
|
159
|
+
test_files:
|
160
|
+
- spec/fixtures/pachube/not_authorized.json
|
161
|
+
- spec/fixtures/pachube/subscribe.json
|
162
|
+
- spec/fixtures/pachube/subscribe_data_stream.json
|
163
|
+
- spec/fixtures/pachube/subscribe_request.json
|
164
|
+
- spec/pachube_spec.rb
|
165
|
+
- spec/spec_helper.rb
|