rapns 1.0.7 → 2.0.0rc1
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/CHANGELOG.md +7 -0
- data/LICENSE +7 -0
- data/README.md +58 -41
- data/bin/rapns +23 -5
- data/lib/generators/rapns_generator.rb +2 -4
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/create_rapns_apps.rb +15 -0
- data/lib/rapns/app.rb +9 -0
- data/lib/rapns/daemon/app_runner.rb +131 -0
- data/lib/rapns/daemon/connection.rb +5 -3
- data/lib/rapns/daemon/delivery_handler.rb +13 -15
- data/lib/rapns/daemon/delivery_handler_pool.rb +8 -10
- data/lib/rapns/daemon/delivery_queue.rb +36 -4
- data/lib/rapns/daemon/feedback_receiver.rb +19 -12
- data/lib/rapns/daemon/feeder.rb +8 -10
- data/lib/rapns/daemon/logger.rb +5 -3
- data/lib/rapns/daemon.rb +52 -38
- data/lib/rapns/notification.rb +16 -5
- data/lib/rapns/patches.rb +2 -2
- data/lib/rapns/version.rb +1 -1
- data/lib/rapns.rb +2 -1
- data/spec/rapns/daemon/app_runner_spec.rb +207 -0
- data/spec/rapns/daemon/connection_spec.rb +177 -236
- data/spec/rapns/daemon/delivery_handler_pool_spec.rb +10 -14
- data/spec/rapns/daemon/delivery_handler_spec.rb +92 -79
- data/spec/rapns/daemon/feedback_receiver_spec.rb +29 -23
- data/spec/rapns/daemon/feeder_spec.rb +40 -44
- data/spec/rapns/daemon/logger_spec.rb +21 -3
- data/spec/rapns/daemon_spec.rb +65 -125
- data/spec/rapns/notification_spec.rb +16 -0
- data/spec/spec_helper.rb +4 -1
- metadata +14 -15
- data/History.md +0 -5
- data/lib/generators/templates/rapns.yml +0 -31
- data/lib/rapns/daemon/certificate.rb +0 -27
- data/lib/rapns/daemon/configuration.rb +0 -98
- data/lib/rapns/daemon/pool.rb +0 -36
- data/spec/rapns/daemon/certificate_spec.rb +0 -22
- data/spec/rapns/daemon/configuration_spec.rb +0 -231
@@ -1,293 +1,234 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe Rapns::Daemon::Connection
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@connection.stub(:setup_at_exit_hook)
|
16
|
-
configuration = mock("Configuration", :certificate_password => "abc123")
|
17
|
-
Rapns::Daemon.stub(:configuration).and_return(configuration)
|
18
|
-
end
|
3
|
+
describe Rapns::Daemon::Connection do
|
4
|
+
let(:ssl_context) { stub(:key= => nil, :cert= => nil) }
|
5
|
+
let(:rsa_key) { stub }
|
6
|
+
let(:certificate) { stub }
|
7
|
+
let(:password) { stub }
|
8
|
+
let(:x509_certificate) { stub }
|
9
|
+
let(:host) { 'gateway.push.apple.com' }
|
10
|
+
let(:port) { '2195' }
|
11
|
+
let(:tcp_socket) { stub(:setsockopt => nil, :close => nil) }
|
12
|
+
let(:ssl_socket) { stub(:sync= => nil, :connect => nil, :close => nil, :write => nil, :flush => nil) }
|
13
|
+
let(:logger) { stub(:info => nil, :error => nil) }
|
14
|
+
let(:connection) { Rapns::Daemon::Connection.new('Connection 0', host, port, certificate, password) }
|
19
15
|
|
20
|
-
it "should set the key on the context" do
|
21
|
-
OpenSSL::PKey::RSA.should_receive(:new).with("certificate contents", "abc123").and_return(@rsa_key)
|
22
|
-
@ssl_context.should_receive(:key=).with(@rsa_key)
|
23
|
-
@connection.connect
|
24
|
-
end
|
25
|
-
|
26
|
-
it "should set the cert on the context" do
|
27
|
-
OpenSSL::X509::Certificate.should_receive(:new).with("certificate contents").and_return(@x509_certificate)
|
28
|
-
@ssl_context.should_receive(:cert=).with(@x509_certificate)
|
29
|
-
@connection.connect
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
describe Rapns::Daemon::Connection, "when connecting the socket" do
|
34
16
|
before do
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@ssl_socket = mock("SSLSocket", :sync= => nil, :connect => nil, :close => nil)
|
42
|
-
OpenSSL::SSL::SSLSocket.stub(:new).and_return(@ssl_socket)
|
43
|
-
@logger = mock("Logger", :info => nil)
|
44
|
-
Rapns::Daemon.stub(:logger).and_return(@logger)
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should create a TCP socket using the configured host and port" do
|
48
|
-
TCPSocket.should_receive(:new).with('gateway.push.apple.com', 2195).and_return(@tcp_socket)
|
49
|
-
@connection.connect
|
17
|
+
OpenSSL::SSL::SSLContext.stub(:new => ssl_context)
|
18
|
+
OpenSSL::PKey::RSA.stub(:new => rsa_key)
|
19
|
+
OpenSSL::X509::Certificate.stub(:new => x509_certificate)
|
20
|
+
TCPSocket.stub(:new => tcp_socket)
|
21
|
+
OpenSSL::SSL::SSLSocket.stub(:new => ssl_socket)
|
22
|
+
Rapns::Daemon.stub(:logger => logger)
|
50
23
|
end
|
51
24
|
|
52
|
-
it "
|
53
|
-
|
54
|
-
|
25
|
+
it "reads the number of bytes from the SSL socket" do
|
26
|
+
ssl_socket.should_receive(:read).with(123)
|
27
|
+
connection.connect
|
28
|
+
connection.read(123)
|
55
29
|
end
|
56
30
|
|
57
|
-
it "
|
58
|
-
|
59
|
-
|
31
|
+
it "selects on the SSL socket until the given timeout" do
|
32
|
+
IO.should_receive(:select).with([ssl_socket], nil, nil, 10)
|
33
|
+
connection.connect
|
34
|
+
connection.select(10)
|
60
35
|
end
|
61
36
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
37
|
+
describe "when setting up the SSL context" do
|
38
|
+
it "sets the key on the context" do
|
39
|
+
OpenSSL::PKey::RSA.should_receive(:new).with(certificate, password).and_return(rsa_key)
|
40
|
+
ssl_context.should_receive(:key=).with(rsa_key)
|
41
|
+
connection.connect
|
42
|
+
end
|
66
43
|
|
67
|
-
|
68
|
-
|
69
|
-
|
44
|
+
it "sets the cert on the context" do
|
45
|
+
OpenSSL::X509::Certificate.should_receive(:new).with(certificate).and_return(x509_certificate)
|
46
|
+
ssl_context.should_receive(:cert=).with(x509_certificate)
|
47
|
+
connection.connect
|
48
|
+
end
|
70
49
|
end
|
71
50
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
51
|
+
describe "when connecting the socket" do
|
52
|
+
it "creates a TCP socket using the configured host and port" do
|
53
|
+
TCPSocket.should_receive(:new).with(host, port).and_return(tcp_socket)
|
54
|
+
connection.connect
|
55
|
+
end
|
77
56
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@ssl_socket = mock("SSLSocket", :close => nil)
|
83
|
-
@tcp_socket = mock("TCPSocket", :close => nil)
|
84
|
-
@connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
|
85
|
-
end
|
57
|
+
it "creates a new SSL socket using the TCP socket and SSL context" do
|
58
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_context).and_return(ssl_socket)
|
59
|
+
connection.connect
|
60
|
+
end
|
86
61
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
62
|
+
it "sets the sync option on the SSL socket" do
|
63
|
+
ssl_socket.should_receive(:sync=).with(true)
|
64
|
+
connection.connect
|
65
|
+
end
|
92
66
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
@connection.close
|
98
|
-
end
|
67
|
+
it "connects the SSL socket" do
|
68
|
+
ssl_socket.should_receive(:connect)
|
69
|
+
connection.connect
|
70
|
+
end
|
99
71
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
72
|
+
it "sets the socket option TCP_NODELAY" do
|
73
|
+
tcp_socket.should_receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
74
|
+
connection.connect
|
75
|
+
end
|
105
76
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
@connection.close
|
77
|
+
it "sets the socket option SO_KEEPALIVE" do
|
78
|
+
tcp_socket.should_receive(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
|
79
|
+
connection.connect
|
80
|
+
end
|
111
81
|
end
|
112
82
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
83
|
+
describe "when shuting down the connection" do
|
84
|
+
it "closes the TCP socket" do
|
85
|
+
connection.connect
|
86
|
+
tcp_socket.should_receive(:close)
|
87
|
+
connection.close
|
88
|
+
end
|
119
89
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
|
127
|
-
end
|
90
|
+
it "does not attempt to close the TCP socket if it is not connected" do
|
91
|
+
connection.connect
|
92
|
+
tcp_socket.should_not_receive(:close)
|
93
|
+
connection.instance_variable_set("@tcp_socket", nil)
|
94
|
+
connection.close
|
95
|
+
end
|
128
96
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end
|
97
|
+
it "closes the SSL socket" do
|
98
|
+
connection.connect
|
99
|
+
ssl_socket.should_receive(:close)
|
100
|
+
connection.close
|
101
|
+
end
|
135
102
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
@connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
|
143
|
-
end
|
103
|
+
it "does not attempt to close the SSL socket if it is not connected" do
|
104
|
+
connection.connect
|
105
|
+
ssl_socket.should_not_receive(:close)
|
106
|
+
connection.instance_variable_set("@ssl_socket", nil)
|
107
|
+
connection.close
|
108
|
+
end
|
144
109
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
110
|
+
it "ignores IOError when the socket is already closed" do
|
111
|
+
tcp_socket.stub(:close).and_raise(IOError)
|
112
|
+
connection.connect
|
113
|
+
expect { connection.close }.should_not raise_error(IOError)
|
114
|
+
end
|
149
115
|
end
|
150
|
-
end
|
151
116
|
|
152
|
-
shared_examples_for "when the write fails" do
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
@connection.stub(:write_data).and_raise(error_type)
|
159
|
-
@connection.stub(:sleep)
|
160
|
-
end
|
117
|
+
shared_examples_for "when the write fails" do
|
118
|
+
before do
|
119
|
+
connection.stub(:sleep)
|
120
|
+
connection.connect
|
121
|
+
ssl_socket.stub(:write).and_raise(error_type)
|
122
|
+
end
|
161
123
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
124
|
+
it "logs that the connection has been lost once only" do
|
125
|
+
logger.should_receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error_type.name}), reconnecting...").once
|
126
|
+
begin
|
127
|
+
connection.write(nil)
|
128
|
+
rescue Rapns::Daemon::ConnectionError
|
129
|
+
end
|
167
130
|
end
|
168
|
-
end
|
169
131
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
132
|
+
it "retries to make a connection 3 times" do
|
133
|
+
connection.should_receive(:reconnect).exactly(3).times
|
134
|
+
begin
|
135
|
+
connection.write(nil)
|
136
|
+
rescue Rapns::Daemon::ConnectionError
|
137
|
+
end
|
175
138
|
end
|
176
|
-
end
|
177
139
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
140
|
+
it "raises a ConnectionError after 3 attempts at reconnecting" do
|
141
|
+
expect do
|
142
|
+
connection.write(nil)
|
143
|
+
end.to raise_error(Rapns::Daemon::ConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error_type.name}).")
|
144
|
+
end
|
183
145
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
146
|
+
it "sleeps 1 second before retrying the connection" do
|
147
|
+
connection.should_receive(:sleep).with(1)
|
148
|
+
begin
|
149
|
+
connection.write(nil)
|
150
|
+
rescue Rapns::Daemon::ConnectionError
|
151
|
+
end
|
189
152
|
end
|
190
153
|
end
|
191
|
-
end
|
192
154
|
|
193
|
-
describe
|
194
|
-
|
155
|
+
describe "when write raises an Errno::EPIPE" do
|
156
|
+
it_should_behave_like "when the write fails"
|
195
157
|
|
196
|
-
|
197
|
-
|
158
|
+
def error_type
|
159
|
+
Errno::EPIPE
|
160
|
+
end
|
198
161
|
end
|
199
|
-
end
|
200
162
|
|
201
|
-
describe
|
202
|
-
|
163
|
+
describe "when write raises an Errno::ETIMEDOUT" do
|
164
|
+
it_should_behave_like "when the write fails"
|
203
165
|
|
204
|
-
|
205
|
-
|
166
|
+
def error_type
|
167
|
+
Errno::ETIMEDOUT
|
168
|
+
end
|
206
169
|
end
|
207
|
-
end
|
208
170
|
|
209
|
-
describe
|
210
|
-
|
171
|
+
describe "when write raises an OpenSSL::SSL::SSLError" do
|
172
|
+
it_should_behave_like "when the write fails"
|
211
173
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
end
|
216
|
-
|
217
|
-
describe Rapns::Daemon::Connection, "when reconnecting" do
|
218
|
-
before do
|
219
|
-
@connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
|
220
|
-
@connection.stub(:close)
|
221
|
-
@connection.stub(:connect_socket)
|
174
|
+
def error_type
|
175
|
+
OpenSSL::SSL::SSLError
|
176
|
+
end
|
222
177
|
end
|
223
178
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
179
|
+
describe "when reconnecting" do
|
180
|
+
it 'closes the socket' do
|
181
|
+
connection.should_receive(:close)
|
182
|
+
connection.send(:reconnect)
|
183
|
+
end
|
228
184
|
|
229
|
-
|
230
|
-
|
231
|
-
|
185
|
+
it 'connects the socket' do
|
186
|
+
connection.should_receive(:connect_socket)
|
187
|
+
connection.send(:reconnect)
|
188
|
+
end
|
232
189
|
end
|
233
|
-
end
|
234
190
|
|
235
|
-
describe
|
236
|
-
|
237
|
-
@connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
|
238
|
-
@ssl_socket = mock("SSLSocket", :write => nil, :flush => nil, :close => nil)
|
239
|
-
@tcp_socket = mock("TCPSocket", :close => nil)
|
240
|
-
@connection.stub(:setup_ssl_context)
|
241
|
-
@connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
|
242
|
-
@connection.connect
|
243
|
-
end
|
191
|
+
describe "when sending a notification" do
|
192
|
+
before { connection.connect }
|
244
193
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
194
|
+
it "writes the data to the SSL socket" do
|
195
|
+
ssl_socket.should_receive(:write).with("blah")
|
196
|
+
connection.write("blah")
|
197
|
+
end
|
249
198
|
|
250
|
-
|
251
|
-
|
252
|
-
|
199
|
+
it "flushes the SSL socket" do
|
200
|
+
ssl_socket.should_receive(:flush)
|
201
|
+
connection.write("blah")
|
202
|
+
end
|
253
203
|
end
|
254
|
-
end
|
255
204
|
|
256
|
-
describe
|
257
|
-
|
258
|
-
@connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
|
259
|
-
@ssl_socket = mock("SSLSocket", :write => nil, :flush => nil, :close => nil)
|
260
|
-
@tcp_socket = mock("TCPSocket", :close => nil)
|
261
|
-
@connection.stub(:setup_ssl_context)
|
262
|
-
@connection.stub(:connect_socket => [@tcp_socket, @ssl_socket])
|
263
|
-
@logger = mock("Logger", :info => nil)
|
264
|
-
Rapns::Daemon.stub(:logger).and_return(@logger)
|
265
|
-
@connection.connect
|
266
|
-
end
|
205
|
+
describe 'idle period' do
|
206
|
+
before { connection.connect }
|
267
207
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
208
|
+
it 'reconnects if the connection has been idle for more than the defined period' do
|
209
|
+
Rapns::Daemon::Connection.stub(:idle_period => 0.1)
|
210
|
+
sleep 0.2
|
211
|
+
connection.should_receive(:reconnect)
|
212
|
+
connection.write('blah')
|
213
|
+
end
|
274
214
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
215
|
+
it 'resets the last write time' do
|
216
|
+
now = Time.now
|
217
|
+
Time.stub(:now => now)
|
218
|
+
connection.write('blah')
|
219
|
+
connection.last_write.should == now
|
220
|
+
end
|
281
221
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
222
|
+
it 'does not reconnect if the connection has not been idle for more than the defined period' do
|
223
|
+
connection.should_not_receive(:reconnect)
|
224
|
+
connection.write('blah')
|
225
|
+
end
|
286
226
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
227
|
+
it 'logs the the connection is idle' do
|
228
|
+
Rapns::Daemon::Connection.stub(:idle_period => 0.1)
|
229
|
+
sleep 0.2
|
230
|
+
Rapns::Daemon.logger.should_receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...')
|
231
|
+
connection.write('blah')
|
232
|
+
end
|
292
233
|
end
|
293
234
|
end
|
@@ -1,21 +1,17 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Rapns::Daemon::DeliveryHandlerPool do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
let(:pool) { Rapns::Daemon::DeliveryHandlerPool.new }
|
5
|
+
let(:handler) { stub(:start => nil, :stop => nil) }
|
6
|
+
|
7
|
+
it 'starts the handler when added to the pool' do
|
8
|
+
handler.should_receive(:start)
|
9
|
+
pool << handler
|
9
10
|
end
|
10
11
|
|
11
|
-
it
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
it "waits for each handle to stop" do
|
17
|
-
@pool.populate
|
18
|
-
@handler.should_receive(:stop).exactly(3).times
|
19
|
-
@pool.drain
|
12
|
+
it 'stops each handler when drained' do
|
13
|
+
pool << handler
|
14
|
+
handler.should_receive(:stop)
|
15
|
+
pool.drain
|
20
16
|
end
|
21
17
|
end
|