pusher 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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