rapns 2.0.5 → 3.0.0.beta.1
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/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
|