pusher 0.10.0 → 0.11.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.
@@ -0,0 +1,47 @@
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
@@ -4,59 +4,12 @@ require 'multi_json'
4
4
 
5
5
  module Pusher
6
6
  class Request
7
- # Query string encoding extracted with thanks from em-http-request
8
- module QueryEncoder
9
- def encode_query(uri, query)
10
- encoded_query = if query.kind_of?(Hash)
11
- query.map { |k, v| encode_param(k, v) }.join('&')
12
- else
13
- query.to_s
14
- end
15
-
16
- if uri && !uri.query.to_s.empty?
17
- encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&")
18
- end
19
- encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}"
20
- end
21
-
22
- # URL encodes query parameters:
23
- # single k=v, or a URL encoded array, if v is an array of values
24
- def encode_param(k, v)
25
- if v.is_a?(Array)
26
- v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
27
- else
28
- escape(k) + "=" + escape(v)
29
- end
30
- end
31
-
32
- def escape(s)
33
- if defined?(EscapeUtils)
34
- EscapeUtils.escape_url(s.to_s)
35
- else
36
- s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {
37
- '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
38
- }
39
- end
40
- end
41
-
42
- if ''.respond_to?(:bytesize)
43
- def bytesize(string)
44
- string.bytesize
45
- end
46
- else
47
- def bytesize(string)
48
- string.size
49
- end
50
- end
51
- end
52
-
53
7
  include QueryEncoder
54
8
 
55
9
  attr_reader :body, :params
56
10
 
57
- def initialize(verb, uri, params, body = nil, token = nil, client = Pusher)
58
- @verb = verb
59
- @uri = uri
11
+ def initialize(client, verb, uri, params, body = nil)
12
+ @client, @verb, @uri = client, verb, uri
60
13
 
61
14
  if body
62
15
  @body = body
@@ -64,36 +17,25 @@ module Pusher
64
17
  end
65
18
 
66
19
  request = Signature::Request.new(verb.to_s.upcase, uri.path, params)
67
- request.sign(token || client.authentication_token)
20
+ request.sign(client.authentication_token)
68
21
  @params = request.signed_params
69
22
  end
70
23
 
71
24
  def send_sync
72
- if ssl?
73
- require 'net/https' unless defined?(Net::HTTPS)
74
- else
75
- require 'net/http' unless defined?(Net::HTTP)
76
- end
77
-
78
- @http_sync ||= begin
79
- http = Net::HTTP.new(@uri.host, @uri.port)
80
- http.use_ssl = true if ssl?
81
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl?
82
- http
83
- end
25
+ http = @client.net_http_client
84
26
 
85
27
  begin
86
28
  case @verb
87
29
  when :post
88
- response = @http_sync.post(encode_query(@uri, @params), @body, {
30
+ response = http.post(encode_query(@uri, @params), @body, {
89
31
  'Content-Type'=> 'application/json'
90
32
  })
91
33
  when :get
92
- response = @http_sync.get(encode_query(@uri, @params), {
34
+ response = http.get(encode_query(@uri, @params), {
93
35
  'Content-Type'=> 'application/json'
94
36
  })
95
37
  else
96
- raise "Unknown verb"
38
+ raise "Unsuported verb"
97
39
  end
98
40
  rescue Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED,
99
41
  Errno::ETIMEDOUT, Errno::EHOSTUNREACH,
@@ -111,31 +53,36 @@ module Pusher
111
53
  end
112
54
 
113
55
  def send_async
114
- unless defined?(EventMachine) && EventMachine.reactor_running?
115
- raise Error, "In order to use trigger_async you must be running inside an eventmachine loop"
56
+ df = EM::DefaultDeferrable.new
57
+
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"
116
72
  end
117
- require 'em-http' unless defined?(EventMachine::HttpRequest)
118
-
119
- deferrable = EM::DefaultDeferrable.new
120
-
121
- http = EventMachine::HttpRequest.new(@uri).post({
122
- :query => @params, :timeout => 5, :body => @body,
123
- :head => {'Content-Type'=> 'application/json'}
124
- })
125
73
  http.callback {
126
74
  begin
127
- handle_response(http.response_header.status, http.response.chomp)
128
- deferrable.succeed
75
+ df.succeed(handle_response(http.response_header.status, http.response.chomp))
129
76
  rescue => e
130
- deferrable.fail(e)
77
+ df.fail(e)
131
78
  end
132
79
  }
133
80
  http.errback {
134
81
  Pusher.logger.debug("Network error connecting to pusher: #{http.inspect}")
135
- deferrable.fail(Error.new("Network error connecting to pusher"))
82
+ df.fail(Error.new("Network error connecting to pusher"))
136
83
  }
137
84
 
138
- deferrable
85
+ df
139
86
  end
140
87
 
141
88
  private
@@ -151,16 +98,14 @@ module Pusher
151
98
  when 401
152
99
  raise AuthenticationError, body
153
100
  when 404
154
- raise Error, "Resource not found: app_id is probably invalid"
101
+ raise Error, "404 Not found (#{@uri.path})"
102
+ when 407
103
+ raise Error, "Proxy Authentication Required"
155
104
  else
156
105
  raise Error, "Unknown error (status code #{status_code}): #{body}"
157
106
  end
158
107
  end
159
108
 
160
- def ssl?
161
- @uri.scheme == 'https'
162
- end
163
-
164
109
  def symbolize_first_level(hash)
165
110
  hash.inject({}) do |result, (key, value)|
166
111
  result[key.to_sym] = value
@@ -0,0 +1,36 @@
1
+ module Pusher
2
+ class Resource
3
+ def initialize(client, path)
4
+ @client = client
5
+ @path = path
6
+ end
7
+
8
+ def get(params)
9
+ create_request(:get, params).send_sync
10
+ end
11
+
12
+ def get_async(params)
13
+ create_request(:get, params).send_async
14
+ end
15
+
16
+ def post(params)
17
+ body = MultiJson.encode(params)
18
+ create_request(:post, {}, body).send_sync
19
+ end
20
+
21
+ def post_async(params)
22
+ body = MultiJson.encode(params)
23
+ create_request(:post, {}, body).send_async
24
+ end
25
+
26
+ private
27
+
28
+ def create_request(verb, params, body = nil)
29
+ Request.new(@client, verb, url, params, body)
30
+ end
31
+
32
+ def url
33
+ @_url ||= @client.url(@path)
34
+ end
35
+ end
36
+ end
@@ -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.10.0"
6
+ s.version = "0.11.0"
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Pusher"]
9
9
  s.email = ["support@pusher.com"]
@@ -9,108 +9,26 @@ describe Pusher::Channel do
9
9
  :host => 'api.pusherapp.com',
10
10
  :port => 80,
11
11
  })
12
- @client.encrypted = false
13
12
  @channel = @client['test_channel']
14
-
15
- WebMock.reset!
16
- WebMock.disable_net_connect!
17
13
  end
18
14
 
19
- let(:pusher_url_regexp) { %r{/apps/20/channels/test_channel/events} }
15
+ let(:pusher_url_regexp) { %r{/apps/20/events} }
20
16
 
21
17
  def stub_post(status, body = nil)
22
18
  options = {:status => status}
23
19
  options.merge!({:body => body}) if body
24
20
 
25
- WebMock.stub_request(:post, pusher_url_regexp).to_return(options)
21
+ stub_request(:post, pusher_url_regexp).to_return(options)
26
22
  end
27
23
 
28
24
  def stub_post_to_raise(e)
29
- WebMock.stub_request(:post, pusher_url_regexp).to_raise(e)
25
+ stub_request(:post, pusher_url_regexp).to_raise(e)
30
26
  end
31
27
 
32
- describe 'trigger!' do
33
- before :each do
34
- stub_post 202
35
- end
36
-
37
- it 'should configure HTTP library to talk to pusher API' do
38
- @channel.trigger!('new_event', 'Some data')
39
- WebMock.should have_requested(:post, %r{http://api.pusherapp.com})
40
- end
41
-
42
- it "should POST to https api if ssl enabled" do
43
- @client.encrypted = true
44
- encrypted_channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
45
- encrypted_channel.trigger('new_event', 'Some data')
46
- WebMock.should have_requested(:post, %r{https://api.pusherapp.com})
47
- end
48
-
49
- it 'should POST hashes by encoding as JSON in the request body' do
50
- @channel.trigger!('new_event', {
51
- :name => 'Pusher',
52
- :last_name => 'App'
53
- })
54
- WebMock.should have_requested(:post, pusher_url_regexp).with { |req|
55
- query_hash = req.uri.query_values
56
- query_hash["name"].should == 'new_event'
57
- query_hash["auth_key"].should == @client.key
58
- query_hash["auth_timestamp"].should_not be_nil
59
-
60
- parsed = MultiJson.decode(req.body)
61
- parsed.should == {
62
- "name" => 'Pusher',
63
- "last_name" => 'App'
64
- }
65
-
66
- req.headers['Content-Type'].should == 'application/json'
67
- }
68
- end
69
-
70
- it "should POST string data unmodified in request body" do
71
- string = "foo\nbar\""
72
- @channel.trigger!('new_event', string)
73
- WebMock.should have_requested(:post, pusher_url_regexp).with { |req| req.body.should == "foo\nbar\"" }
74
- end
75
-
76
- def trigger
77
- lambda { @channel.trigger!('new_event', 'Some data') }
78
- end
79
-
80
- it "should catch all Net::HTTP exceptions and raise a Pusher::HTTPError, exposing the original error if required" do
81
- stub_post_to_raise Timeout::Error
82
- error_raised = nil
83
-
84
- begin
85
- trigger.call
86
- rescue => e
87
- error_raised = e
88
- end
89
-
90
- error_raised.class.should == Pusher::HTTPError
91
- error_raised.message.should == 'Exception from WebMock (Timeout::Error)'
92
- error_raised.original_error.class.should == Timeout::Error
93
- end
94
-
95
-
96
- it "should raise Pusher::Error if pusher returns 400" do
97
- stub_post 400
98
- trigger.should raise_error(Pusher::Error)
99
- end
100
-
101
- it "should raise AuthenticationError if pusher returns 401" do
102
- stub_post 401
103
- trigger.should raise_error(Pusher::AuthenticationError)
104
- end
105
-
106
- it "should raise Pusher::Error if pusher returns 404" do
107
- stub_post 404
108
- trigger.should raise_error(Pusher::Error, 'Resource not found: app_id is probably invalid')
109
- end
110
-
111
- it "should raise Pusher::Error if pusher returns 500" do
112
- stub_post 500, "some error"
113
- trigger.should raise_error(Pusher::Error, 'Unknown error (status code 500): some error')
28
+ describe '#trigger!' do
29
+ it "should use @client.trigger internally" do
30
+ @client.should_receive(:trigger)
31
+ @channel.trigger('new_event', 'Some data')
114
32
  end
115
33
  end
116
34
 
@@ -134,70 +52,21 @@ describe Pusher::Channel do
134
52
  end
135
53
 
136
54
  describe "#trigger_async" do
137
- it "should by default POST to http api" do
138
- EM.run {
139
- stub_post 202
140
- @channel.trigger_async('new_event', 'Some data').callback {
141
- WebMock.should have_requested(:post, %r{http://api.pusherapp.com})
142
- EM.stop
143
- }
144
- }
145
- end
146
-
147
- it "should POST to https api if ssl enabled" do
148
- @client.encrypted = true
149
- EM.run {
150
- stub_post 202
151
- channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
152
- channel.trigger_async('new_event', 'Some data').callback {
153
- WebMock.should have_requested(:post, %r{https://api.pusherapp.com})
154
- EM.stop
155
- }
156
- }
157
- end
158
-
159
- it "should return a deferrable which succeeds in success case" do
160
- stub_post 202
161
-
162
- EM.run {
163
- d = @channel.trigger_async('new_event', 'Some data')
164
- d.callback {
165
- WebMock.should have_requested(:post, pusher_url_regexp)
166
- EM.stop
167
- }
168
- d.errback {
169
- fail
170
- EM.stop
171
- }
172
- }
173
- end
174
-
175
- it "should return a deferrable which fails (with exception) in fail case" do
176
- stub_post 401
177
-
178
- EM.run {
179
- d = @channel.trigger_async('new_event', 'Some data')
180
- d.callback {
181
- fail
182
- }
183
- d.errback { |error|
184
- WebMock.should have_requested(:post, pusher_url_regexp)
185
- error.should be_kind_of(Pusher::AuthenticationError)
186
- EM.stop
187
- }
188
- }
55
+ it "should use @client.trigger_async internally" do
56
+ @client.should_receive(:trigger_async)
57
+ @channel.trigger_async('new_event', 'Some data')
189
58
  end
190
59
  end
191
60
 
192
61
  describe '#info' do
193
62
  it "should call the Client#channel_info" do
194
- @client.should_receive(:channel_info).with('mychannel', anything)
63
+ @client.should_receive(:get).with("/channels/mychannel", anything)
195
64
  @channel = @client['mychannel']
196
65
  @channel.info
197
66
  end
198
67
 
199
68
  it "should assemble the requested attribes into the info option" do
200
- @client.should_receive(:channel_info).with(anything, {
69
+ @client.should_receive(:get).with(anything, {
201
70
  :info => "user_count,connection_count"
202
71
  })
203
72
  @channel = @client['presence-foo']