pusher 0.4.2 → 0.4.3

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/README.md CHANGED
@@ -25,6 +25,19 @@ Errors are logged to `Pusher.logger`. It will by default use `Logger` from stdli
25
25
 
26
26
  Pusher.logger = Rails.logger
27
27
 
28
+ Asynchronous triggering
29
+ -----------------------
30
+
31
+ To avoid blocking in a typical web application, if you are running inside eventmachine (for example if you use the thin server), you may wish to use the `trigger_async` method which uses the em-http-request gem to make api requests to pusher. It returns a deferrable which you can optionally bind to with success and failure callbacks. This is not a gem dependency, so you will need to install it manually.
32
+
33
+ d = Pusher['a_channel'].trigger('an_event', {:some => 'data'}, socket_id)
34
+ d.callback {
35
+ # Do something on success
36
+ }
37
+ d.errback { |error|
38
+ # error is a pusher exception
39
+ }
40
+
28
41
  Copyright
29
42
  ---------
30
43
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.2
1
+ 0.4.3
@@ -1,7 +1,3 @@
1
- require 'json'
2
- require 'uri'
3
- require 'net/http'
4
-
5
1
  autoload 'Logger', 'logger'
6
2
 
7
3
  module Pusher
@@ -1,6 +1,9 @@
1
1
  require 'crack/core_extensions' # Used for Hash#to_params
2
2
  require 'digest/md5'
3
3
 
4
+ require 'json'
5
+ require 'uri'
6
+
4
7
  module Pusher
5
8
  class Channel
6
9
  def initialize(app_id, name)
@@ -9,48 +12,51 @@ module Pusher
9
12
  :port => Pusher.port,
10
13
  :path => "/apps/#{app_id}/channels/#{name}/events"
11
14
  })
12
- @http = Net::HTTP.new(@uri.host, @uri.port)
13
15
  end
14
16
 
15
- def trigger!(event_name, data, socket_id = nil)
16
- params = {
17
- :name => event_name,
18
- }
19
- params[:socket_id] = socket_id if socket_id
20
-
21
- body = case data
22
- when String
23
- data
24
- else
17
+ def trigger_async(event_name, data, socket_id = nil, &block)
18
+ unless defined?(EventMachine) && EventMachine.reactor_running?
19
+ raise Error, "In order to use trigger_async you must be running inside an eventmachine loop"
20
+ end
21
+ require 'em-http' unless defined?(EventMachine::HttpRequest)
22
+
23
+ @http_async ||= EventMachine::HttpRequest.new(@uri)
24
+
25
+ query, body = construct_request(event_name, data, socket_id)
26
+
27
+ deferrable = EM::DefaultDeferrable.new
28
+
29
+ http = @http_async.post({
30
+ :query => query, :timeout => 2, :body => body
31
+ })
32
+ http.callback {
25
33
  begin
26
- self.class.turn_into_json(data)
34
+ handle_response(http.response_header.status, http.response.chomp)
35
+ deferrable.succeed
27
36
  rescue => e
28
- Pusher.logger.error("Could not convert #{data.inspect} into JSON")
29
- raise e
37
+ deferrable.fail(e)
30
38
  end
31
- end
32
- params[:body_md5] = Digest::MD5.hexdigest(body)
33
-
34
- request = Authentication::Request.new('POST', @uri.path, params)
35
- auth_hash = request.sign(Pusher.authentication_token)
39
+ }
40
+ http.errback {
41
+ Pusher.logger.debug("Network error connecting to pusher: #{http.inspect}")
42
+ deferrable.fail(Error.new("Network error connecting to pusher"))
43
+ }
44
+
45
+ deferrable
46
+ end
36
47
 
37
- query_params = params.merge(auth_hash)
38
- @uri.query = query_params.to_params
48
+ def trigger!(event_name, data, socket_id = nil)
49
+ require 'net/http' unless defined?(Net::HTTP)
39
50
 
40
- response = @http.post("#{@uri.path}?#{@uri.query}", body, {
51
+ @http_sync ||= Net::HTTP.new(@uri.host, @uri.port)
52
+
53
+ query, body = construct_request(event_name, data, socket_id)
54
+
55
+ response = @http_sync.post("#{@uri.path}?#{query.to_params}", body, {
41
56
  'Content-Type'=> 'application/json'
42
57
  })
43
58
 
44
- case response.code
45
- when "202"
46
- return true
47
- when "401"
48
- raise AuthenticationError, response.body.chomp
49
- when "404"
50
- raise Error, "Resource not found: app_id is probably invalid"
51
- else
52
- raise Error, "Unknown error in Pusher: #{response.body.chomp}"
53
- end
59
+ handle_response(response.code.to_i, response.body.chomp)
54
60
  end
55
61
 
56
62
  def trigger(event_name, data, socket_id = nil)
@@ -73,5 +79,47 @@ module Pusher
73
79
  Pusher.logger.error("#{e.message} (#{e.class})")
74
80
  Pusher.logger.debug(e.backtrace.join("\n"))
75
81
  end
82
+
83
+ def construct_request(event_name, data, socket_id)
84
+ params = {
85
+ :name => event_name,
86
+ }
87
+ params[:socket_id] = socket_id if socket_id
88
+
89
+ body = case data
90
+ when String
91
+ data
92
+ else
93
+ begin
94
+ self.class.turn_into_json(data)
95
+ rescue => e
96
+ Pusher.logger.error("Could not convert #{data.inspect} into JSON")
97
+ raise e
98
+ end
99
+ end
100
+ params[:body_md5] = Digest::MD5.hexdigest(body)
101
+
102
+ request = Authentication::Request.new('POST', @uri.path, params)
103
+ auth_hash = request.sign(Pusher.authentication_token)
104
+
105
+ query_params = params.merge(auth_hash)
106
+
107
+ return query_params, body
108
+ end
109
+
110
+ def handle_response(status_code, body)
111
+ case status_code
112
+ when 202
113
+ return true
114
+ when 400
115
+ raise Error, "Bad request: #{body}"
116
+ when 401
117
+ raise AuthenticationError, body
118
+ when 404
119
+ raise Error, "Resource not found: app_id is probably invalid"
120
+ else
121
+ raise Error, "Unknown error in Pusher: #{body}"
122
+ end
123
+ end
76
124
  end
77
125
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{pusher}
8
- s.version = "0.4.2"
8
+ s.version = "0.4.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["New Bamboo"]
12
- s.date = %q{2010-05-05}
12
+ s.date = %q{2010-05-11}
13
13
  s.description = %q{Wrapper for pusherapp.com REST api}
14
14
  s.email = %q{support@pusherapp.com}
15
15
  s.extra_rdoc_files = [
@@ -1,6 +1,8 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
3
  require 'webmock/rspec'
4
+ require 'em-http'
5
+ require 'em-http/mock'
4
6
 
5
7
  describe Pusher do
6
8
  describe 'configuration' do
@@ -166,5 +168,98 @@ describe Pusher do
166
168
  Pusher['test_channel'].trigger('new_event', 'Some data')
167
169
  end
168
170
  end
171
+
172
+ describe "Channgel#trigger_async" do
173
+ #in order to match URLs when testing http requests
174
+ #override the method that converts query hash to string
175
+ #to include a sort so URL is consistent
176
+ module EventMachine
177
+ module HttpEncoding
178
+ def encode_query(path, query, uri_query)
179
+ encoded_query = if query.kind_of?(Hash)
180
+ query.sort{|a, b| a.to_s <=> b.to_s}.
181
+ map { |k, v| encode_param(k, v) }.
182
+ join('&')
183
+ else
184
+ query.to_s
185
+ end
186
+ if !uri_query.to_s.empty?
187
+ encoded_query = [encoded_query, uri_query].reject {|part| part.empty?}.join("&")
188
+ end
189
+ return path if encoded_query.to_s.empty?
190
+ "#{path}?#{encoded_query}"
191
+ end
192
+ end
193
+ end
194
+
195
+ before :each do
196
+ EM::HttpRequest = EM::MockHttpRequest
197
+ EM::HttpRequest.reset_registry!
198
+ EM::HttpRequest.reset_counts!
199
+ EM::HttpRequest.pass_through_requests = false
200
+ end
201
+
202
+ it "should return a deferrable which succeeds in success case" do
203
+ # Yeah, mocking EM::MockHttpRequest isn't that feature rich :)
204
+ Time.stub(:now).and_return(123)
205
+
206
+ url = 'http://api.pusherapp.com:80/apps/20/channels/test_channel/events?auth_key=12345678900000001&auth_signature=0ffe2a3749f886ca69c3f516a30c7bc9a12d2ebd8bda5b718b90ad58507c8261&auth_timestamp=123&auth_version=1.0&body_md5=5b82f8bf4df2bfb0e66ccaa7306fd024&name=new_event'
207
+
208
+ data = <<-RESPONSE.gsub(/^ +/, '')
209
+ HTTP/1.1 202 Accepted
210
+ Content-Type: text/html
211
+ Content-Length: 13
212
+ Connection: keep-alive
213
+ Server: thin 1.2.7 codename No Hup
214
+
215
+ 202 ACCEPTED
216
+ RESPONSE
217
+
218
+ EM::HttpRequest.register(url, :post, data)
219
+
220
+ EM.run {
221
+ d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
222
+ d.callback {
223
+ EM::HttpRequest.count(url, :post).should == 1
224
+ EM.stop
225
+ }
226
+ d.errback {
227
+ fail
228
+ EM.stop
229
+ }
230
+ }
231
+ end
232
+
233
+ it "should return a deferrable which fails (with exception) in fail case" do
234
+ # Yeah, mocking EM::MockHttpRequest isn't that feature rich :)
235
+ Time.stub(:now).and_return(123)
236
+
237
+ url = 'http://api.pusherapp.com:80/apps/20/channels/test_channel/events?auth_key=12345678900000001&auth_signature=0ffe2a3749f886ca69c3f516a30c7bc9a12d2ebd8bda5b718b90ad58507c8261&auth_timestamp=123&auth_version=1.0&body_md5=5b82f8bf4df2bfb0e66ccaa7306fd024&name=new_event'
238
+
239
+ data = <<-RESPONSE.gsub(/^ +/, '')
240
+ HTTP/1.1 401 Unauthorized
241
+ Content-Type: text/html
242
+ Content-Length: 130
243
+ Connection: keep-alive
244
+ Server: thin 1.2.7 codename No Hup
245
+
246
+ 401 UNAUTHORIZED: Timestamp expired: Given timestamp (2010-05-05T11:24:42Z) not within 600s of server time (2010-05-05T11:51:42Z)
247
+ RESPONSE
248
+
249
+ EM::HttpRequest.register(url, :post, data)
250
+
251
+ EM.run {
252
+ d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
253
+ d.callback {
254
+ fail
255
+ }
256
+ d.errback { |error|
257
+ EM::HttpRequest.count(url, :post).should == 1
258
+ error.should be_kind_of(Pusher::AuthenticationError)
259
+ EM.stop
260
+ }
261
+ }
262
+ end
263
+ end
169
264
  end
170
265
  end
@@ -7,6 +7,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
7
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
8
 
9
9
  require 'pusher'
10
+ require 'eventmachine'
10
11
 
11
12
  Spec::Runner.configure do |config|
12
13
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 4
8
- - 2
9
- version: 0.4.2
8
+ - 3
9
+ version: 0.4.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - New Bamboo
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-05 00:00:00 +01:00
17
+ date: 2010-05-11 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency