rpush 1.0.0-java → 2.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +37 -22
  4. data/bin/rpush +13 -4
  5. data/lib/generators/rpush_generator.rb +2 -0
  6. data/lib/generators/templates/add_adm.rb +5 -5
  7. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +1 -1
  8. data/lib/generators/templates/add_app_to_rapns.rb +2 -2
  9. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +1 -1
  10. data/lib/generators/templates/add_gcm.rb +32 -32
  11. data/lib/generators/templates/add_rpush.rb +67 -67
  12. data/lib/generators/templates/add_wpns.rb +2 -2
  13. data/lib/generators/templates/create_rapns_apps.rb +5 -5
  14. data/lib/generators/templates/create_rapns_feedback.rb +2 -2
  15. data/lib/generators/templates/create_rapns_notifications.rb +15 -15
  16. data/lib/generators/templates/rpush.rb +28 -7
  17. data/lib/generators/templates/rpush_2_0_0_updates.rb +42 -0
  18. data/lib/rpush/client/active_model/adm/app.rb +23 -0
  19. data/lib/rpush/client/active_model/adm/data_validator.rb +14 -0
  20. data/lib/rpush/client/active_model/adm/notification.rb +28 -0
  21. data/lib/rpush/client/active_model/apns/app.rb +37 -0
  22. data/lib/rpush/client/active_model/apns/binary_notification_validator.rb +16 -0
  23. data/lib/rpush/client/active_model/apns/device_token_format_validator.rb +14 -0
  24. data/lib/rpush/client/active_model/apns/notification.rb +90 -0
  25. data/lib/rpush/client/active_model/gcm/app.rb +19 -0
  26. data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +14 -0
  27. data/lib/rpush/client/active_model/gcm/notification.rb +31 -0
  28. data/lib/rpush/client/active_model/notification.rb +26 -0
  29. data/lib/rpush/client/active_model/payload_data_size_validator.rb +13 -0
  30. data/lib/rpush/client/active_model/registration_ids_count_validator.rb +13 -0
  31. data/lib/rpush/client/active_model/wpns/app.rb +13 -0
  32. data/lib/rpush/client/active_model/wpns/notification.rb +17 -0
  33. data/lib/rpush/client/active_model.rb +21 -0
  34. data/lib/rpush/client/active_record/adm/app.rb +11 -0
  35. data/lib/rpush/client/active_record/adm/notification.rb +11 -0
  36. data/lib/rpush/client/active_record/apns/app.rb +11 -0
  37. data/lib/rpush/client/active_record/apns/feedback.rb +22 -0
  38. data/lib/rpush/client/active_record/apns/notification.rb +46 -0
  39. data/lib/rpush/client/active_record/app.rb +17 -0
  40. data/lib/rpush/client/active_record/gcm/app.rb +11 -0
  41. data/lib/rpush/client/active_record/gcm/notification.rb +11 -0
  42. data/lib/rpush/client/active_record/notification.rb +38 -0
  43. data/lib/rpush/client/active_record/wpns/app.rb +11 -0
  44. data/lib/rpush/client/active_record/wpns/notification.rb +11 -0
  45. data/lib/rpush/client/active_record.rb +19 -0
  46. data/lib/rpush/client/redis/adm/app.rb +14 -0
  47. data/lib/rpush/client/redis/adm/notification.rb +11 -0
  48. data/lib/rpush/client/redis/apns/app.rb +11 -0
  49. data/lib/rpush/client/redis/apns/feedback.rb +20 -0
  50. data/lib/rpush/client/redis/apns/notification.rb +11 -0
  51. data/lib/rpush/client/redis/app.rb +24 -0
  52. data/lib/rpush/client/redis/gcm/app.rb +11 -0
  53. data/lib/rpush/client/redis/gcm/notification.rb +11 -0
  54. data/lib/rpush/client/redis/notification.rb +68 -0
  55. data/lib/rpush/client/redis/wpns/app.rb +11 -0
  56. data/lib/rpush/client/redis/wpns/notification.rb +11 -0
  57. data/lib/rpush/client/redis.rb +35 -0
  58. data/lib/rpush/configuration.rb +27 -6
  59. data/lib/rpush/daemon/adm/delivery.rb +56 -55
  60. data/lib/rpush/daemon/apns/delivery.rb +20 -44
  61. data/lib/rpush/daemon/apns/feedback_receiver.rb +11 -8
  62. data/lib/rpush/daemon/apns.rb +6 -5
  63. data/lib/rpush/daemon/app_runner.rb +103 -99
  64. data/lib/rpush/daemon/batch.rb +54 -40
  65. data/lib/rpush/daemon/delivery.rb +13 -3
  66. data/lib/rpush/daemon/delivery_error.rb +10 -2
  67. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +114 -0
  68. data/lib/rpush/daemon/dispatcher/http.rb +3 -3
  69. data/lib/rpush/daemon/dispatcher/tcp.rb +3 -3
  70. data/lib/rpush/daemon/dispatcher_loop.rb +37 -23
  71. data/lib/rpush/daemon/errors.rb +18 -0
  72. data/lib/rpush/daemon/feeder.rb +28 -39
  73. data/lib/rpush/daemon/gcm/delivery.rb +19 -20
  74. data/lib/rpush/daemon/interruptible_sleep.rb +26 -45
  75. data/lib/rpush/daemon/loggable.rb +2 -4
  76. data/lib/rpush/daemon/proc_title.rb +16 -0
  77. data/lib/rpush/daemon/queue_payload.rb +12 -0
  78. data/lib/rpush/daemon/reflectable.rb +3 -5
  79. data/lib/rpush/daemon/retry_header_parser.rb +6 -6
  80. data/lib/rpush/daemon/retryable_error.rb +2 -0
  81. data/lib/rpush/daemon/ring_buffer.rb +16 -0
  82. data/lib/rpush/daemon/service_config_methods.rb +23 -7
  83. data/lib/rpush/daemon/signal_handler.rb +56 -0
  84. data/lib/rpush/daemon/store/active_record/reconnectable.rb +21 -17
  85. data/lib/rpush/daemon/store/active_record.rb +71 -38
  86. data/lib/rpush/daemon/store/interface.rb +19 -0
  87. data/lib/rpush/daemon/store/redis.rb +149 -0
  88. data/lib/rpush/daemon/string_helpers.rb +15 -0
  89. data/lib/rpush/daemon/synchronizer.rb +60 -0
  90. data/lib/rpush/daemon/tcp_connection.rb +6 -11
  91. data/lib/rpush/daemon/wpns/delivery.rb +21 -30
  92. data/lib/rpush/daemon.rb +40 -60
  93. data/lib/rpush/deprecatable.rb +4 -3
  94. data/lib/rpush/deprecation.rb +7 -10
  95. data/lib/rpush/embed.rb +8 -3
  96. data/lib/rpush/logger.rb +11 -15
  97. data/lib/rpush/push.rb +1 -2
  98. data/lib/rpush/reflection.rb +8 -12
  99. data/lib/rpush/version.rb +1 -1
  100. data/lib/rpush.rb +5 -29
  101. data/lib/tasks/quality.rake +35 -0
  102. data/lib/tasks/test.rake +1 -5
  103. data/spec/.rubocop.yml +4 -0
  104. data/spec/functional/adm_spec.rb +3 -6
  105. data/spec/functional/apns_spec.rb +117 -24
  106. data/spec/functional/embed_spec.rb +20 -20
  107. data/spec/functional/gcm_spec.rb +4 -7
  108. data/spec/functional/new_app_spec.rb +59 -0
  109. data/spec/functional/retry_spec.rb +46 -0
  110. data/spec/functional/synchronization_spec.rb +68 -0
  111. data/spec/functional/wpns_spec.rb +3 -6
  112. data/spec/functional_spec_helper.rb +26 -0
  113. data/spec/integration/rpush_spec.rb +13 -0
  114. data/spec/integration/support/gcm_success_response.json +1 -0
  115. data/spec/spec_helper.rb +60 -0
  116. data/spec/support/active_record_setup.rb +48 -0
  117. data/{config → spec/support/config}/database.yml +0 -0
  118. data/spec/support/install.sh +43 -7
  119. data/spec/support/simplecov_helper.rb +9 -5
  120. data/spec/support/simplecov_quality_formatter.rb +10 -6
  121. data/spec/unit/apns_feedback_spec.rb +3 -3
  122. data/spec/unit/{adm → client/active_record/adm}/app_spec.rb +3 -3
  123. data/spec/unit/{adm → client/active_record/adm}/notification_spec.rb +5 -7
  124. data/spec/unit/client/active_record/apns/app_spec.rb +29 -0
  125. data/spec/unit/client/active_record/apns/feedback_spec.rb +9 -0
  126. data/spec/unit/client/active_record/apns/notification_spec.rb +231 -0
  127. data/spec/unit/client/active_record/app_spec.rb +30 -0
  128. data/spec/unit/client/active_record/gcm/app_spec.rb +4 -0
  129. data/spec/unit/{gcm → client/active_record/gcm}/notification_spec.rb +5 -7
  130. data/spec/unit/client/active_record/notification_spec.rb +21 -0
  131. data/spec/unit/client/active_record/wpns/app_spec.rb +4 -0
  132. data/spec/unit/client/active_record/wpns/notification_spec.rb +21 -0
  133. data/spec/unit/configuration_spec.rb +12 -5
  134. data/spec/unit/daemon/adm/delivery_spec.rb +66 -55
  135. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +3 -3
  136. data/spec/unit/daemon/apns/delivery_spec.rb +90 -83
  137. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +22 -17
  138. data/spec/unit/daemon/app_runner_spec.rb +78 -186
  139. data/spec/unit/daemon/batch_spec.rb +52 -115
  140. data/spec/unit/daemon/delivery_spec.rb +15 -1
  141. data/spec/unit/daemon/dispatcher/http_spec.rb +3 -2
  142. data/spec/unit/daemon/dispatcher/tcp_spec.rb +10 -9
  143. data/spec/unit/daemon/dispatcher_loop_spec.rb +6 -24
  144. data/spec/unit/daemon/feeder_spec.rb +38 -39
  145. data/spec/unit/daemon/gcm/delivery_spec.rb +122 -101
  146. data/spec/unit/daemon/reflectable_spec.rb +2 -2
  147. data/spec/unit/daemon/retryable_error_spec.rb +1 -1
  148. data/spec/unit/daemon/service_config_methods_spec.rb +6 -3
  149. data/spec/unit/daemon/signal_handler_spec.rb +95 -0
  150. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +48 -27
  151. data/spec/unit/daemon/store/active_record_spec.rb +38 -47
  152. data/spec/unit/daemon/tcp_connection_spec.rb +22 -34
  153. data/spec/unit/daemon/wpns/delivery_spec.rb +58 -50
  154. data/spec/unit/daemon_spec.rb +48 -82
  155. data/spec/unit/embed_spec.rb +6 -4
  156. data/spec/unit/logger_spec.rb +35 -43
  157. data/spec/unit/notification_shared.rb +9 -79
  158. data/spec/unit/push_spec.rb +6 -10
  159. data/spec/unit/reflection_spec.rb +25 -25
  160. data/spec/unit/rpush_spec.rb +1 -2
  161. data/spec/unit_spec_helper.rb +33 -88
  162. metadata +126 -76
  163. data/lib/rpush/TODO +0 -3
  164. data/lib/rpush/adm/app.rb +0 -15
  165. data/lib/rpush/adm/data_validator.rb +0 -11
  166. data/lib/rpush/adm/notification.rb +0 -29
  167. data/lib/rpush/apns/app.rb +0 -29
  168. data/lib/rpush/apns/binary_notification_validator.rb +0 -12
  169. data/lib/rpush/apns/device_token_format_validator.rb +0 -12
  170. data/lib/rpush/apns/feedback.rb +0 -16
  171. data/lib/rpush/apns/notification.rb +0 -84
  172. data/lib/rpush/app.rb +0 -18
  173. data/lib/rpush/daemon/apns/certificate_expired_error.rb +0 -20
  174. data/lib/rpush/daemon/apns/disconnection_error.rb +0 -20
  175. data/lib/rpush/daemon/dispatcher_loop_collection.rb +0 -33
  176. data/lib/rpush/daemon/too_many_requests_error.rb +0 -20
  177. data/lib/rpush/gcm/app.rb +0 -11
  178. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +0 -11
  179. data/lib/rpush/gcm/notification.rb +0 -30
  180. data/lib/rpush/notification.rb +0 -69
  181. data/lib/rpush/notifier.rb +0 -52
  182. data/lib/rpush/payload_data_size_validator.rb +0 -10
  183. data/lib/rpush/railtie.rb +0 -11
  184. data/lib/rpush/registration_ids_count_validator.rb +0 -10
  185. data/lib/rpush/wpns/app.rb +0 -9
  186. data/lib/rpush/wpns/notification.rb +0 -26
  187. data/lib/tasks/cane.rake +0 -18
  188. data/lib/tasks/rpush.rake +0 -16
  189. data/spec/unit/apns/app_spec.rb +0 -29
  190. data/spec/unit/apns/feedback_spec.rb +0 -9
  191. data/spec/unit/apns/notification_spec.rb +0 -208
  192. data/spec/unit/app_spec.rb +0 -30
  193. data/spec/unit/daemon/apns/disconnection_error_spec.rb +0 -18
  194. data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +0 -37
  195. data/spec/unit/daemon/interruptible_sleep_spec.rb +0 -68
  196. data/spec/unit/daemon/too_many_requests_error_spec.rb +0 -14
  197. data/spec/unit/gcm/app_spec.rb +0 -4
  198. data/spec/unit/notification_spec.rb +0 -15
  199. data/spec/unit/notifier_spec.rb +0 -49
  200. data/spec/unit/wpns/app_spec.rb +0 -4
  201. data/spec/unit/wpns/notification_spec.rb +0 -30
@@ -12,7 +12,7 @@ module Rpush
12
12
  AMAZON_ADM_URL = 'https://api.amazon.com/messaging/registrations/%s/messages'
13
13
 
14
14
  # Data used to request authorization tokens.
15
- ACCESS_TOKEN_REQUEST_DATA = {"grant_type" => "client_credentials", "scope" => "messaging:push"}
15
+ ACCESS_TOKEN_REQUEST_DATA = { "grant_type" => "client_credentials", "scope" => "messaging:push" }
16
16
 
17
17
  def initialize(app, http, notification, batch)
18
18
  @app = app
@@ -24,29 +24,27 @@ module Rpush
24
24
  end
25
25
 
26
26
  def perform
27
- begin
28
- @notification.registration_ids.each do |registration_id|
29
- handle_response(do_post(registration_id), registration_id)
30
- end
31
-
32
- if(@sent_registration_ids.empty?)
33
- raise Rpush::DeliveryError.new(nil, @notification.id, describe_errors)
34
- else
35
- unless(@failed_registration_ids.empty?)
36
- @notification.error_description = describe_errors
37
- Rpush::Daemon.store.update_notification(@notification)
38
- end
39
-
40
- mark_delivered
27
+ @notification.registration_ids.each do |registration_id|
28
+ handle_response(do_post(registration_id), registration_id)
29
+ end
30
+ if @sent_registration_ids.empty?
31
+ fail Rpush::DeliveryError.new(nil, @notification.id, describe_errors)
32
+ else
33
+ unless @failed_registration_ids.empty?
34
+ @notification.error_description = describe_errors
35
+ Rpush::Daemon.store.update_notification(@notification)
41
36
  end
42
- rescue Rpush::RetryableError => error
43
- handle_retryable(error)
44
- rescue Rpush::TooManyRequestsError => error
45
- handle_too_many_requests(error)
46
- rescue Rpush::DeliveryError => error
47
- mark_failed(error.code, error.description)
48
- raise
37
+ mark_delivered
49
38
  end
39
+ rescue Rpush::RateLimitError => error
40
+ handle_rate_limited(error)
41
+ rescue Rpush::RetryableError => error
42
+ handle_retryable(error)
43
+ rescue StandardError => error
44
+ mark_failed(error)
45
+ raise
46
+ ensure
47
+ @batch.notification_processed
50
48
  end
51
49
 
52
50
  protected
@@ -60,27 +58,27 @@ module Rpush
60
58
  when 401
61
59
  unauthorized(response)
62
60
  when 429
63
- too_many_requests(response)
61
+ rate_limited(response)
64
62
  when 500
65
- internal_server_error(response, current_registration_id)
63
+ internal_server_error(current_registration_id)
66
64
  when 503
67
65
  service_unavailable(response)
68
66
  else
69
- raise Rpush::DeliveryError.new(response.code, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
67
+ fail Rpush::DeliveryError.new(response.code, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
70
68
  end
71
69
  end
72
70
 
73
71
  def ok(response, current_registration_id)
74
72
  response_body = multi_json_load(response.body)
75
73
 
76
- if(response_body.has_key?('registrationID'))
74
+ if response_body.key?('registrationID')
77
75
  @sent_registration_ids << response_body['registrationID']
78
76
  log_info("#{@notification.id} sent to #{response_body['registrationID']}")
79
77
  end
80
78
 
81
- if(current_registration_id != response_body['registrationID'])
82
- reflect(:adm_canonical_id, current_registration_id, response_body['registrationID'])
83
- end
79
+ return if current_registration_id == response_body['registrationID']
80
+
81
+ reflect(:adm_canonical_id, current_registration_id, response_body['registrationID'])
84
82
  end
85
83
 
86
84
  def handle_retryable(error)
@@ -88,7 +86,7 @@ module Rpush
88
86
  when 401
89
87
  # clear app access_token so a new one is fetched
90
88
  @notification.app.access_token = nil
91
- get_access_token
89
+ access_token
92
90
  mark_retryable(@notification, Time.now) if @notification.app.access_token
93
91
  when 503
94
92
  retry_delivery(@notification, error.response)
@@ -96,8 +94,8 @@ module Rpush
96
94
  end
97
95
  end
98
96
 
99
- def handle_too_many_requests(error)
100
- if(@sent_registration_ids.empty?)
97
+ def handle_rate_limited(error)
98
+ if @sent_registration_ids.empty?
101
99
  # none sent yet, just resend after the specified retry-after response.header
102
100
  retry_delivery(@notification, error.response)
103
101
  else
@@ -120,30 +118,33 @@ module Rpush
120
118
  def bad_request(response, current_registration_id)
121
119
  response_body = multi_json_load(response.body)
122
120
 
123
- if(response_body.has_key?('reason'))
124
- log_warn("bad_request: #{current_registration_id} (#{response_body['reason']})")
125
- @failed_registration_ids[current_registration_id] = response_body['reason']
126
- end
121
+ return unless response_body.key?('reason')
122
+
123
+ reason = response_body['reason']
124
+ log_warn("bad_request: #{current_registration_id} (#{reason})")
125
+ @failed_registration_ids[current_registration_id] = reason
126
+
127
+ reflect(:adm_failed_to_recipient, @notification, current_registration_id, reason)
127
128
  end
128
129
 
129
130
  def unauthorized(response)
130
131
  # Indicate a notification is retryable. Because ADM requires separate request for each push token, this will safely mark the entire notification to retry delivery.
131
- raise Rpush::RetryableError.new(response.code.to_i, @notification.id, 'ADM responded with an Unauthorized Error.', response)
132
+ fail Rpush::RetryableError.new(response.code.to_i, @notification.id, 'ADM responded with an Unauthorized Error.', response)
132
133
  end
133
134
 
134
- def too_many_requests(response)
135
+ def rate_limited(response)
135
136
  # raise error so the current notification stops sending messages to remaining reg ids
136
- raise Rpush::TooManyRequestsError.new(response.code.to_i, @notification.id, 'Exceeded maximum allowable rate of messages.', response)
137
+ fail Rpush::RateLimitError.new(response.code.to_i, @notification.id, 'Exceeded maximum allowable rate of messages.', response)
137
138
  end
138
139
 
139
- def internal_server_error(response, current_registration_id)
140
+ def internal_server_error(current_registration_id)
140
141
  @failed_registration_ids[current_registration_id] = "Internal Server Error"
141
142
  log_warn("internal_server_error: #{current_registration_id} (Internal Server Error)")
142
143
  end
143
144
 
144
145
  def service_unavailable(response)
145
146
  # Indicate a notification is retryable. Because ADM requires separate request for each push token, this will safely mark the entire notification to retry delivery.
146
- raise Rpush::RetryableError.new(response.code.to_i, @notification.id, 'ADM responded with an Service Unavailable Error.', response)
147
+ fail Rpush::RetryableError.new(response.code.to_i, @notification.id, 'ADM responded with an Service Unavailable Error.', response)
147
148
  end
148
149
 
149
150
  def create_new_notification(response, registration_ids)
@@ -156,7 +157,8 @@ module Rpush
156
157
  end
157
158
 
158
159
  def retry_delivery(notification, response)
159
- if time = deliver_after_header(response)
160
+ time = deliver_after_header(response)
161
+ if time
160
162
  mark_retryable(notification, time)
161
163
  else
162
164
  mark_retryable_exponential(notification)
@@ -178,23 +180,22 @@ module Rpush
178
180
  end
179
181
 
180
182
  def do_post(registration_id)
181
- adm_uri = URI.parse(AMAZON_ADM_URL % [registration_id])
182
- post = Net::HTTP::Post.new(adm_uri.path, initheader = {
183
- 'Content-Type' => 'application/json',
184
- 'Accept' => 'application/json',
185
- 'x-amzn-type-version' => 'com.amazon.device.messaging.ADMMessage@1.0',
186
- 'x-amzn-accept-type' => 'com.amazon.device.messaging.ADMSendResult@1.0',
187
- 'Authorization' => "Bearer #{get_access_token}"
188
- })
183
+ adm_uri = URI.parse(format(AMAZON_ADM_URL, registration_id))
184
+ post = Net::HTTP::Post.new(adm_uri.path,
185
+ 'Content-Type' => 'application/json',
186
+ 'Accept' => 'application/json',
187
+ 'x-amzn-type-version' => 'com.amazon.device.messaging.ADMMessage@1.0',
188
+ 'x-amzn-accept-type' => 'com.amazon.device.messaging.ADMSendResult@1.0',
189
+ 'Authorization' => "Bearer #{access_token}")
189
190
  post.body = @notification.as_json.to_json
190
191
 
191
192
  @http.request(adm_uri, post)
192
193
  end
193
194
 
194
- def get_access_token
195
- if(@notification.app.access_token.nil? || @notification.app.access_token_expired?)
196
- post = Net::HTTP::Post.new(AMAZON_TOKEN_URI.path, initheader = {'Content-Type' => 'application/x-www-form-urlencoded'})
197
- post.set_form_data(ACCESS_TOKEN_REQUEST_DATA.merge({'client_id' => @notification.app.client_id, 'client_secret' => @notification.app.client_secret}))
195
+ def access_token
196
+ if @notification.app.access_token.nil? || @notification.app.access_token_expired?
197
+ post = Net::HTTP::Post.new(AMAZON_TOKEN_URI.path, 'Content-Type' => 'application/x-www-form-urlencoded')
198
+ post.set_form_data(ACCESS_TOKEN_REQUEST_DATA.merge('client_id' => @notification.app.client_id, 'client_secret' => @notification.app.client_secret))
198
199
 
199
200
  handle_access_token(@http.request(AMAZON_TOKEN_URI, post))
200
201
  end
@@ -203,7 +204,7 @@ module Rpush
203
204
  end
204
205
 
205
206
  def handle_access_token(response)
206
- if(response.code.to_i == 200)
207
+ if response.code.to_i == 200
207
208
  update_access_token(JSON.parse(response.body))
208
209
  Rpush::Daemon.store.update_app(@notification.app)
209
210
  log_info("ADM access token updated: token = #{@notification.app.access_token}, expires = #{@notification.app.access_token_expiration}")
@@ -2,60 +2,36 @@ module Rpush
2
2
  module Daemon
3
3
  module Apns
4
4
  class Delivery < Rpush::Daemon::Delivery
5
- SELECT_TIMEOUT = 0.2
6
- ERROR_TUPLE_BYTES = 6
7
- APN_ERRORS = {
8
- 1 => "Processing error",
9
- 2 => "Missing device token",
10
- 3 => "Missing topic",
11
- 4 => "Missing payload",
12
- 5 => "Missing token size",
13
- 6 => "Missing topic size",
14
- 7 => "Missing payload size",
15
- 8 => "Invalid token",
16
- 255 => "None (unknown error)"
17
- }
18
-
19
- def initialize(app, conneciton, notification, batch)
5
+ def initialize(app, connection, batch)
20
6
  @app = app
21
- @connection = conneciton
22
- @notification = notification
7
+ @connection = connection
23
8
  @batch = batch
24
9
  end
25
10
 
26
11
  def perform
27
- begin
28
- @connection.write(@notification.to_binary)
29
- check_for_error if Rpush.config.check_for_errors
30
- mark_delivered
31
- log_info("#{@notification.id} sent to #{@notification.device_token}")
32
- rescue Rpush::DeliveryError, Rpush::Apns::DisconnectionError => error
33
- mark_failed(error.code, error.description)
34
- raise
35
- end
12
+ @connection.write(batch_to_binary)
13
+ mark_batch_delivered
14
+ describe_deliveries
15
+ rescue StandardError => error
16
+ mark_batch_failed(error)
17
+ raise
18
+ ensure
19
+ @batch.all_processed
36
20
  end
37
21
 
38
22
  protected
39
23
 
40
- def check_for_error
41
- if @connection.select(SELECT_TIMEOUT)
42
- error = nil
43
-
44
- if tuple = @connection.read(ERROR_TUPLE_BYTES)
45
- _, code, notification_id = tuple.unpack("ccN")
46
-
47
- description = APN_ERRORS[code.to_i] || "Unknown error. Possible Rpush bug?"
48
- error = Rpush::DeliveryError.new(code, notification_id, description)
49
- else
50
- error = Rpush::Apns::DisconnectionError.new
51
- end
24
+ def batch_to_binary
25
+ payload = ""
26
+ @batch.each_notification do |notification|
27
+ payload << notification.to_binary
28
+ end
29
+ payload
30
+ end
52
31
 
53
- begin
54
- log_error("Error received, reconnecting...")
55
- @connection.reconnect
56
- ensure
57
- raise error if error
58
- end
32
+ def describe_deliveries
33
+ @batch.each_notification do |notification|
34
+ log_info("#{notification.id} sent to #{notification.device_token}")
59
35
  end
60
36
  end
61
37
  end
@@ -7,28 +7,29 @@ module Rpush
7
7
 
8
8
  TUPLE_BYTES = 38
9
9
  HOSTS = {
10
- :production => ['feedback.push.apple.com', 2196],
11
- :development => ['feedback.sandbox.push.apple.com', 2196], # deprecated
12
- :sandbox => ['feedback.sandbox.push.apple.com', 2196]
10
+ production: ['feedback.push.apple.com', 2196],
11
+ development: ['feedback.sandbox.push.apple.com', 2196], # deprecated
12
+ sandbox: ['feedback.sandbox.push.apple.com', 2196]
13
13
  }
14
14
 
15
15
  def initialize(app)
16
16
  @app = app
17
17
  @host, @port = HOSTS[@app.environment.to_sym]
18
- @poll = Rpush.config.feedback_poll
19
18
  @certificate = app.certificate
20
19
  @password = app.password
21
- @interruptible_sleep = InterruptibleSleep.new
20
+ @interruptible_sleep = InterruptibleSleep.new(Rpush.config.feedback_poll)
22
21
  end
23
22
 
24
23
  def start
25
24
  return if Rpush.config.push
25
+ log_info("APNs Feedback Receiver started.")
26
+ @interruptible_sleep.start
26
27
 
27
28
  @thread = Thread.new do
28
29
  loop do
29
30
  break if @stop
30
31
  check_for_feedback
31
- @interruptible_sleep.sleep @poll
32
+ @interruptible_sleep.sleep
32
33
  end
33
34
 
34
35
  Rpush::Daemon.store.release_connection
@@ -37,7 +38,7 @@ module Rpush
37
38
 
38
39
  def stop
39
40
  @stop = true
40
- @interruptible_sleep.interrupt_sleep
41
+ @interruptible_sleep.stop
41
42
  @thread.join if @thread
42
43
  end
43
44
 
@@ -46,10 +47,12 @@ module Rpush
46
47
  begin
47
48
  connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
48
49
  connection.connect
50
+ tuple = connection.read(TUPLE_BYTES)
49
51
 
50
- while tuple = connection.read(TUPLE_BYTES)
52
+ while tuple
51
53
  timestamp, device_token = parse_tuple(tuple)
52
54
  create_feedback(timestamp, device_token)
55
+ tuple = connection.read(TUPLE_BYTES)
53
56
  end
54
57
  rescue StandardError => e
55
58
  log_error(e)
@@ -4,13 +4,14 @@ module Rpush
4
4
  extend ServiceConfigMethods
5
5
 
6
6
  HOSTS = {
7
- :production => ['gateway.push.apple.com', 2195],
8
- :development => ['gateway.sandbox.push.apple.com', 2195], # deprecated
9
- :sandbox => ['gateway.sandbox.push.apple.com', 2195]
7
+ production: ['gateway.push.apple.com', 2195],
8
+ development: ['gateway.sandbox.push.apple.com', 2195], # deprecated
9
+ sandbox: ['gateway.sandbox.push.apple.com', 2195]
10
10
  }
11
11
 
12
- dispatcher :tcp, :host => Proc.new { |app| HOSTS[app.environment.to_sym] }
13
- loops Rpush::Daemon::Apns::FeedbackReceiver
12
+ batch_deliveries true
13
+ dispatcher :apns_tcp, host: proc { |app| HOSTS[app.environment.to_sym] }
14
+ loops Rpush::Daemon::Apns::FeedbackReceiver, if: -> { !Rpush.config.push }
14
15
  end
15
16
  end
16
17
  end
@@ -4,152 +4,161 @@ module Rpush
4
4
  extend Reflectable
5
5
  include Reflectable
6
6
  include Loggable
7
-
8
- class << self
9
- attr_reader :runners
10
- end
7
+ extend Loggable
8
+ include StringHelpers
9
+ extend StringHelpers
11
10
 
12
11
  @runners = {}
13
12
 
14
13
  def self.enqueue(notifications)
15
14
  notifications.group_by(&:app_id).each do |app_id, group|
16
- batch = Batch.new(group)
17
- if app = runners[app_id]
18
- app.enqueue(batch)
19
- else
20
- Rpush.logger.error("No such app '#{app_id}' for notifications #{batch.describe}.")
21
- end
15
+ start_app_with_id(app_id) unless @runners[app_id]
16
+ @runners[app_id].enqueue(group) if @runners[app_id]
22
17
  end
18
+
19
+ ProcTitle.update
23
20
  end
24
21
 
25
- def self.sync
26
- apps = Rpush::App.all
27
- apps.each { |app| sync_app(app) }
28
- removed = runners.keys - apps.map(&:id)
29
- removed.each { |app_id| runners.delete(app_id).stop }
22
+ def self.start_app_with_id(app_id)
23
+ start_app(Rpush::Daemon.store.app(app_id))
30
24
  end
31
25
 
32
- def self.sync_app(app)
33
- if runners[app.id]
34
- runners[app.id].sync(app)
35
- else
36
- runner = new(app)
37
- begin
38
- runner.start
39
- runners[app.id] = runner
40
- rescue StandardError => e
41
- Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
42
- Rpush.logger.error(e)
43
- reflect(:error, e)
44
- end
26
+ def self.start_app(app)
27
+ @runners[app.id] = new(app)
28
+ @runners[app.id].start
29
+ log_info("[#{app.name}] Started, #{pluralize(app.connections, 'dispatcher')}.")
30
+ rescue StandardError => e
31
+ @runners.delete(app.id)
32
+ Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
33
+ Rpush.logger.error(e)
34
+ reflect(:error, e)
35
+ end
36
+
37
+ def self.stop_app(app_id)
38
+ runner = @runners.delete(app_id)
39
+ if runner
40
+ runner.stop
41
+ log_info("[#{runner.app.name}] Stopped.")
45
42
  end
46
43
  end
47
44
 
45
+ def self.app_with_id(app_id)
46
+ @runners[app_id].app
47
+ end
48
+
49
+ def self.app_running?(app)
50
+ @runners.key?(app.id)
51
+ end
52
+
53
+ def self.app_ids
54
+ @runners.keys
55
+ end
56
+
48
57
  def self.stop
49
- runners.values.map(&:stop)
50
- runners.clear
58
+ @runners.values.map(&:stop)
59
+ @runners.clear
51
60
  end
52
61
 
53
- def self.debug
54
- runners.values.map(&:debug)
62
+ def self.total_dispatchers
63
+ @runners.values.sum(&:num_dispatcher_loops)
64
+ end
65
+
66
+ def self.total_queued
67
+ @runners.values.sum(&:queue_size)
68
+ end
69
+
70
+ def self.num_dispatchers_for_app(app)
71
+ runner = @runners[app.id]
72
+ runner ? runner.num_dispatcher_loops : 0
73
+ end
74
+
75
+ def self.decrement_dispatchers(app, num)
76
+ @runners[app.id].decrement_dispatchers(num)
55
77
  end
56
78
 
57
- def self.idle
58
- runners.values.select(&:idle?)
79
+ def self.increment_dispatchers(app, num)
80
+ @runners[app.id].increment_dispatchers(num)
59
81
  end
60
82
 
61
- def self.wait
62
- sleep 0.1 while !runners.values.all?(&:idle?)
83
+ def self.debug
84
+ @runners.values.map(&:debug)
63
85
  end
64
86
 
65
87
  attr_reader :app
66
- attr_accessor :batch
67
88
 
68
89
  def initialize(app)
69
90
  @app = app
70
91
  @loops = []
92
+ @dispatcher_loops = []
71
93
  end
72
94
 
73
95
  def start
74
- app.connections.times { dispatchers.push(new_dispatcher_loop) }
96
+ app.connections.times { @dispatcher_loops.push(new_dispatcher_loop) }
75
97
  start_loops
76
- log_info("Started, #{dispatchers_str}.")
77
98
  end
78
99
 
79
100
  def stop
80
- dispatchers.stop
101
+ wait_until_idle
102
+ stop_dispatcher_loops
81
103
  stop_loops
82
104
  end
83
105
 
84
- def enqueue(batch)
85
- self.batch = batch
86
- batch.notifications.each do |notification|
87
- queue.push([notification, batch])
88
- reflect(:notification_enqueued, notification)
89
- end
106
+ def wait_until_idle
107
+ sleep 0.5 while queue.size > 0
90
108
  end
91
109
 
92
- def sync(app)
93
- @app = app
94
- diff = dispatchers.size - app.connections
95
- return if diff == 0
96
- if diff > 0
97
- decrement_dispatchers(diff)
98
- log_info("Stopped #{dispatchers_str(diff)}. #{dispatchers_str} running.")
110
+ def enqueue(notifications)
111
+ if service.batch_deliveries?
112
+ batch_size = (notifications.size / num_dispatcher_loops.to_f).ceil
113
+ notifications.in_groups_of(batch_size, false).each do |batch_notifications|
114
+ batch = Batch.new(batch_notifications)
115
+ queue.push(QueuePayload.new(batch))
116
+ end
99
117
  else
100
- increment_dispatchers(diff.abs)
101
- log_info("Started #{dispatchers_str(diff)}. #{dispatchers_str} running.")
118
+ batch = Batch.new(notifications)
119
+ notifications.each do |notification|
120
+ queue.push(QueuePayload.new(batch, notification))
121
+ reflect(:notification_enqueued, notification)
122
+ end
102
123
  end
103
124
  end
104
125
 
105
126
  def decrement_dispatchers(num)
106
- num.times { dispatchers.pop }
127
+ num.times { @dispatcher_loops.pop.stop }
107
128
  end
108
129
 
109
130
  def increment_dispatchers(num)
110
- num.times { dispatchers.push(new_dispatcher_loop) }
131
+ num.times { @dispatcher_loops.push(new_dispatcher_loop) }
111
132
  end
112
133
 
113
134
  def debug
114
- Rpush.logger.info <<-EOS
115
-
116
- #{@app.name}:
117
- dispatchers: #{num_dispatchers}
118
- queued: #{queue_size}
119
- batch size: #{batch_size}
120
- batch processed: #{batch_processed}
121
- idle: #{idle?}
122
- EOS
123
- end
135
+ dispatcher_details = {}
136
+
137
+ @dispatcher_loops.each_with_index do |dispatcher_loop, i|
138
+ dispatcher_details[i] = {
139
+ started_at: dispatcher_loop.started_at.iso8601,
140
+ dispatched: dispatcher_loop.dispatch_count,
141
+ thread_status: dispatcher_loop.thread_status
142
+ }
143
+ end
124
144
 
125
- def idle?
126
- batch ? batch.complete? : true
145
+ runner_details = { dispatchers: dispatcher_details, queued: queue_size }
146
+ log_info(JSON.pretty_generate(runner_details))
127
147
  end
128
148
 
129
149
  def queue_size
130
150
  queue.size
131
151
  end
132
152
 
133
- def batch_size
134
- batch ? batch.num_notifications : 0
135
- end
136
-
137
- def batch_processed
138
- batch ? batch.num_processed : 0
139
- end
140
-
141
- def num_dispatchers
142
- dispatchers.size
153
+ def num_dispatcher_loops
154
+ @dispatcher_loops.size
143
155
  end
144
156
 
145
- protected
157
+ private
146
158
 
147
159
  def start_loops
148
- service_module.loops.each do |loop_class|
149
- instance = loop_class.new(@app)
150
- instance.start
151
- @loops << instance
152
- end
160
+ @loops = service.loop_instances(@app)
161
+ @loops.map(&:start)
153
162
  end
154
163
 
155
164
  def stop_loops
@@ -157,31 +166,26 @@ module Rpush
157
166
  @loops = []
158
167
  end
159
168
 
169
+ def stop_dispatcher_loops
170
+ @dispatcher_loops.map(&:stop)
171
+ @dispatcher_loops.clear
172
+ end
173
+
160
174
  def new_dispatcher_loop
161
- dispatcher = service_module.new_dispatcher(@app)
175
+ dispatcher = service.new_dispatcher(@app)
162
176
  dispatcher_loop = Rpush::Daemon::DispatcherLoop.new(queue, dispatcher)
163
177
  dispatcher_loop.start
164
178
  dispatcher_loop
165
179
  end
166
180
 
167
- def service_module
168
- return @service_module if defined? @service_module
169
- @service_module = "Rpush::Daemon::#{@app.service_name.camelize}".constantize
181
+ def service
182
+ return @service if defined? @service
183
+ @service = "Rpush::Daemon::#{@app.service_name.camelize}".constantize
170
184
  end
171
185
 
172
186
  def queue
173
187
  @queue ||= Queue.new
174
188
  end
175
-
176
- def dispatchers
177
- @dispatchers ||= Rpush::Daemon::DispatcherLoopCollection.new
178
- end
179
-
180
- def dispatchers_str(count = app.connections)
181
- count = count.abs
182
- str = count == 1 ? 'dispatcher' : 'dispatchers'
183
- "#{count} #{str}"
184
- end
185
189
  end
186
190
  end
187
191
  end