rpush 1.0.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.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +99 -0
  3. data/LICENSE +7 -0
  4. data/README.md +189 -0
  5. data/bin/rpush +36 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rpush_generator.rb +44 -0
  8. data/lib/generators/templates/add_adm.rb +23 -0
  9. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  10. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  11. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
  12. data/lib/generators/templates/add_gcm.rb +102 -0
  13. data/lib/generators/templates/add_rpush.rb +349 -0
  14. data/lib/generators/templates/add_wpns.rb +16 -0
  15. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  16. data/lib/generators/templates/create_rapns_feedback.rb +18 -0
  17. data/lib/generators/templates/create_rapns_notifications.rb +29 -0
  18. data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
  19. data/lib/generators/templates/rpush.rb +104 -0
  20. data/lib/rpush.rb +62 -0
  21. data/lib/rpush/TODO +3 -0
  22. data/lib/rpush/adm/app.rb +15 -0
  23. data/lib/rpush/adm/data_validator.rb +11 -0
  24. data/lib/rpush/adm/notification.rb +29 -0
  25. data/lib/rpush/apns/app.rb +29 -0
  26. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  27. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  28. data/lib/rpush/apns/feedback.rb +16 -0
  29. data/lib/rpush/apns/notification.rb +84 -0
  30. data/lib/rpush/apns_feedback.rb +13 -0
  31. data/lib/rpush/app.rb +18 -0
  32. data/lib/rpush/configuration.rb +75 -0
  33. data/lib/rpush/daemon.rb +140 -0
  34. data/lib/rpush/daemon/adm.rb +9 -0
  35. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  36. data/lib/rpush/daemon/apns.rb +16 -0
  37. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  38. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  39. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  40. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  41. data/lib/rpush/daemon/app_runner.rb +187 -0
  42. data/lib/rpush/daemon/batch.rb +115 -0
  43. data/lib/rpush/daemon/constants.rb +59 -0
  44. data/lib/rpush/daemon/delivery.rb +28 -0
  45. data/lib/rpush/daemon/delivery_error.rb +19 -0
  46. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  47. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  48. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  49. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  50. data/lib/rpush/daemon/feeder.rb +68 -0
  51. data/lib/rpush/daemon/gcm.rb +9 -0
  52. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  53. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  54. data/lib/rpush/daemon/loggable.rb +31 -0
  55. data/lib/rpush/daemon/reflectable.rb +13 -0
  56. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  57. data/lib/rpush/daemon/retryable_error.rb +20 -0
  58. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  59. data/lib/rpush/daemon/store/active_record.rb +154 -0
  60. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  61. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  62. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  63. data/lib/rpush/daemon/wpns.rb +9 -0
  64. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  65. data/lib/rpush/deprecatable.rb +23 -0
  66. data/lib/rpush/deprecation.rb +23 -0
  67. data/lib/rpush/embed.rb +28 -0
  68. data/lib/rpush/gcm/app.rb +11 -0
  69. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  70. data/lib/rpush/gcm/notification.rb +30 -0
  71. data/lib/rpush/logger.rb +63 -0
  72. data/lib/rpush/multi_json_helper.rb +16 -0
  73. data/lib/rpush/notification.rb +69 -0
  74. data/lib/rpush/notifier.rb +52 -0
  75. data/lib/rpush/payload_data_size_validator.rb +10 -0
  76. data/lib/rpush/push.rb +16 -0
  77. data/lib/rpush/railtie.rb +11 -0
  78. data/lib/rpush/reflection.rb +58 -0
  79. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  80. data/lib/rpush/version.rb +3 -0
  81. data/lib/rpush/wpns/app.rb +9 -0
  82. data/lib/rpush/wpns/notification.rb +26 -0
  83. data/lib/tasks/cane.rake +18 -0
  84. data/lib/tasks/rpush.rake +16 -0
  85. data/lib/tasks/test.rake +38 -0
  86. data/spec/functional/adm_spec.rb +43 -0
  87. data/spec/functional/apns_spec.rb +58 -0
  88. data/spec/functional/embed_spec.rb +49 -0
  89. data/spec/functional/gcm_spec.rb +42 -0
  90. data/spec/functional/wpns_spec.rb +41 -0
  91. data/spec/support/cert_with_password.pem +90 -0
  92. data/spec/support/cert_without_password.pem +59 -0
  93. data/spec/support/install.sh +32 -0
  94. data/spec/support/simplecov_helper.rb +20 -0
  95. data/spec/support/simplecov_quality_formatter.rb +8 -0
  96. data/spec/tmp/.gitkeep +0 -0
  97. data/spec/unit/adm/app_spec.rb +58 -0
  98. data/spec/unit/adm/notification_spec.rb +45 -0
  99. data/spec/unit/apns/app_spec.rb +29 -0
  100. data/spec/unit/apns/feedback_spec.rb +9 -0
  101. data/spec/unit/apns/notification_spec.rb +208 -0
  102. data/spec/unit/apns_feedback_spec.rb +21 -0
  103. data/spec/unit/app_spec.rb +30 -0
  104. data/spec/unit/configuration_spec.rb +45 -0
  105. data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
  106. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  107. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  108. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  109. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
  110. data/spec/unit/daemon/app_runner_spec.rb +292 -0
  111. data/spec/unit/daemon/batch_spec.rb +232 -0
  112. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  113. data/spec/unit/daemon/delivery_spec.rb +38 -0
  114. data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
  115. data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
  116. data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
  117. data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
  118. data/spec/unit/daemon/feeder_spec.rb +98 -0
  119. data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
  120. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  121. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  122. data/spec/unit/daemon/retryable_error_spec.rb +14 -0
  123. data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
  124. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  125. data/spec/unit/daemon/store/active_record_spec.rb +357 -0
  126. data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
  127. data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
  128. data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
  129. data/spec/unit/daemon_spec.rb +159 -0
  130. data/spec/unit/deprecatable_spec.rb +32 -0
  131. data/spec/unit/deprecation_spec.rb +15 -0
  132. data/spec/unit/embed_spec.rb +50 -0
  133. data/spec/unit/gcm/app_spec.rb +4 -0
  134. data/spec/unit/gcm/notification_spec.rb +36 -0
  135. data/spec/unit/logger_spec.rb +127 -0
  136. data/spec/unit/notification_shared.rb +105 -0
  137. data/spec/unit/notification_spec.rb +15 -0
  138. data/spec/unit/notifier_spec.rb +49 -0
  139. data/spec/unit/push_spec.rb +43 -0
  140. data/spec/unit/reflection_spec.rb +30 -0
  141. data/spec/unit/rpush_spec.rb +9 -0
  142. data/spec/unit/wpns/app_spec.rb +4 -0
  143. data/spec/unit/wpns/notification_spec.rb +30 -0
  144. data/spec/unit_spec_helper.rb +101 -0
  145. metadata +276 -0
@@ -0,0 +1,61 @@
1
+ module Rpush
2
+ module Daemon
3
+ class InterruptibleSleep
4
+
5
+ def initialize
6
+ @sleep_reader, @wake_writer = IO.pipe
7
+ @udp_wakeup = nil
8
+ end
9
+
10
+ # enable wake on receiving udp packets at the given address and port
11
+ # this returns the host,port used by bind in case an ephemeral port
12
+ # was indicated by specifying 0 as the port number.
13
+ # @return [String,Integer] host,port of bound UDP socket.
14
+ def enable_wake_on_udp(host, port)
15
+ @udp_wakeup = UDPSocket.new
16
+ @udp_wakeup.bind(host, port)
17
+ @udp_wakeup.addr.values_at(3,1)
18
+ end
19
+
20
+ # wait for the given timeout in seconds, or data was written to the pipe
21
+ # or the udp wakeup port if enabled.
22
+ # @return [boolean] true if the sleep was interrupted, or false
23
+ def sleep(timeout)
24
+ read_ports = [@sleep_reader]
25
+ read_ports << @udp_wakeup if @udp_wakeup
26
+ rs, = IO.select(read_ports, nil, nil, timeout) rescue nil
27
+
28
+ # consume all data on the readable io's so that our next call will wait for more data
29
+ perform_io(rs, @sleep_reader, :read_nonblock)
30
+ perform_io(rs, @udp_wakeup, :recv_nonblock)
31
+
32
+ !rs.nil? && rs.any?
33
+ end
34
+
35
+ # writing to the pipe will wake the sleeping thread
36
+ def interrupt_sleep
37
+ @wake_writer.write('.')
38
+ end
39
+
40
+ def close
41
+ @sleep_reader.close rescue nil
42
+ @wake_writer.close rescue nil
43
+ @udp_wakeup.close if @udp_wakeup rescue nil
44
+ end
45
+
46
+ private
47
+
48
+ def perform_io(selected, io, meth)
49
+ if selected && selected.include?(io)
50
+ while true
51
+ begin
52
+ io.__send__(meth, 1)
53
+ rescue Errno::EAGAIN, IO::WaitReadable
54
+ break
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,31 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Loggable
4
+ def log_info(msg)
5
+ Rpush.logger.info(app_prefix(msg))
6
+ end
7
+
8
+ def log_warn(msg)
9
+ Rpush.logger.warn(app_prefix(msg))
10
+ end
11
+
12
+ def log_error(e)
13
+ if e.is_a?(Exception)
14
+ Rpush.logger.error(e)
15
+ else
16
+ Rpush.logger.error(app_prefix(e))
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def app_prefix(msg)
23
+ if app = instance_variable_get('@app')
24
+ msg = "[#{app.name}] #{msg}"
25
+ end
26
+
27
+ msg
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Reflectable
4
+ def reflect(name, *args)
5
+ begin
6
+ Rpush.reflections.__dispatch(name, *args)
7
+ rescue StandardError => e
8
+ Rpush.logger.error(e)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Rpush
2
+ module Daemon
3
+ class RetryHeaderParser
4
+ def self.parse(header)
5
+ new(header).parse
6
+ end
7
+
8
+ def initialize(header)
9
+ @header = header
10
+ end
11
+
12
+ def parse
13
+ if @header
14
+ if @header.to_s =~ /^[0-9]+$/
15
+ Time.now + @header.to_i
16
+ else
17
+ Time.httpdate(@header)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ class RetryableError < StandardError
3
+ attr_reader :code, :description, :response
4
+
5
+ def initialize(code, notification_id, description, response)
6
+ @code = code
7
+ @notification_id = notification_id
8
+ @description = description
9
+ @response = response
10
+ end
11
+
12
+ def to_s
13
+ message
14
+ end
15
+
16
+ def message
17
+ "Retryable error for #{@notification_id}, received error #{@code} (#{@description}) - retry after #{@response.header['retry-after']}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module Rpush
2
+ module Daemon
3
+ module ServiceConfigMethods
4
+ DISPATCHERS = {
5
+ :http => Rpush::Daemon::Dispatcher::Http,
6
+ :tcp => Rpush::Daemon::Dispatcher::Tcp
7
+ }
8
+
9
+ def dispatcher(name = nil, options = {})
10
+ @dispatcher_name = name
11
+ @dispatcher_options = options
12
+ end
13
+
14
+ def dispatcher_class
15
+ DISPATCHERS[@dispatcher_name] || (raise NotImplementedError)
16
+ end
17
+
18
+ def delivery_class
19
+ const_get('Delivery')
20
+ end
21
+
22
+ def new_dispatcher(app)
23
+ dispatcher_class.new(app, delivery_class, @dispatcher_options)
24
+ end
25
+
26
+ def loops(*loops)
27
+ @loops ||= []
28
+ @loops = loops if loops.any?
29
+ @loops
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,154 @@
1
+ require 'active_record'
2
+
3
+ require 'rpush/daemon/store/active_record/reconnectable'
4
+
5
+ module Rpush
6
+ module Daemon
7
+ module Store
8
+ class ActiveRecord
9
+ include Reconnectable
10
+
11
+ DEFAULT_MARK_OPTIONS = {:persist => true}
12
+
13
+ def deliverable_notifications(apps)
14
+ with_database_reconnect_and_retry do
15
+ batch_size = Rpush.config.batch_size
16
+ relation = Rpush::Notification.ready_for_delivery.for_apps(apps)
17
+ relation = relation.limit(batch_size) unless Rpush.config.push
18
+ relation.to_a
19
+ end
20
+ end
21
+
22
+ def mark_retryable(notification, deliver_after, opts = {})
23
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
24
+ notification.retries += 1
25
+ notification.deliver_after = deliver_after
26
+
27
+ if opts[:persist]
28
+ with_database_reconnect_and_retry do
29
+ notification.save!(:validate => false)
30
+ end
31
+ end
32
+ end
33
+
34
+ def mark_batch_retryable(notifications, deliver_after)
35
+ ids = []
36
+ notifications.each do |n|
37
+ mark_retryable(n, deliver_after, :persist => false)
38
+ ids << n.id
39
+ end
40
+ with_database_reconnect_and_retry do
41
+ Rpush::Notification.where(:id => ids).update_all(['retries = retries + 1, deliver_after = ?', deliver_after])
42
+ end
43
+ end
44
+
45
+ def mark_delivered(notification, time, opts = {})
46
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
47
+ notification.delivered = true
48
+ notification.delivered_at = time
49
+
50
+ if opts[:persist]
51
+ with_database_reconnect_and_retry do
52
+ notification.save!(:validate => false)
53
+ end
54
+ end
55
+ end
56
+
57
+ def mark_batch_delivered(notifications)
58
+ now = Time.now
59
+ ids = []
60
+ notifications.each do |n|
61
+ mark_delivered(n, now, :persist => false)
62
+ ids << n.id
63
+ end
64
+ with_database_reconnect_and_retry do
65
+ Rpush::Notification.where(:id => ids).update_all(['delivered = ?, delivered_at = ?', true, now])
66
+ end
67
+ end
68
+
69
+ def mark_failed(notification, code, description, time, opts = {})
70
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
71
+ notification.delivered = false
72
+ notification.delivered_at = nil
73
+ notification.failed = true
74
+ notification.failed_at = time
75
+ notification.error_code = code
76
+ notification.error_description = description
77
+
78
+ if opts[:persist]
79
+ with_database_reconnect_and_retry do
80
+ notification.save!(:validate => false)
81
+ end
82
+ end
83
+ end
84
+
85
+ def mark_batch_failed(notifications, code, description)
86
+ now = Time.now
87
+ ids = []
88
+ notifications.each do |n|
89
+ mark_failed(n, code, description, now, :persist => false)
90
+ ids << n.id
91
+ end
92
+ with_database_reconnect_and_retry do
93
+ Rpush::Notification.where(:id => ids).update_all(['delivered = ?, delivered_at = NULL, failed = ?, failed_at = ?, error_code = ?, error_description = ?', false, true, now, code, description])
94
+ end
95
+ end
96
+
97
+ def create_apns_feedback(failed_at, device_token, app)
98
+ with_database_reconnect_and_retry do
99
+ Rpush::Apns::Feedback.create!(:failed_at => failed_at,
100
+ :device_token => device_token, :app => app)
101
+ end
102
+ end
103
+
104
+ def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
105
+ notification = Rpush::Gcm::Notification.new
106
+ create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
107
+ end
108
+
109
+ def create_adm_notification(attrs, data, registration_ids, deliver_after, app)
110
+ notification = Rpush::Adm::Notification.new
111
+ create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
112
+ end
113
+
114
+ def update_app(app)
115
+ with_database_reconnect_and_retry do
116
+ app.save!
117
+ end
118
+ end
119
+
120
+ def update_notification(notification)
121
+ with_database_reconnect_and_retry do
122
+ notification.save!
123
+ end
124
+ end
125
+
126
+ def after_daemonize
127
+ reconnect_database
128
+ end
129
+
130
+ def release_connection
131
+ begin
132
+ ::ActiveRecord::Base.connection_pool.release_connection
133
+ rescue StandardError => e
134
+ Rpush.logger.error(e)
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
141
+ with_database_reconnect_and_retry do
142
+ notification.assign_attributes(attrs)
143
+ notification.data = data
144
+ notification.registration_ids = registration_ids
145
+ notification.deliver_after = deliver_after
146
+ notification.app = app
147
+ notification.save!
148
+ notification
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,68 @@
1
+ class PGError < StandardError; end if !defined?(PGError)
2
+ class Mysql; class Error < StandardError; end; end if !defined?(Mysql)
3
+ module Mysql2; class Error < StandardError; end; end if !defined?(Mysql2)
4
+ module ActiveRecord; end
5
+ class ActiveRecord::JDBCError < StandardError; end if !defined?(::ActiveRecord::JDBCError)
6
+
7
+ # :nocov:
8
+ if !defined?(::SQLite3::Exception)
9
+ module SQLite3
10
+ class Exception < StandardError; end
11
+ end
12
+ end
13
+
14
+ module Rpush
15
+ module Daemon
16
+ module Store
17
+ class ActiveRecord
18
+ module Reconnectable
19
+ ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error,
20
+ Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
21
+
22
+ def with_database_reconnect_and_retry
23
+ begin
24
+ ::ActiveRecord::Base.connection_pool.with_connection do
25
+ yield
26
+ end
27
+ rescue *ADAPTER_ERRORS => e
28
+ Rpush.logger.error(e)
29
+ database_connection_lost
30
+ retry
31
+ end
32
+ end
33
+
34
+ def database_connection_lost
35
+ Rpush.logger.warn("Lost connection to database, reconnecting...")
36
+ attempts = 0
37
+ loop do
38
+ begin
39
+ Rpush.logger.warn("Attempt #{attempts += 1}")
40
+ reconnect_database
41
+ check_database_is_connected
42
+ break
43
+ rescue *ADAPTER_ERRORS => e
44
+ Rpush.logger.error(e)
45
+ sleep_to_avoid_thrashing
46
+ end
47
+ end
48
+ Rpush.logger.warn("Database reconnected")
49
+ end
50
+
51
+ def reconnect_database
52
+ ::ActiveRecord::Base.clear_all_connections!
53
+ ::ActiveRecord::Base.establish_connection
54
+ end
55
+
56
+ def check_database_is_connected
57
+ # Simply asking the adapter for the connection state is not sufficient.
58
+ Rpush::Notification.count
59
+ end
60
+
61
+ def sleep_to_avoid_thrashing
62
+ sleep 2
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,143 @@
1
+ module Rpush
2
+ module Daemon
3
+ class TcpConnectionError < StandardError; end
4
+
5
+ class TcpConnection
6
+ include Reflectable
7
+ include Loggable
8
+
9
+ attr_accessor :last_write
10
+
11
+ def self.idle_period
12
+ 30.minutes
13
+ end
14
+
15
+ def initialize(app, host, port)
16
+ @app = app
17
+ @host = host
18
+ @port = port
19
+ @certificate = app.certificate
20
+ @password = app.password
21
+ written
22
+ end
23
+
24
+ def connect
25
+ @ssl_context = setup_ssl_context
26
+ @tcp_socket, @ssl_socket = connect_socket
27
+ end
28
+
29
+ def close
30
+ begin
31
+ @ssl_socket.close if @ssl_socket
32
+ @tcp_socket.close if @tcp_socket
33
+ rescue IOError
34
+ end
35
+ end
36
+
37
+ def read(num_bytes)
38
+ @ssl_socket.read(num_bytes)
39
+ end
40
+
41
+ def select(timeout)
42
+ IO.select([@ssl_socket], nil, nil, timeout)
43
+ end
44
+
45
+ def write(data)
46
+ reconnect_idle if idle_period_exceeded?
47
+
48
+ retry_count = 0
49
+
50
+ begin
51
+ write_data(data)
52
+ rescue Errno::EPIPE, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, IOError => e
53
+ retry_count += 1;
54
+
55
+ if retry_count == 1
56
+ log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}), reconnecting...")
57
+ reflect(:apns_connection_lost, @app, e) # deprecated
58
+ reflect(:tcp_connection_lost, @app, e)
59
+ end
60
+
61
+ if retry_count <= 3
62
+ reconnect
63
+ sleep 1
64
+ retry
65
+ else
66
+ raise TcpConnectionError, "#{@app.name} tried #{retry_count-1} times to reconnect but failed (#{e.class.name})."
67
+ end
68
+ end
69
+ end
70
+
71
+ def reconnect
72
+ close
73
+ @tcp_socket, @ssl_socket = connect_socket
74
+ end
75
+
76
+ protected
77
+
78
+ def reconnect_idle
79
+ log_info("Idle period exceeded, reconnecting...")
80
+ reconnect
81
+ end
82
+
83
+ def idle_period_exceeded?
84
+ Time.now - last_write > self.class.idle_period
85
+ end
86
+
87
+ def write_data(data)
88
+ @ssl_socket.write(data)
89
+ @ssl_socket.flush
90
+ written
91
+ end
92
+
93
+ def written
94
+ self.last_write = Time.now
95
+ end
96
+
97
+ def setup_ssl_context
98
+ ssl_context = OpenSSL::SSL::SSLContext.new
99
+ ssl_context.key = OpenSSL::PKey::RSA.new(@certificate, @password)
100
+ ssl_context.cert = OpenSSL::X509::Certificate.new(@certificate)
101
+ ssl_context
102
+ end
103
+
104
+ def connect_socket
105
+ check_certificate_expiration
106
+
107
+ tcp_socket = TCPSocket.new(@host, @port)
108
+ tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
109
+ tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
110
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
111
+ ssl_socket.sync = true
112
+ ssl_socket.connect
113
+ log_info("Connected to #{@host}:#{@port}")
114
+ [tcp_socket, ssl_socket]
115
+ end
116
+
117
+ def check_certificate_expiration
118
+ cert = @ssl_context.cert
119
+ if certificate_expired?
120
+ log_error(certificate_msg('expired'))
121
+ raise Rpush::Apns::CertificateExpiredError.new(@app, cert.not_after)
122
+ elsif certificate_expires_soon?
123
+ log_warn(certificate_msg('will expire'))
124
+ reflect(:apns_certificate_will_expire, @app, cert.not_after) # deprecated
125
+ reflect(:ssl_certificate_will_expire, @app, cert.not_after)
126
+ end
127
+ end
128
+
129
+ def certificate_msg(msg)
130
+ time = @ssl_context.cert.not_after.utc.strftime("%Y-%m-%d %H:%M:%S UTC")
131
+ "Certificate #{msg} at #{time}."
132
+ end
133
+
134
+ def certificate_expired?
135
+ @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < Time.now.utc
136
+ end
137
+
138
+ def certificate_expires_soon?
139
+ @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < (Time.now + 1.month).utc
140
+ end
141
+ end
142
+ end
143
+ end