rpush 1.0.0-java → 2.0.0-java

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 (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