pusher 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,5 +13,3 @@ matrix:
13
13
  allow_failures:
14
14
  - rvm: jruby-18mode
15
15
  - rvm: jruby-19mode
16
-
17
- script: "rspec spec/*_spec.rb"
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
2
- gemspec
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -1,48 +1,52 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pusher (0.11.3)
4
+ pusher (0.12.0)
5
+ httpclient (~> 2.3.0)
5
6
  multi_json (~> 1.0)
6
7
  signature (~> 0.1.6)
7
8
 
8
9
  GEM
9
- remote: http://rubygems.org/
10
+ remote: https://rubygems.org/
10
11
  specs:
11
- addressable (2.3.2)
12
+ addressable (2.3.5)
12
13
  cookiejar (0.3.0)
13
- crack (0.3.2)
14
- diff-lcs (1.1.3)
15
- em-http-request (1.0.3)
16
- addressable (>= 2.2.3)
14
+ crack (0.4.1)
15
+ safe_yaml (~> 0.9.0)
16
+ diff-lcs (1.2.4)
17
+ em-http-request (1.1.0)
18
+ addressable (>= 2.3.4)
17
19
  cookiejar
18
- em-socksify
20
+ em-socksify (>= 0.3)
21
+ eventmachine (>= 1.0.3)
22
+ http_parser.rb (>= 0.6.0.beta.2)
23
+ em-socksify (0.3.0)
19
24
  eventmachine (>= 1.0.0.beta.4)
20
- http_parser.rb (>= 0.5.3)
21
- em-socksify (0.2.1)
22
- eventmachine (>= 1.0.0.beta.4)
23
- eventmachine (1.0.0)
24
- http_parser.rb (0.5.3)
25
- multi_json (1.5.0)
26
- rack (1.5.1)
27
- rake (10.0.3)
28
- rspec (2.12.0)
29
- rspec-core (~> 2.12.0)
30
- rspec-expectations (~> 2.12.0)
31
- rspec-mocks (~> 2.12.0)
32
- rspec-core (2.12.2)
33
- rspec-expectations (2.12.1)
34
- diff-lcs (~> 1.1.3)
35
- rspec-mocks (2.12.2)
36
- signature (0.1.6)
37
- webmock (1.9.0)
25
+ eventmachine (1.0.3)
26
+ http_parser.rb (0.6.0.beta.2)
27
+ httpclient (2.3.4.1)
28
+ multi_json (1.7.8)
29
+ rack (1.5.2)
30
+ rake (10.1.0)
31
+ rspec (2.14.1)
32
+ rspec-core (~> 2.14.0)
33
+ rspec-expectations (~> 2.14.0)
34
+ rspec-mocks (~> 2.14.0)
35
+ rspec-core (2.14.4)
36
+ rspec-expectations (2.14.0)
37
+ diff-lcs (>= 1.1.3, < 2.0)
38
+ rspec-mocks (2.14.2)
39
+ safe_yaml (0.9.4)
40
+ signature (0.1.7)
41
+ webmock (1.13.0)
38
42
  addressable (>= 2.2.7)
39
- crack (>= 0.1.7)
43
+ crack (>= 0.3.2)
40
44
 
41
45
  PLATFORMS
42
46
  ruby
43
47
 
44
48
  DEPENDENCIES
45
- em-http-request (~> 1.0.0)
49
+ em-http-request (~> 1.1.0)
46
50
  pusher!
47
51
  rack
48
52
  rake
data/README.md CHANGED
@@ -3,7 +3,15 @@ Pusher gem
3
3
 
4
4
  [![Build Status](https://secure.travis-ci.org/pusher/pusher-gem.png?branch=master)](http://travis-ci.org/pusher/pusher-gem)
5
5
 
6
- ## Configuration
6
+ ## Installation & Configuration
7
+
8
+ Add pusher to your Gemfile, and then run `bundle install`
9
+
10
+ gem 'pusher'
11
+
12
+ or install via gem
13
+
14
+ gem install pusher
7
15
 
8
16
  After registering at <http://pusher.com> configure your app with the security credentials.
9
17
 
@@ -25,6 +33,10 @@ By default API requests are made over HTTP. HTTPS can be used by setting
25
33
 
26
34
  Pusher.encrypted = true
27
35
 
36
+ As of version 0.12, SSL certificates are verified when using the synchronous http client. If you need to disable this behaviour for any reason use:
37
+
38
+ Pusher.default_client.sync_http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
39
+
28
40
  ### Instantiating a Pusher client
29
41
 
30
42
  Sometimes you may have multiple sets of API keys, or want different configuration in different parts of your application. In these scenarios, a pusher `client` may be configured:
@@ -94,24 +106,45 @@ All requests must be signed by using your secret key, which is handled automatic
94
106
  # using a client
95
107
  pusher_client.post('url_without_app_id', params)
96
108
 
97
- Note that you don't need to specify your app_id in the URL, as this is inferred from your credentials. As with the trigger method above, `_async` can be suffixed to the method name to return a deferrable.
109
+ Note that you don't need to specify your app_id in the URL, as this is inferred from your credentials.
98
110
 
99
111
  ### Asynchronous requests
100
112
 
101
- If you are running your application in an evented environment, you may want to use the asynchronous versions of the Pusher API methods to avoid blocking. The convention for this is to add the suffix `_async` to the method, e.g. `trigger_async` or `post_async`.
113
+ There are two main reasons for using the `_async` methods:
114
+
115
+ * In a web application where the response from Pusher is not used, but you'd like to avoid a blocking call in the request-response cycle
116
+ * Your application is running in an event loop and you need to avoid blocking the reactor
117
+
118
+ Asynchronous calls are supported either by using an event loop (eventmachine, preferred), or via a thread.
102
119
 
103
- You need to be running eventmachine to make use of this functionality. This is already the case if, for example, you're deploying to Heroku or using the Thin web server. You will also need to add `em-http-request` to your Gemfile.
120
+ The following methods are available (in each case the calling iterface matches the non-async version):
104
121
 
105
- When using an asynchronous version of a method, it will return a deferrable.
122
+ * `Pusher.get_async`
123
+ * `Pusher.post_async`
124
+ * `Pusher.trigger_async`
106
125
 
107
- Pusher.trigger_async(['a_channel'], 'an_event', {
108
- :some => 'data'
109
- }, socket_id).callback {
110
- # Do something on success
126
+ It is of course also possible to make calls to pusher via a job queue. This approach is recommended if you're sending a large number of events to pusher.
127
+
128
+ #### With eventmachine
129
+
130
+ * Add the `em-http-request` gem to your Gemfile (it's not a gem dependency).
131
+ * Run the eventmachine reactor (either using `EM.run` or by running inside an evented server such as Thin).
132
+
133
+ The `_async` methods return an `EM::Deferrable` which you can bind callbacks to:
134
+
135
+ Pusher.get_async("/channels").callback { |response|
136
+ # use reponse[:channels]
111
137
  }.errback { |error|
112
- # error is a instance of Pusher::Error
138
+ # error is an instance of Pusher::Error
113
139
  }
114
140
 
141
+ A HTTP error or an error response from pusher will cause the errback to be called with an appropriate error object.
142
+
143
+ #### Without eventmachine
144
+
145
+ If the eventmachine reactor is not running, async requests will be make using threads (managed by the httpclient gem).
146
+
147
+ An `HTTPClient::Connection` object is returned immediately which can be [interrogated](http://rubydoc.info/gems/httpclient/HTTPClient/Connection) to discover the status of the request. The usual response checking and processing is not done when the request completes, and frankly this method is most useful when you're not interested in waiting for the response.
115
148
 
116
149
 
117
150
  ## Authenticating subscription requests
@@ -28,6 +28,7 @@ module Pusher
28
28
 
29
29
  def_delegators :default_client, :authentication_token, :url
30
30
  def_delegators :default_client, :encrypted=, :url=
31
+ def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=, :keep_alive_timeout=
31
32
 
32
33
  def_delegators :default_client, :get, :get_async, :post, :post_async
33
34
  def_delegators :default_client, :channels, :channel_info, :trigger, :trigger_async
@@ -43,8 +44,6 @@ module Pusher
43
44
  end
44
45
  end
45
46
 
46
- private
47
-
48
47
  def default_client
49
48
  @default_client ||= Pusher::Client.new
50
49
  end
@@ -56,7 +55,6 @@ module Pusher
56
55
  end
57
56
 
58
57
  require 'pusher/channel'
59
- require 'pusher/query_encoder'
60
58
  require 'pusher/request'
61
59
  require 'pusher/resource'
62
60
  require 'pusher/webhook'
@@ -51,7 +51,7 @@ module Pusher
51
51
  # event - see http://pusher.com/docs/publisher_api_guide/publisher_excluding_recipients for more info
52
52
  #
53
53
  # @raise [Pusher::Error] on invalid Pusher response - see the error message for more details
54
- # @raise [Pusher::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
54
+ # @raise [Pusher::HTTPError] on any error raised inside http client - the original error is available in the original_error attribute
55
55
  #
56
56
  def trigger!(event_name, data, socket_id = nil)
57
57
  params = {}
@@ -79,7 +79,7 @@ module Pusher
79
79
  # @param info [Array] Array of attributes required (as lowercase strings)
80
80
  # @return [Hash] Hash of requested attributes for this channel
81
81
  # @raise [Pusher::Error] on invalid Pusher response - see the error message for more details
82
- # @raise [Pusher::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
82
+ # @raise [Pusher::HTTPError] on any error raised inside http client - the original error is available in the original_error attribute
83
83
  #
84
84
  def info(attributes = [])
85
85
  @client.get("/channels/#{name}", :info => attributes.join(','))
@@ -2,8 +2,10 @@ require 'signature'
2
2
 
3
3
  module Pusher
4
4
  class Client
5
- attr_accessor :scheme, :host, :port, :app_id, :key, :secret, :http_proxy
6
- attr_reader :proxy
5
+ attr_accessor :scheme, :host, :port, :app_id, :key, :secret
6
+ attr_reader :http_proxy, :proxy
7
+ attr_writer :connect_timeout, :send_timeout, :receive_timeout,
8
+ :keep_alive_timeout
7
9
 
8
10
  ## CONFIGURATION ##
9
11
 
@@ -16,7 +18,14 @@ module Pusher
16
18
  @scheme, @host, @port, @app_id, @key, @secret = options.values_at(
17
19
  :scheme, :host, :port, :app_id, :key, :secret
18
20
  )
21
+ @http_proxy = nil
19
22
  self.http_proxy = options[:http_proxy] if options[:http_proxy]
23
+
24
+ # Default timeouts
25
+ @connect_timeout = 5
26
+ @send_timeout = 5
27
+ @receive_timeout = 5
28
+ @keep_alive_timeout = 30
20
29
  end
21
30
 
22
31
  # @private Returns the authentication token for the client
@@ -79,6 +88,12 @@ module Pusher
79
88
  @scheme == 'https'
80
89
  end
81
90
 
91
+ # Convenience method to set all timeouts to the same value (in seconds).
92
+ # For more control, use the individual writers.
93
+ def timeout=(value)
94
+ @connect_timeout, @send_timeout, @receive_timeout = value, value, value
95
+ end
96
+
82
97
  ## INTERACE WITH THE API ##
83
98
 
84
99
  def resource(path)
@@ -101,7 +116,7 @@ module Pusher
101
116
  # @return [Hash] See Pusher API docs
102
117
  #
103
118
  # @raise [Pusher::Error] Unsuccessful response - see the error message
104
- # @raise [Pusher::HTTPError] Error raised inside Net::HTTP. The original error is wrapped in error.original_error
119
+ # @raise [Pusher::HTTPError] Error raised inside http client. The original error is wrapped in error.original_error
105
120
  #
106
121
  def get(path, params = {})
107
122
  Resource.new(self, path).get(params)
@@ -110,19 +125,14 @@ module Pusher
110
125
  # GET arbitrary REST API resource using an asynchronous http client.
111
126
  # All request signing is handled automatically.
112
127
  #
113
- # @example
114
- # Pusher.get_async('/channels', {
115
- # filter_by_prefix: 'private-'
116
- # }).callback { |response_hash|
117
- # # ...
118
- # }.errback { |error|
119
- # # error is a instance of Pusher::Error
120
- # }
128
+ # When the eventmachine reactor is running, the em-http-request gem is used;
129
+ # otherwise an async request is made using httpclient. See README for
130
+ # details and examples.
121
131
  #
122
132
  # @param path [String] Path excluding /apps/APP_ID
123
133
  # @param params [Hash] API params (see http://pusher.com/docs/rest_api)
124
134
  #
125
- # @return [EM::DefaultDeferrable]
135
+ # @return Either an EM::DefaultDeferrable or a HTTPClient::Connection
126
136
  #
127
137
  def get_async(path, params = {})
128
138
  Resource.new(self, path).get_async(params)
@@ -175,7 +185,7 @@ module Pusher
175
185
  # @return [Hash] See Pusher API docs
176
186
  #
177
187
  # @raise [Pusher::Error] Unsuccessful response - see the error message
178
- # @raise [Pusher::HTTPError] Error raised inside Net::HTTP. The original error is wrapped in error.original_error
188
+ # @raise [Pusher::HTTPError] Error raised inside http client. The original error is wrapped in error.original_error
179
189
  #
180
190
  def channels(params = {})
181
191
  get('/channels', params)
@@ -191,7 +201,7 @@ module Pusher
191
201
  # @return [Hash] See Pusher API docs
192
202
  #
193
203
  # @raise [Pusher::Error] Unsuccessful response - see the error message
194
- # @raise [Pusher::HTTPError] Error raised inside Net::HTTP. The original error is wrapped in error.original_error
204
+ # @raise [Pusher::HTTPError] Error raised inside http client. The original error is wrapped in error.original_error
195
205
  #
196
206
  def channel_info(channel_name, params = {})
197
207
  get("/channels/#{channel_name}", params)
@@ -210,7 +220,7 @@ module Pusher
210
220
  # @return [Hash] See Pusher API docs
211
221
  #
212
222
  # @raise [Pusher::Error] Unsuccessful response - see the error message
213
- # @raise [Pusher::HTTPError] Error raised inside Net::HTTP. The original error is wrapped in error.original_error
223
+ # @raise [Pusher::HTTPError] Error raised inside http client. The original error is wrapped in error.original_error
214
224
  #
215
225
  def trigger(channels, event_name, data, params = {})
216
226
  post('/events', trigger_params(channels, event_name, data, params))
@@ -224,28 +234,16 @@ module Pusher
224
234
  end
225
235
 
226
236
  # @private Construct a net/http http client
227
- def net_http_client
228
- begin
229
- if encrypted?
230
- require 'net/https' unless defined?(Net::HTTPS)
231
- else
232
- require 'net/http' unless defined?(Net::HTTP)
237
+ def sync_http_client
238
+ @client ||= begin
239
+ require 'httpclient'
240
+
241
+ HTTPClient.new(@http_proxy).tap do |c|
242
+ c.connect_timeout = @connect_timeout
243
+ c.send_timeout = @send_timeout
244
+ c.receive_timeout = @receive_timeout
245
+ c.keep_alive_timeout = @keep_alive_timeout
233
246
  end
234
-
235
- http_klass = if (p = @proxy)
236
- Net::HTTP.Proxy(p[:host], p[:port], p[:user], p[:password])
237
- else
238
- Net::HTTP
239
- end
240
-
241
- http = http_klass.new(@host, @port)
242
-
243
- if encrypted?
244
- http.use_ssl = true
245
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
246
- end
247
-
248
- http
249
247
  end
250
248
  end
251
249
 
@@ -257,9 +255,12 @@ module Pusher
257
255
  end
258
256
  require 'em-http' unless defined?(EventMachine::HttpRequest)
259
257
 
260
- connection_opts = {}
258
+ connection_opts = {
259
+ :connect_timeout => @connect_timeout,
260
+ :inactivity_timeout => @receive_timeout,
261
+ }
261
262
 
262
- if @proxy
263
+ if defined?(@proxy)
263
264
  proxy_opts = {
264
265
  :host => @proxy[:host],
265
266
  :port => @proxy[:port]
@@ -277,7 +278,7 @@ module Pusher
277
278
  private
278
279
 
279
280
  def trigger_params(channels, event_name, data, params)
280
- channels = [channels] if channels.kind_of?(String)
281
+ channels = Array(channels).map(&:to_s)
281
282
 
282
283
  encoded_data = case data
283
284
  when String
@@ -4,16 +4,16 @@ require 'multi_json'
4
4
 
5
5
  module Pusher
6
6
  class Request
7
- include QueryEncoder
8
-
9
7
  attr_reader :body, :params
10
8
 
11
9
  def initialize(client, verb, uri, params, body = nil)
12
10
  @client, @verb, @uri = client, verb, uri
11
+ @head = {}
13
12
 
13
+ @body = body
14
14
  if body
15
- @body = body
16
15
  params[:body_md5] = Digest::MD5.hexdigest(body)
16
+ @head['Content-Type'] = 'application/json'
17
17
  end
18
18
 
19
19
  request = Signature::Request.new(verb.to_s.upcase, uri.path, params)
@@ -22,26 +22,12 @@ module Pusher
22
22
  end
23
23
 
24
24
  def send_sync
25
- http = @client.net_http_client
25
+ http = @client.sync_http_client
26
26
 
27
27
  begin
28
- case @verb
29
- when :post
30
- response = http.post(encode_query(@uri, @params), @body, {
31
- 'Content-Type'=> 'application/json'
32
- })
33
- when :get
34
- response = http.get(encode_query(@uri, @params), {
35
- 'Content-Type'=> 'application/json'
36
- })
37
- else
38
- raise "Unsuported verb"
39
- end
40
- rescue Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED,
41
- Errno::ETIMEDOUT, Errno::EHOSTUNREACH,
42
- Timeout::Error, EOFError,
43
- Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
44
- Net::ProtocolError => e
28
+ response = http.request(@verb, @uri, @params, @body, @head)
29
+ rescue HTTPClient::BadResponseError, HTTPClient::TimeoutError,
30
+ SocketError, Errno::ECONNREFUSED => e
45
31
  error = Pusher::HTTPError.new("#{e.message} (#{e.class})")
46
32
  error.original_error = e
47
33
  raise error
@@ -53,36 +39,41 @@ module Pusher
53
39
  end
54
40
 
55
41
  def send_async
56
- df = EM::DefaultDeferrable.new
42
+ if defined?(EventMachine) && EventMachine.reactor_running?
43
+ http_client = @client.em_http_client(@uri)
44
+ df = EM::DefaultDeferrable.new
57
45
 
58
- http_client = @client.em_http_client(@uri)
59
- http = case @verb
60
- when :post
61
- http_client.post({
62
- :query => @params, :timeout => 5, :body => @body,
63
- :head => {'Content-Type'=> 'application/json'}
64
- })
65
- when :get
66
- http_client.get({
67
- :query => @params, :timeout => 5,
68
- :head => {'Content-Type'=> 'application/json'}
69
- })
70
- else
71
- raise "Unsuported verb"
72
- end
73
- http.callback {
74
- begin
75
- df.succeed(handle_response(http.response_header.status, http.response.chomp))
76
- rescue => e
77
- df.fail(e)
46
+ http = case @verb
47
+ when :post
48
+ http_client.post({
49
+ :query => @params, :body => @body, :head => @head
50
+ })
51
+ when :get
52
+ http_client.get({
53
+ :query => @params, :head => @head
54
+ })
55
+ else
56
+ raise "Unsuported verb"
78
57
  end
79
- }
80
- http.errback {
81
- Pusher.logger.debug("Network error connecting to pusher: #{http.inspect}")
82
- df.fail(Error.new("Network error connecting to pusher"))
83
- }
58
+ http.callback {
59
+ begin
60
+ df.succeed(handle_response(http.response_header.status, http.response.chomp))
61
+ rescue => e
62
+ df.fail(e)
63
+ end
64
+ }
65
+ http.errback { |e|
66
+ message = "Network error connecting to pusher (#{http.error})"
67
+ Pusher.logger.debug(message)
68
+ df.fail(Error.new(message))
69
+ }
70
+
71
+ return df
72
+ else
73
+ http = @client.sync_http_client
84
74
 
85
- df
75
+ return http.request_async(@verb, @uri, @params, @body, @head)
76
+ end
86
77
  end
87
78
 
88
79
  private
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "pusher"
6
- s.version = "0.11.3"
6
+ s.version = "0.12.0"
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Pusher"]
9
9
  s.email = ["support@pusher.com"]
@@ -13,11 +13,12 @@ Gem::Specification.new do |s|
13
13
 
14
14
  s.add_dependency "multi_json", "~> 1.0"
15
15
  s.add_dependency 'signature', "~> 0.1.6"
16
+ s.add_dependency "httpclient", "~> 2.3.0"
16
17
  s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
17
18
 
18
19
  s.add_development_dependency "rspec", "~> 2.0"
19
20
  s.add_development_dependency "webmock"
20
- s.add_development_dependency "em-http-request", "~> 1.0.0"
21
+ s.add_development_dependency "em-http-request", "~> 1.1.0"
21
22
  s.add_development_dependency "rake"
22
23
  s.add_development_dependency "rack"
23
24
 
@@ -33,18 +33,18 @@ describe Pusher::Channel do
33
33
  end
34
34
 
35
35
  describe '#trigger' do
36
- it "should log failure if error raised in Net::HTTP call" do
37
- stub_post_to_raise(Net::HTTPBadResponse)
36
+ it "should log failure if error raised in http call" do
37
+ stub_post_to_raise(HTTPClient::BadResponseError)
38
38
 
39
- Pusher.logger.should_receive(:error).with("Exception from WebMock (Net::HTTPBadResponse) (Pusher::HTTPError)")
39
+ Pusher.logger.should_receive(:error).with("Exception from WebMock (HTTPClient::BadResponseError) (Pusher::HTTPError)")
40
40
  Pusher.logger.should_receive(:debug) #backtrace
41
41
  channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
42
42
  channel.trigger('new_event', 'Some data')
43
43
  end
44
44
 
45
45
  it "should log failure if Pusher returns an error response" do
46
- stub_post 401
47
- Pusher.logger.should_receive(:error).with("Pusher::AuthenticationError (Pusher::AuthenticationError)")
46
+ stub_post 401, "some signature info"
47
+ Pusher.logger.should_receive(:error).with("some signature info (Pusher::AuthenticationError)")
48
48
  Pusher.logger.should_receive(:debug) #backtrace
49
49
  channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
50
50
  channel.trigger('new_event', 'Some data')
@@ -114,7 +114,7 @@ describe Pusher::Channel do
114
114
  end
115
115
 
116
116
  it 'should return a hash with signature including custom data and data as json string' do
117
- MultiJson.stub!(:encode).with(@custom_data).and_return 'a json string'
117
+ MultiJson.stub(:encode).with(@custom_data).and_return 'a json string'
118
118
 
119
119
  response = @channel.authenticate('socketid', @custom_data)
120
120
 
@@ -102,7 +102,7 @@ describe Pusher do
102
102
 
103
103
  describe 'logging configuration' do
104
104
  it "can be configured to use any logger" do
105
- logger = mock("ALogger")
105
+ logger = double("ALogger")
106
106
  logger.should_receive(:debug).with('foo')
107
107
  Pusher.logger = logger
108
108
  Pusher.logger.debug('foo')
@@ -314,8 +314,8 @@ describe Pusher do
314
314
  }
315
315
  end
316
316
 
317
- it "should catch all Net::HTTP exceptions and raise a Pusher::HTTPError wrapping the original error" do
318
- stub_request(verb, @url_regexp).to_raise(Timeout::Error)
317
+ it "should catch all http exceptions and raise a Pusher::HTTPError wrapping the original error" do
318
+ stub_request(verb, @url_regexp).to_raise(HTTPClient::TimeoutError)
319
319
 
320
320
  error = nil
321
321
  begin
@@ -326,8 +326,8 @@ describe Pusher do
326
326
 
327
327
  error.class.should == Pusher::HTTPError
328
328
  error.should be_kind_of(Pusher::Error)
329
- error.message.should == 'Exception from WebMock (Timeout::Error)'
330
- error.original_error.class.should == Timeout::Error
329
+ error.message.should == 'Exception from WebMock (HTTPClient::TimeoutError)'
330
+ error.original_error.class.should == HTTPClient::TimeoutError
331
331
  end
332
332
 
333
333
  it "should raise Pusher::Error if call returns 400" do
@@ -357,61 +357,105 @@ describe Pusher do
357
357
  end
358
358
  end
359
359
 
360
- [[:get, :get_async], [:post, :post_async]].each do |verb, method|
361
- describe "##{method}" do
362
- before :each do
363
- @url_regexp = %r{api.pusherapp.com}
364
- stub_request(verb, @url_regexp).
365
- to_return(:status => 200, :body => "{}")
366
- end
367
-
368
- let(:call_api) { @client.send(method, '/path') }
360
+ describe "async calling without eventmachine" do
361
+ [[:get, :get_async], [:post, :post_async]].each do |verb, method|
362
+ describe "##{method}" do
363
+ before :each do
364
+ @url_regexp = %r{api.pusherapp.com}
365
+ stub_request(verb, @url_regexp).
366
+ to_return(:status => 200, :body => "{}")
367
+ end
369
368
 
370
- it "should use http by default" do
371
- EM.run {
372
- call_api.callback {
373
- WebMock.should have_requested(verb, %r{http://api.pusherapp.com/apps/20/path})
374
- EM.stop
369
+ let(:call_api) {
370
+ @client.send(method, '/path').tap { |c|
371
+ # Allow the async thread (inside httpclient) to run
372
+ while !c.finished?
373
+ sleep 0.01
374
+ end
375
375
  }
376
376
  }
377
- end
378
377
 
379
- it "should use https if configured" do
380
- EM.run {
378
+ it "should use http by default" do
379
+ call_api
380
+ WebMock.should have_requested(verb, %r{http://api.pusherapp.com/apps/20/path})
381
+ end
382
+
383
+ it "should use https if configured" do
381
384
  @client.encrypted = true
382
- call_api.callback {
383
- WebMock.should have_requested(verb, %r{https://api.pusherapp.com})
384
- EM.stop
385
- }
386
- }
385
+ call_api
386
+ WebMock.should have_requested(verb, %r{https://api.pusherapp.com})
387
+ end
388
+
389
+ # Note that the raw httpclient connection object is returned and
390
+ # the response isn't handled (by handle_response) in the normal way.
391
+ it "should return a httpclient connection object" do
392
+ connection = call_api
393
+ connection.finished?.should be_true
394
+ response = connection.pop
395
+ response.status.should == 200
396
+ response.body.read.should == "{}"
397
+ end
387
398
  end
399
+ end
400
+ end
388
401
 
389
- it "should format the respose hash with symbols at first level" do
390
- EM.run {
391
- stub_request(verb, @url_regexp).to_return({
392
- :status => 200,
393
- :body => MultiJson.encode({'something' => {'a' => 'hash'}})
394
- })
395
- call_api.callback { |response|
396
- response.should == {
397
- :something => {'a' => 'hash'}
402
+ describe "async calling with eventmachine" do
403
+ [[:get, :get_async], [:post, :post_async]].each do |verb, method|
404
+ describe "##{method}" do
405
+ before :each do
406
+ @url_regexp = %r{api.pusherapp.com}
407
+ stub_request(verb, @url_regexp).
408
+ to_return(:status => 200, :body => "{}")
409
+ end
410
+
411
+ let(:call_api) { @client.send(method, '/path') }
412
+
413
+ it "should use http by default" do
414
+ EM.run {
415
+ call_api.callback {
416
+ WebMock.should have_requested(verb, %r{http://api.pusherapp.com/apps/20/path})
417
+ EM.stop
398
418
  }
399
- EM.stop
400
419
  }
401
- }
402
- end
420
+ end
403
421
 
404
- it "should errback with Pusher::Error on unsuccessful response" do
405
- EM.run {
406
- stub_request(verb, @url_regexp).to_return({:status => 400})
422
+ it "should use https if configured" do
423
+ EM.run {
424
+ @client.encrypted = true
425
+ call_api.callback {
426
+ WebMock.should have_requested(verb, %r{https://api.pusherapp.com})
427
+ EM.stop
428
+ }
429
+ }
430
+ end
407
431
 
408
- call_api.errback { |e|
409
- e.class.should == Pusher::Error
410
- EM.stop
411
- }.callback {
412
- fail
432
+ it "should format the respose hash with symbols at first level" do
433
+ EM.run {
434
+ stub_request(verb, @url_regexp).to_return({
435
+ :status => 200,
436
+ :body => MultiJson.encode({'something' => {'a' => 'hash'}})
437
+ })
438
+ call_api.callback { |response|
439
+ response.should == {
440
+ :something => {'a' => 'hash'}
441
+ }
442
+ EM.stop
443
+ }
413
444
  }
414
- }
445
+ end
446
+
447
+ it "should errback with Pusher::Error on unsuccessful response" do
448
+ EM.run {
449
+ stub_request(verb, @url_regexp).to_return({:status => 400})
450
+
451
+ call_api.errback { |e|
452
+ e.class.should == Pusher::Error
453
+ EM.stop
454
+ }.callback {
455
+ fail
456
+ }
457
+ }
458
+ end
415
459
  end
416
460
  end
417
461
  end
@@ -21,5 +21,5 @@ end
21
21
 
22
22
  def hmac(key, data)
23
23
  digest = OpenSSL::Digest::SHA256.new
24
- expected = OpenSSL::HMAC.hexdigest(digest, key, data)
24
+ OpenSSL::HMAC.hexdigest(digest, key, data)
25
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pusher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.3
4
+ version: 0.12.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: 2013-02-03 00:00:00.000000000 Z
12
+ date: 2013-08-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: 0.1.6
46
+ - !ruby/object:Gem::Dependency
47
+ name: httpclient
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.0
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: rspec
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -82,7 +98,7 @@ dependencies:
82
98
  requirements:
83
99
  - - ~>
84
100
  - !ruby/object:Gem::Version
85
- version: 1.0.0
101
+ version: 1.1.0
86
102
  type: :development
87
103
  prerelease: false
88
104
  version_requirements: !ruby/object:Gem::Requirement
@@ -90,7 +106,7 @@ dependencies:
90
106
  requirements:
91
107
  - - ~>
92
108
  - !ruby/object:Gem::Version
93
- version: 1.0.0
109
+ version: 1.1.0
94
110
  - !ruby/object:Gem::Dependency
95
111
  name: rake
96
112
  requirement: !ruby/object:Gem::Requirement
@@ -143,7 +159,6 @@ files:
143
159
  - lib/pusher.rb
144
160
  - lib/pusher/channel.rb
145
161
  - lib/pusher/client.rb
146
- - lib/pusher/query_encoder.rb
147
162
  - lib/pusher/request.rb
148
163
  - lib/pusher/resource.rb
149
164
  - lib/pusher/webhook.rb
@@ -166,7 +181,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
166
181
  version: '0'
167
182
  segments:
168
183
  - 0
169
- hash: -2045572575417180827
184
+ hash: -1060459840263191989
170
185
  required_rubygems_version: !ruby/object:Gem::Requirement
171
186
  none: false
172
187
  requirements:
@@ -175,10 +190,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
190
  version: '0'
176
191
  segments:
177
192
  - 0
178
- hash: -2045572575417180827
193
+ hash: -1060459840263191989
179
194
  requirements: []
180
195
  rubyforge_project:
181
- rubygems_version: 1.8.24
196
+ rubygems_version: 1.8.25
182
197
  signing_key:
183
198
  specification_version: 3
184
199
  summary: Pusher API client
@@ -1,47 +0,0 @@
1
- module Pusher
2
- # Query string encoding extracted with thanks from em-http-request
3
- module QueryEncoder
4
- def encode_query(uri, query)
5
- encoded_query = if query.kind_of?(Hash)
6
- query.map { |k, v| encode_param(k, v) }.join('&')
7
- else
8
- query.to_s
9
- end
10
-
11
- if uri && !uri.query.to_s.empty?
12
- encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&")
13
- end
14
- encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}"
15
- end
16
-
17
- # URL encodes query parameters:
18
- # single k=v, or a URL encoded array, if v is an array of values
19
- def encode_param(k, v)
20
- if v.is_a?(Array)
21
- v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
22
- else
23
- escape(k) + "=" + escape(v)
24
- end
25
- end
26
-
27
- def escape(s)
28
- if defined?(EscapeUtils)
29
- EscapeUtils.escape_url(s.to_s)
30
- else
31
- s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {
32
- '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
33
- }
34
- end
35
- end
36
-
37
- if ''.respond_to?(:bytesize)
38
- def bytesize(string)
39
- string.bytesize
40
- end
41
- else
42
- def bytesize(string)
43
- string.size
44
- end
45
- end
46
- end
47
- end