rapns 2.0.5 → 3.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/generators/rapns_generator.rb +1 -0
- data/lib/generators/templates/add_gcm.rb +86 -0
- data/lib/generators/templates/create_rapns_notifications.rb +1 -1
- data/lib/rapns/apns/app.rb +8 -0
- data/lib/rapns/apns/binary_notification_validator.rb +12 -0
- data/lib/rapns/apns/device_token_format_validator.rb +12 -0
- data/lib/rapns/apns/feedback.rb +14 -0
- data/lib/rapns/apns/notification.rb +84 -0
- data/lib/rapns/app.rb +5 -6
- data/lib/rapns/{config.rb → configuration.rb} +5 -5
- data/lib/rapns/daemon/apns/app_runner.rb +36 -0
- data/lib/rapns/daemon/apns/connection.rb +113 -0
- data/lib/rapns/daemon/apns/delivery.rb +63 -0
- data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
- data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
- data/lib/rapns/daemon/app_runner.rb +76 -77
- data/lib/rapns/daemon/database_reconnectable.rb +3 -3
- data/lib/rapns/daemon/delivery.rb +43 -0
- data/lib/rapns/daemon/delivery_error.rb +6 -2
- data/lib/rapns/daemon/delivery_handler.rb +13 -79
- data/lib/rapns/daemon/delivery_queue_18.rb +2 -2
- data/lib/rapns/daemon/delivery_queue_19.rb +3 -3
- data/lib/rapns/daemon/feeder.rb +5 -5
- data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
- data/lib/rapns/daemon/gcm/delivery.rb +206 -0
- data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
- data/lib/rapns/daemon.rb +31 -20
- data/lib/rapns/gcm/app.rb +7 -0
- data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rapns/gcm/notification.rb +31 -0
- data/lib/rapns/gcm/payload_size_validator.rb +13 -0
- data/lib/rapns/multi_json_helper.rb +16 -0
- data/lib/rapns/notification.rb +28 -95
- data/lib/rapns/version.rb +1 -1
- data/lib/rapns.rb +14 -4
- data/lib/tasks/cane.rake +19 -0
- data/lib/tasks/test.rake +34 -0
- data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
- data/spec/acceptance_spec_helper.rb +85 -0
- data/spec/support/simplecov_helper.rb +13 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/unit/apns/app_spec.rb +15 -0
- data/spec/unit/apns/feedback_spec.rb +12 -0
- data/spec/{rapns → unit/apns}/notification_spec.rb +44 -72
- data/spec/unit/app_spec.rb +18 -0
- data/spec/unit/daemon/apns/app_runner_spec.rb +37 -0
- data/spec/{rapns/daemon → unit/daemon/apns}/connection_spec.rb +9 -9
- data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +154 -0
- data/spec/{rapns/daemon → unit/daemon/apns}/feedback_receiver_spec.rb +14 -14
- data/spec/unit/daemon/app_runner_shared.rb +66 -0
- data/spec/unit/daemon/app_runner_spec.rb +78 -0
- data/spec/{rapns → unit}/daemon/database_reconnectable_spec.rb +4 -5
- data/spec/{rapns → unit}/daemon/delivery_error_spec.rb +2 -2
- data/spec/unit/daemon/delivery_handler_shared.rb +19 -0
- data/spec/{rapns → unit}/daemon/delivery_queue_spec.rb +1 -1
- data/spec/{rapns → unit}/daemon/feeder_spec.rb +33 -33
- data/spec/unit/daemon/gcm/app_runner_spec.rb +15 -0
- data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
- data/spec/{rapns → unit}/daemon/interruptible_sleep_spec.rb +1 -1
- data/spec/{rapns → unit}/daemon/logger_spec.rb +1 -1
- data/spec/{rapns → unit}/daemon_spec.rb +1 -1
- data/spec/unit/gcm/app_spec.rb +5 -0
- data/spec/unit/gcm/notification_spec.rb +55 -0
- data/spec/unit/notification_shared.rb +38 -0
- data/spec/unit/notification_spec.rb +6 -0
- data/spec/{rapns/app_spec.rb → unit_spec_helper.rb} +76 -16
- metadata +107 -45
- data/lib/rapns/binary_notification_validator.rb +0 -10
- data/lib/rapns/daemon/connection.rb +0 -114
- data/lib/rapns/daemon/delivery_handler_pool.rb +0 -18
- data/lib/rapns/daemon/disconnection_error.rb +0 -14
- data/lib/rapns/daemon/feedback_receiver.rb +0 -82
- data/lib/rapns/device_token_format_validator.rb +0 -10
- data/lib/rapns/feedback.rb +0 -12
- data/spec/rapns/daemon/app_runner_spec.rb +0 -193
- data/spec/rapns/daemon/delivery_handler_pool_spec.rb +0 -17
- data/spec/rapns/daemon/delivery_handler_spec.rb +0 -206
- data/spec/rapns/feedback_spec.rb +0 -12
- data/spec/spec_helper.rb +0 -78
@@ -1,130 +1,129 @@
|
|
1
1
|
module Rapns
|
2
|
-
|
2
|
+
module Daemon
|
3
3
|
class AppRunner
|
4
|
-
HOSTS = {
|
5
|
-
:production => {
|
6
|
-
:push => ['gateway.push.apple.com', 2195],
|
7
|
-
:feedback => ['feedback.push.apple.com', 2196]
|
8
|
-
},
|
9
|
-
:development => {
|
10
|
-
:push => ['gateway.sandbox.push.apple.com', 2195],
|
11
|
-
:feedback => ['feedback.sandbox.push.apple.com', 2196]
|
12
|
-
}
|
13
|
-
}
|
14
|
-
|
15
4
|
class << self
|
16
|
-
attr_reader :
|
5
|
+
attr_reader :runners # TODO: Needed?
|
17
6
|
end
|
18
7
|
|
19
|
-
@
|
20
|
-
|
21
|
-
def self.ready
|
22
|
-
ready = []
|
23
|
-
@all.each { |app, runner| ready << app if runner.ready? }
|
24
|
-
ready
|
25
|
-
end
|
8
|
+
@runners = {}
|
26
9
|
|
27
|
-
def self.
|
28
|
-
if app = @
|
29
|
-
app.
|
10
|
+
def self.enqueue(notification)
|
11
|
+
if app = @runners[notification.app_id]
|
12
|
+
app.enqueue(notification)
|
30
13
|
else
|
31
|
-
Rapns::Daemon.logger.error("No such app '#{notification.
|
14
|
+
Rapns::Daemon.logger.error("No such app '#{notification.app_id}' for notification #{notification.id}.")
|
32
15
|
end
|
33
16
|
end
|
34
17
|
|
35
18
|
def self.sync
|
36
19
|
apps = Rapns::App.all
|
37
|
-
apps.each
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
20
|
+
apps.each { |app| sync_app(app) }
|
21
|
+
removed = runners.keys - apps.map(&:id)
|
22
|
+
removed.each { |app_id| runners.delete(app_id).stop }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.sync_app(app)
|
26
|
+
if runners[app.id]
|
27
|
+
runners[app.id].sync(app)
|
28
|
+
else
|
29
|
+
runner = new_runner(app)
|
30
|
+
begin
|
31
|
+
runner.start
|
32
|
+
runners[app.id] = runner
|
33
|
+
rescue StandardError => e
|
34
|
+
Rapns::Daemon.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
|
35
|
+
Rapns::Daemon.logger.error(e)
|
51
36
|
end
|
52
37
|
end
|
38
|
+
end
|
53
39
|
|
54
|
-
|
55
|
-
|
40
|
+
def self.new_runner(app)
|
41
|
+
type = app.class.parent.name.demodulize
|
42
|
+
"Rapns::Daemon::#{type}::AppRunner".constantize.new(app)
|
56
43
|
end
|
57
44
|
|
58
45
|
def self.stop
|
59
|
-
@
|
46
|
+
@runners.values.map(&:stop)
|
60
47
|
end
|
61
48
|
|
62
49
|
def self.debug
|
63
|
-
@
|
50
|
+
@runners.values.map(&:debug)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.idle
|
54
|
+
runners.values.select { |runner| runner.idle? }
|
64
55
|
end
|
65
56
|
|
66
|
-
|
57
|
+
attr_reader :app
|
58
|
+
|
59
|
+
def initialize(app)
|
67
60
|
@app = app
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_delivery_handler
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
73
66
|
|
74
|
-
|
75
|
-
@feedback_receiver = nil
|
76
|
-
@handlers = []
|
67
|
+
def started
|
77
68
|
end
|
78
69
|
|
70
|
+
def stopped
|
71
|
+
end
|
79
72
|
|
80
73
|
def start
|
81
|
-
|
82
|
-
|
83
|
-
@feedback_receiver.start
|
84
|
-
|
85
|
-
@app.connections.times { @handlers << start_handler }
|
86
|
-
rescue OpenSSL::SSL::SSLError
|
87
|
-
# these errors mean that a connection is not possible, raise back to caller
|
88
|
-
raise
|
89
|
-
rescue StandardError => e
|
90
|
-
Rapns::Daemon.logger.error(e)
|
91
|
-
end
|
74
|
+
app.connections.times { handlers << start_handler }
|
75
|
+
started
|
92
76
|
end
|
93
77
|
|
94
|
-
def
|
95
|
-
|
78
|
+
def stop
|
79
|
+
handlers.map(&:stop)
|
80
|
+
stopped
|
96
81
|
end
|
97
82
|
|
98
|
-
def
|
99
|
-
|
100
|
-
@feedback_receiver.stop if @feedback_receiver
|
83
|
+
def enqueue(notification)
|
84
|
+
queue.push(notification)
|
101
85
|
end
|
102
86
|
|
103
87
|
def sync(app)
|
104
88
|
@app = app
|
105
|
-
diff =
|
89
|
+
diff = handlers.size - app.connections
|
106
90
|
if diff > 0
|
107
|
-
diff.times {
|
91
|
+
diff.times { handlers.pop.stop }
|
108
92
|
else
|
109
|
-
diff.abs.times {
|
93
|
+
diff.abs.times { handlers << start_handler }
|
110
94
|
end
|
111
95
|
end
|
112
96
|
|
113
|
-
def
|
114
|
-
|
97
|
+
def debug
|
98
|
+
Rapns::Daemon.logger.info <<-EOS
|
99
|
+
|
100
|
+
#{@app.name}:
|
101
|
+
handlers: #{handlers.size}
|
102
|
+
queued: #{queue.size}
|
103
|
+
idle: #{idle?}
|
104
|
+
EOS
|
115
105
|
end
|
116
106
|
|
117
|
-
def
|
118
|
-
|
107
|
+
def idle?
|
108
|
+
queue.notifications_processed?
|
119
109
|
end
|
120
110
|
|
121
111
|
protected
|
122
112
|
|
123
113
|
def start_handler
|
124
|
-
handler =
|
114
|
+
handler = new_delivery_handler
|
115
|
+
handler.queue = queue
|
125
116
|
handler.start
|
126
117
|
handler
|
127
118
|
end
|
119
|
+
|
120
|
+
def queue
|
121
|
+
@queue ||= Rapns::Daemon::DeliveryQueue.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def handlers
|
125
|
+
@handler ||= []
|
126
|
+
end
|
128
127
|
end
|
129
|
-
|
130
|
-
end
|
128
|
+
end
|
129
|
+
end
|
@@ -23,11 +23,11 @@ module Rapns
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def database_connection_lost
|
26
|
-
Rapns::Daemon.logger.warn("
|
26
|
+
Rapns::Daemon.logger.warn("Lost connection to database, reconnecting...")
|
27
27
|
attempts = 0
|
28
28
|
loop do
|
29
29
|
begin
|
30
|
-
Rapns::Daemon.logger.warn("
|
30
|
+
Rapns::Daemon.logger.warn("Attempt #{attempts += 1}")
|
31
31
|
reconnect_database
|
32
32
|
check_database_is_connected
|
33
33
|
break
|
@@ -36,7 +36,7 @@ module Rapns
|
|
36
36
|
sleep_to_avoid_thrashing
|
37
37
|
end
|
38
38
|
end
|
39
|
-
Rapns::Daemon.logger.warn("
|
39
|
+
Rapns::Daemon.logger.warn("Database reconnected")
|
40
40
|
end
|
41
41
|
|
42
42
|
def reconnect_database
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rapns
|
2
|
+
module Daemon
|
3
|
+
class Delivery
|
4
|
+
include DatabaseReconnectable
|
5
|
+
|
6
|
+
def self.perform(*args)
|
7
|
+
new(*args).perform
|
8
|
+
end
|
9
|
+
|
10
|
+
def retry_after(notification, deliver_after)
|
11
|
+
with_database_reconnect_and_retry do
|
12
|
+
notification.retries += 1
|
13
|
+
notification.deliver_after = deliver_after
|
14
|
+
notification.save!(:validate => false)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def retry_exponentially(notification)
|
19
|
+
retry_after(notification, Time.now + 2 ** (notification.retries + 1))
|
20
|
+
end
|
21
|
+
|
22
|
+
def mark_delivered
|
23
|
+
with_database_reconnect_and_retry do
|
24
|
+
@notification.delivered = true
|
25
|
+
@notification.delivered_at = Time.now
|
26
|
+
@notification.save!(:validate => false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def mark_failed(code, description)
|
31
|
+
with_database_reconnect_and_retry do
|
32
|
+
@notification.delivered = false
|
33
|
+
@notification.delivered_at = nil
|
34
|
+
@notification.failed = true
|
35
|
+
@notification.failed_at = Time.now
|
36
|
+
@notification.error_code = code
|
37
|
+
@notification.error_description = description
|
38
|
+
@notification.save!(:validate => false)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -8,8 +8,12 @@ module Rapns
|
|
8
8
|
@description = description
|
9
9
|
end
|
10
10
|
|
11
|
+
def to_s
|
12
|
+
message
|
13
|
+
end
|
14
|
+
|
11
15
|
def message
|
12
|
-
"Unable to deliver notification #{@notification_id}, received
|
16
|
+
"Unable to deliver notification #{@notification_id}, received error #{@code} (#{@description})"
|
13
17
|
end
|
14
18
|
end
|
15
|
-
end
|
19
|
+
end
|
@@ -1,105 +1,39 @@
|
|
1
1
|
module Rapns
|
2
2
|
module Daemon
|
3
3
|
class DeliveryHandler
|
4
|
-
|
4
|
+
attr_accessor :queue
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
APN_ERRORS = {
|
9
|
-
1 => "Processing error",
|
10
|
-
2 => "Missing device token",
|
11
|
-
3 => "Missing topic",
|
12
|
-
4 => "Missing payload",
|
13
|
-
5 => "Missing token size",
|
14
|
-
6 => "Missing topic size",
|
15
|
-
7 => "Missing payload size",
|
16
|
-
8 => "Invalid token",
|
17
|
-
255 => "None (unknown error)"
|
18
|
-
}
|
19
|
-
|
20
|
-
attr_reader :name
|
21
|
-
|
22
|
-
def initialize(queue, name, host, port, certificate, password)
|
23
|
-
@queue = queue
|
24
|
-
@name = "DeliveryHandler:#{name}"
|
25
|
-
@connection = Connection.new(@name, host, port, certificate, password)
|
6
|
+
def deliver(notification)
|
7
|
+
raise NotImplementedError
|
26
8
|
end
|
27
9
|
|
28
10
|
def start
|
29
|
-
@connection.connect
|
30
|
-
|
31
11
|
@thread = Thread.new do
|
32
12
|
loop do
|
33
|
-
break if @stop
|
34
13
|
handle_next_notification
|
14
|
+
break if @stop
|
35
15
|
end
|
36
16
|
end
|
37
17
|
end
|
38
18
|
|
39
19
|
def stop
|
40
20
|
@stop = true
|
41
|
-
@
|
42
|
-
|
43
|
-
|
44
|
-
protected
|
45
|
-
|
46
|
-
def deliver(notification)
|
47
|
-
begin
|
48
|
-
@connection.write(notification.to_binary)
|
49
|
-
check_for_error if Rapns::Daemon.config.check_for_errors
|
50
|
-
|
51
|
-
with_database_reconnect_and_retry do
|
52
|
-
notification.delivered = true
|
53
|
-
notification.delivered_at = Time.now
|
54
|
-
notification.save!(:validate => false)
|
55
|
-
end
|
56
|
-
|
57
|
-
Rapns::Daemon.logger.info("[#{@name}] #{notification.id} sent to #{notification.device_token}")
|
58
|
-
rescue Rapns::DeliveryError, Rapns::DisconnectionError => error
|
59
|
-
handle_delivery_error(notification, error)
|
60
|
-
raise
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def handle_delivery_error(notification, error)
|
65
|
-
with_database_reconnect_and_retry do
|
66
|
-
notification.delivered = false
|
67
|
-
notification.delivered_at = nil
|
68
|
-
notification.failed = true
|
69
|
-
notification.failed_at = Time.now
|
70
|
-
notification.error_code = error.code
|
71
|
-
notification.error_description = error.description
|
72
|
-
notification.save!(:validate => false)
|
21
|
+
if @thread
|
22
|
+
queue.wakeup(@thread)
|
23
|
+
@thread.join
|
73
24
|
end
|
25
|
+
stopped
|
74
26
|
end
|
75
27
|
|
76
|
-
|
77
|
-
if @connection.select(SELECT_TIMEOUT)
|
78
|
-
error = nil
|
79
|
-
|
80
|
-
if tuple = @connection.read(ERROR_TUPLE_BYTES)
|
81
|
-
cmd, code, notification_id = tuple.unpack("ccN")
|
82
|
-
|
83
|
-
description = APN_ERRORS[code.to_i] || "Unknown error. Possible rapns bug?"
|
84
|
-
error = Rapns::DeliveryError.new(code, notification_id, description)
|
85
|
-
else
|
86
|
-
error = Rapns::DisconnectionError.new
|
87
|
-
end
|
28
|
+
protected
|
88
29
|
|
89
|
-
|
90
|
-
Rapns::Daemon.logger.error("[#{@name}] Error received, reconnecting...")
|
91
|
-
@connection.reconnect
|
92
|
-
ensure
|
93
|
-
raise error if error
|
94
|
-
end
|
95
|
-
end
|
30
|
+
def stopped
|
96
31
|
end
|
97
32
|
|
98
33
|
def handle_next_notification
|
99
34
|
begin
|
100
|
-
notification =
|
35
|
+
notification = queue.pop
|
101
36
|
rescue DeliveryQueue::WakeupError
|
102
|
-
@connection.close
|
103
37
|
return
|
104
38
|
end
|
105
39
|
|
@@ -108,9 +42,9 @@ module Rapns
|
|
108
42
|
rescue StandardError => e
|
109
43
|
Rapns::Daemon.logger.error(e)
|
110
44
|
ensure
|
111
|
-
|
45
|
+
queue.notification_processed
|
112
46
|
end
|
113
47
|
end
|
114
48
|
end
|
115
49
|
end
|
116
|
-
end
|
50
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Rapns
|
2
2
|
module Daemon
|
3
|
-
|
3
|
+
class DeliveryQueue19
|
4
4
|
def initialize
|
5
5
|
@mutex = Mutex.new
|
6
6
|
end
|
@@ -35,8 +35,8 @@ module Rapns
|
|
35
35
|
protected
|
36
36
|
|
37
37
|
def synchronize(&blk)
|
38
|
-
|
38
|
+
@mutex.synchronize(&blk)
|
39
39
|
end
|
40
|
-
|
40
|
+
end
|
41
41
|
end
|
42
42
|
end
|
data/lib/rapns/daemon/feeder.rb
CHANGED
@@ -10,9 +10,9 @@ module Rapns
|
|
10
10
|
|
11
11
|
def self.start(poll)
|
12
12
|
loop do
|
13
|
-
break if @stop
|
14
13
|
enqueue_notifications
|
15
14
|
interruptible_sleep poll
|
15
|
+
break if @stop
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -26,10 +26,10 @@ module Rapns
|
|
26
26
|
def self.enqueue_notifications
|
27
27
|
begin
|
28
28
|
with_database_reconnect_and_retry do
|
29
|
-
ready_apps = Rapns::Daemon::AppRunner.ready
|
30
29
|
batch_size = Rapns::Daemon.config.batch_size
|
31
|
-
Rapns::
|
32
|
-
|
30
|
+
idle = Rapns::Daemon::AppRunner.idle.map(&:app)
|
31
|
+
Rapns::Notification.ready_for_delivery.for_apps(idle).limit(batch_size).each do |notification|
|
32
|
+
Rapns::Daemon::AppRunner.enqueue(notification)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
rescue StandardError => e
|
@@ -38,4 +38,4 @@ module Rapns
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
-
end
|
41
|
+
end
|