apn_sender 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d85dcde79eb0f21533547c631b827e73081d5167
4
- data.tar.gz: a3bd028152b5f7ee6960b1516bf87b2fa7abdce8
3
+ metadata.gz: e9297870e306966d00d3d0acf01a3022dbbb1636
4
+ data.tar.gz: 25b8a81befe782fba5a0b7f0376ce9491f674a86
5
5
  SHA512:
6
- metadata.gz: 96a448c4a6d1ed2fcc9807617eea4171e876ef45f7dd94b41925ca84c81f656404ef526ab590f7d30612571bd3ecab4f47621aabe44aeb66c5c3909b89794da5
7
- data.tar.gz: 12b386591581bdcb234d0dd90de44f0836ad27705aedeeefcc6e0333d5225207fdabd0e085c0cf2675abfbbed1b725d716611ca859ea06c60a6ffe9cb1385a46
6
+ metadata.gz: 5048150d58ffba87b276ebe50d24bd1ab744f6eefa58ac94662cf22b5b773fde19fa66b840c1c982303693460d0d53283606aa9a0af7f99f04a6e4eb6802e634
7
+ data.tar.gz: ef4fc9e0ce014ac4bef75d1da4f858ae6317a86bed03aa09e8ca1f248e9b9cffef8f9a5ec2939024fb7db4ee89cfe87e91dcc0bb2c302a581cfcb8a38466f126
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
1
  # Version 2.0
2
+ ## 2.0.1
3
+ - Use bytesize to truncate alert when necessary
4
+ - Better calculation on payload size. (botvinik)
5
+ - Fix generating payload should use bytesize. (piotr-sokolowski)
6
+ - Rescuing and repairing broken connections (Arseniy Ivanov)
7
+
2
8
  ## 2.0.0
3
9
  - adding connection_pool for handle apple sockets
4
10
  - removing resque hard dependency
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Kali Donovan
1
+ Copyright (c) 2013 Arthur Nogueira Neves
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,32 +1,33 @@
1
1
  [![Code Climate](https://codeclimate.com/github/arthurnn/apn_sender.png)](https://codeclimate.com/github/arthurnn/apn_sender)
2
2
  [![Build Status](https://travis-ci.org/arthurnn/apn_sender.png)](https://travis-ci.org/arthurnn/apn_sender)
3
3
 
4
- ## UPDATE May 3, 2013: current status
5
-
6
- This project was not supported for a while, but we are going to support it again.
7
-
8
- -----
9
-
10
4
  ## Synopsis
11
5
 
12
6
  Need to send background notifications to an iPhone application over a <em>persistent</em> connection in Ruby? Keep reading...
13
7
 
14
8
  ## The Story
15
9
 
16
- So you're building the server component of an iPhone application in Ruby. And you want to send background notifications through the Apple Push Notification servers, which doesn't seem too bad at first. But then you read in the [Apple Documentation](https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html) that Apple's servers may treat non-persistent connections as a Denial of Service attack, and you realize that Rails has no easy way to maintain a persistent connection internally, and things start looking more complicated.
10
+ So you're building the server component of an iPhone application in Ruby and you want to send background notifications through the Apple Push Notification servers. This doesn't seem too bad at first, but then you read in the [Apple Documentation](https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html) that Apple's servers may treat non-persistent connections as a Denial of Service attack. Since Rails has no easy way to maintain a persistent connection internally, things start to look complicated.
17
11
 
18
- The apn_sender gem includes a background daemon which processes background messages from your application and sends them along to Apple <em>over a single, persistent socket</em>. It also includes the ability to query the Feedback service, helper methods for enqueueing your jobs, and a sample monit config to make sure the background worker is around when you need it.
12
+ This gem includes a background daemon which processes background messages from your application and sends them along to Apple <em>over a single, persistent socket</em>. It also includes the ability to query the Feedback service, helper methods for enqueueing your jobs, and a sample monit config to make sure the background worker is around when you need it.
19
13
 
20
14
  ## Yet another ApplePushNotification interface?
21
15
 
22
16
  Yup. There's some great code out there already, but we didn't like the idea of getting banned from the APN gateway for establishing a new connection each time we needed to send a batch of messages, and none of the libraries I found handled maintaining a persistent connection.
23
17
 
24
18
  ## Current Status
25
- This gem has been used in production, on 500px, sending millions of notifications.
19
+
20
+ This gem has been used in production, on [500px](http://500px.com), sending hundreds of millions, if not, billions of notifications.
26
21
 
27
22
  ## Usage
28
23
 
29
- ### 1. Queueing Messages From Your Application
24
+ APN sender can use [Resque](http://github.com/defunkt/resque) or [Sidekiq](https://github.com/mperham/sidekiq) to send asynchronous messages, if none of them are installed it creates a new thread to send messages.
25
+
26
+ ### 1. Use a background processor or not.
27
+
28
+ You can either use Resque or Sidekiq, I strongly advice using Sidekiq, as apn_sender uses a connection pool for the apple socks. To use apn_sender with one of them you dont have to do anything, just include the background processor gem into your gemfile and it will all work.
29
+
30
+ ### 2. Queueing Messages From Your Application
30
31
 
31
32
  To queue a message for sending through Apple's Push Notification service from your Rails application:
32
33
 
@@ -34,44 +35,33 @@ To queue a message for sending through Apple's Push Notification service from yo
34
35
  APN.notify_async(token, opts_hash)
35
36
  ```
36
37
 
37
- where ```token``` is the unique identifier of the iPhone to receive the notification and ```opts_hash``` can have any of the following keys:
38
-
39
- Options:
38
+ Where ```token``` is the unique identifier of the iPhone to receive the notification and ```opts_hash``` can have any of the following keys:
40
39
 
41
40
  * :alert ## The alert to send
42
41
  * :badge ## The badge number to send
43
42
  * :sound ## The sound file to play on receipt, or true to play the default sound installed with your app
44
43
 
45
-
46
44
  If any other keys are present they'll be be passed along as custom data to your application.
47
45
 
48
- ### 2. Sending Queued Messages
46
+ ### 3. Sending Queued Messages
49
47
 
50
48
  Put your ```apn_development.pem``` and ```apn_production.pem``` certificates from Apple in your ```RAILS_ROOT/config/certs``` directory.
51
49
 
52
- Once this is done, you can fire off a background worker with
50
+ You also can configure some extra settings:
53
51
 
54
52
  ```
55
- $ rake apn:sender
56
- ```
57
-
58
- For production, you're probably better off running a dedicated daemon and setting up monit to watch over it for you. Luckily, that's pretty easy:
59
-
60
- ```
61
- # To generate daemon
62
- ./script/generate apn_sender
63
-
64
- # To run daemon. Pass --help to print all options
65
- ./script/apn_sender start
53
+ APN.root = 'RAILS_ROOT/config/certs' # root to certificates folder
54
+ APN.certificate_name = 'apn_production.pem' # certificate filename
55
+ APN.host = 'apple host (on development sandbox url is used by default)'
56
+ APN.password = 'certificate_password'
57
+ APN.pool_size = 1 # number of connections on the pool
58
+ APN.pool_timeout = 5 # timeout in seconds for connection pool
66
59
  ```
67
60
 
68
-
69
- Also, there are two similar options: ```:cert_path``` and ```:full_cert_path```. The former specifies the directory in which to find the .pem file (either apn_production.pem or apn_development.pem, depending on the environment). The latter specifies a .pem file explicitly, allowing customized certificate names if needed.
70
-
71
61
  Check ```logs/apn_sender.log``` for debugging output. In addition to logging any major errors there, apn_sender hooks into the Resque::Worker logging to display any verbose or very_verbose worker output in apn_sender.log file as well.
72
62
 
73
63
 
74
- ### 3. Checking Apple's Feedback Service
64
+ ### 4. Checking Apple's Feedback Service
75
65
 
76
66
  Since push notifications are a fire-and-forget sorta deal, where you get no indication if your message was received (or if the specified recipient even exists), Apple needed to come up with some other way to ensure their network isn't clogged with thousands of bogus messages (e.g. from developers sending messages to phones where their application <em>used</em> to be installed, but where the user has since removed it). Hence, the Feedback Service.
77
67
 
@@ -134,27 +124,28 @@ There's also an included sample ```apn_sender.monitrc``` file in the ```contrib/
134
124
 
135
125
  ## Installation
136
126
 
137
- APN is built on top of [Resque](http://github.com/defunkt/resque) (an awesome [Redis](http://code.google.com/p/redis/)-based background runner similar to [delayed_job](http://github.com/collectiveidea/delayed_job)). Read through the [Resque README](http://github.com/defunkt/resque#readme) to get a feel for what's going on, follow the installation instructions there, and then run:
127
+ Add this line to your application's Gemfile:
138
128
 
139
- ```
140
- $ gem install apn_sender
141
- ```
142
- In your Rails app, add (2.3.x):
129
+ gem 'apn_sender', require: 'apn'
143
130
 
144
- ```
145
- config.gem 'apn_sender', :lib => 'apn'
146
- ```
147
- or (3.x) to your Gemfile:
131
+ And then execute:
132
+
133
+ $ bundle
134
+
135
+ Or install it yourself as:
136
+
137
+ $ gem install apn_sender
148
138
 
149
- ```
150
- gem 'apn_sender', require: 'apn'
151
- ```
152
139
  To add a few useful rake tasks for running workers, add the following line to your Rakefile:
153
140
 
154
141
  ```
155
142
  require 'apn/tasks'
156
143
  ```
157
144
 
158
- ## Copyright
145
+ ## License
146
+
147
+ APN Sender is released under the [MIT License](http://www.opensource.org/licenses/MIT).
148
+
149
+
150
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/arthurnn/apn_sender/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
159
151
 
160
- Copyright (c) 2013 Arthur Nogueira Neves. See LICENSE for details.
data/lib/apn.rb CHANGED
@@ -34,8 +34,10 @@ module APN
34
34
  def backend
35
35
  @backend ||=
36
36
  if defined?(Sidekiq)
37
+ require 'apn/jobs/sidekiq_notification_job'
37
38
  APN::Backend::Sidekiq.new
38
39
  elsif defined?(Resque)
40
+ require 'apn/jobs/resque_notification_job'
39
41
  APN::Backend::Resque.new
40
42
  else
41
43
  APN::Backend::Simple.new
@@ -89,8 +91,5 @@ module APN::Jobs
89
91
  QUEUE_NAME = :apple_push_notifications
90
92
  end
91
93
 
92
- require 'apn/jobs/sidekiq_notification_job' if defined?(Sidekiq)
93
- require 'apn/jobs/resque_notification_job' if defined?(Resque)
94
94
  require "apn/railtie" if defined?(Rails)
95
-
96
95
  require 'apn/backend'
data/lib/apn/client.rb CHANGED
@@ -13,6 +13,7 @@ module APN
13
13
  def push(message)
14
14
  socket.write(message.to_s)
15
15
  socket.flush
16
+
16
17
  if IO.select([socket], nil, nil, 1) && error = socket.read(6)
17
18
  error = error.unpack("ccN")
18
19
  APN.log(:error, "Error on message: #{error}")
@@ -21,6 +22,11 @@ module APN
21
22
 
22
23
  APN.log(:debug, "Message sent.")
23
24
  true
25
+ rescue OpenSSL::SSL::SSLError, Errno::EPIPE => e
26
+ APN.log(:error, "[##{self.object_id}] Exception occurred: #{e.inspect}, socket state: #{socket.inspect}")
27
+ reset_socket
28
+ APN.log(:debug, "[##{self.object_id}] Socket reestablished, socket state: #{socket.inspect}")
29
+ retry
24
30
  end
25
31
 
26
32
  def feedback
@@ -35,6 +41,7 @@ module APN
35
41
  end
36
42
 
37
43
  private
44
+
38
45
  # Open socket to Apple's servers
39
46
  def setup_socket
40
47
  ctx = setup_certificate
@@ -48,6 +55,12 @@ module APN
48
55
  end
49
56
  end
50
57
 
58
+ def reset_socket
59
+ @socket.close if @socket
60
+ @socket = nil
61
+ socket
62
+ end
63
+
51
64
  def setup_certificate
52
65
  ctx = OpenSSL::SSL::SSLContext.new
53
66
  ctx.cert = OpenSSL::X509::Certificate.new(@apn_cert)
@@ -1,7 +1,6 @@
1
1
  module APN
2
2
  module Connection
3
- # APN::Connection::Base takes care of all the boring certificate loading, socket creating, and logging
4
- # responsibilities so APN::Sender and APN::Feedback and focus on their respective specialties.
3
+
5
4
  def connection_pool
6
5
  @pool ||= ConnectionPool.new(size: (pool_size || 1), timeout: (pool_timeout || 5)) do
7
6
  APN::Client.new(host: host,
@@ -1,6 +1,6 @@
1
1
  module APN::Jobs
2
- # This is the class that's actually enqueued via Resque when user calls +APN.notify+.
3
- # It gets added to the +apple_server_notifications+ Resque queue, which should only be operated on by
2
+ # This is the class that's actually enqueued via Sidekiq when user calls +APN.notify+.
3
+ # It gets added to the +apple_server_notifications+ Sidekiq queue, which should only be operated on by
4
4
  # workers of the +APN::Sender+ class.
5
5
  class SidekiqNotificationJob
6
6
  include Sidekiq::Worker
@@ -19,9 +19,8 @@ module APN
19
19
  #
20
20
  class Notification
21
21
  # Available to help clients determine before they create the notification if their message will be too large.
22
- # Each iPhone Notification payload must be 256 or fewer characters. Encoding a null message has a 57
23
- # character overhead, so there are 199 characters available for the alert string.
24
- MAX_ALERT_LENGTH = 199
22
+ # Each iPhone Notification payload must be 256 or fewer characters (not including the token or other push data), see Apple specs at:
23
+ # https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4
25
24
  DATA_MAX_BYTES = 256
26
25
 
27
26
  attr_accessor :options, :token
@@ -29,15 +28,17 @@ module APN
29
28
  @options = opts.is_a?(Hash) ? opts.symbolize_keys : {:alert => opts}
30
29
  @token = token
31
30
 
32
- truncate_alert! if APN.truncate_alert
33
-
34
- raise "The maximum size allowed for a notification payload is 256 bytes." if packaged_notification.size.to_i > 256
31
+ raise "The maximum size allowed for a notification payload is #{DATA_MAX_BYTES} bytes." if payload_size > DATA_MAX_BYTES
35
32
  end
36
33
 
37
34
  def to_s
38
35
  packaged_notification
39
36
  end
40
37
 
38
+ def payload_size
39
+ packaged_message.bytesize
40
+ end
41
+
41
42
  # Ensures at least one of <code>%w(alert badge sound)</code> is present
42
43
  def valid?
43
44
  return true if [:alert, :badge, :sound].any?{|key| options.keys.include?(key) }
@@ -48,7 +49,7 @@ module APN
48
49
  def packaged_notification
49
50
  pt = packaged_token
50
51
  pm = packaged_message
51
- [0, 0, 32, pt, 0, pm.size, pm].pack("ccca*cca*")
52
+ [0, 0, 32, pt, 0, payload_size, pm].pack("ccca*cca*")
52
53
  end
53
54
 
54
55
  # Device token, compressed and hex-ified
@@ -60,29 +61,58 @@ module APN
60
61
  # Extracts :alert, :badge, and :sound keys into the 'aps' hash, merges any other hash data
61
62
  # into the root of the hash to encode and send to apple.
62
63
  def packaged_message
63
- opts = @options.clone # Don't destroy our pristine copy
64
- hsh = {'aps' => {}}
65
- if alert = opts.delete(:alert)
66
- alert = alert.to_s unless alert.is_a?(Hash)
67
- hsh['aps']['alert'] = alert
68
- end
69
- hsh['aps']['badge'] = opts.delete(:badge).to_i if opts[:badge]
70
- if sound = opts.delete(:sound)
71
- hsh['aps']['sound'] = sound.is_a?(TrueClass) ? 'default' : sound.to_s
72
- end
73
- hsh.merge!(opts)
74
- ActiveSupport::JSON::encode(hsh)
64
+ @packaged_message ||=
65
+ begin
66
+ opts = @options.dup
67
+ hsh = {'aps' => {}}
68
+ if alert = opts.delete(:alert)
69
+ alert = alert.to_s unless alert.is_a?(Hash)
70
+ hsh['aps']['alert'] = alert
71
+ end
72
+ hsh['aps']['badge'] = opts.delete(:badge).to_i if opts[:badge]
73
+ if sound = opts.delete(:sound)
74
+ hsh['aps']['sound'] = sound.is_a?(TrueClass) ? 'default' : sound.to_s
75
+ end
76
+ hsh.merge!(opts)
77
+ payload(hsh)
78
+ end
75
79
  end
76
80
 
77
- def truncate_alert!
78
- while packaged_notification.size.to_i > DATA_MAX_BYTES
79
- if @options[:alert].is_a? Hash
80
- last = @options[:alert]['loc-args'].pop
81
- @options[:alert]['loc-args'] << last[0..-2]
82
- else
83
- @options[:alert] = @options[:alert][0..-2]
81
+ private
82
+
83
+ def payload(hash)
84
+ str = ActiveSupport::JSON::encode(hash)
85
+
86
+ if APN.truncate_alert && str.bytesize > DATA_MAX_BYTES
87
+ if hash['aps']['alert'].is_a?(Hash)
88
+ alert = hash['aps']['alert']['loc-args'][0]
89
+ else
90
+ alert = hash['aps']['alert']
91
+ end
92
+ max_bytesize = DATA_MAX_BYTES - (str.bytesize - alert.bytesize)
93
+
94
+ raise "Even truncating the alert wont be enought to have a #{DATA_MAX_BYTES} message" if max_bytesize <= 0
95
+ alert = truncate_alert(alert, max_bytesize)
96
+
97
+ if hash['aps']['alert'].is_a?(Hash)
98
+ hash['aps']['alert']['loc-args'][0] = alert
99
+ else
100
+ hash['aps']['alert'] = alert
101
+ end
102
+ str = ActiveSupport::JSON::encode(hash)
84
103
  end
104
+ str
85
105
  end
86
- end
106
+
107
+ def truncate_alert(alert, max_size)
108
+ alert.each_char.each_with_object('') do |char, result|
109
+ if result.bytesize + char.bytesize > max_size
110
+ break result
111
+ else
112
+ result << char
113
+ end
114
+ end
115
+ end
116
+
87
117
  end
88
118
  end
data/lib/apn/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module APN
3
- VERSION = "2.0.0"
3
+ VERSION = "2.0.1"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apn_sender
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kali Donovan
@@ -15,42 +15,42 @@ dependencies:
15
15
  name: connection_pool
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - '>='
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '0'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - '>='
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: activesupport
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - '>='
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: '3.1'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - '>='
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '3.1'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: daemons
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - '>='
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
48
  version: '0'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - '>='
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
56
  description: Background worker to send Apple Push Notifications over a persistent
@@ -61,6 +61,11 @@ executables: []
61
61
  extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
+ - CHANGELOG.md
65
+ - LICENSE
66
+ - README.md
67
+ - Rakefile
68
+ - lib/apn.rb
64
69
  - lib/apn/backend.rb
65
70
  - lib/apn/client.rb
66
71
  - lib/apn/connection.rb
@@ -72,11 +77,6 @@ files:
72
77
  - lib/apn/sender_daemon.rb
73
78
  - lib/apn/tasks.rb
74
79
  - lib/apn/version.rb
75
- - lib/apn.rb
76
- - CHANGELOG.md
77
- - LICENSE
78
- - README.md
79
- - Rakefile
80
80
  homepage: http://github.com/arthurnn/apn_sender
81
81
  licenses:
82
82
  - MIT
@@ -87,17 +87,17 @@ require_paths:
87
87
  - lib
88
88
  required_ruby_version: !ruby/object:Gem::Requirement
89
89
  requirements:
90
- - - '>='
90
+ - - ">="
91
91
  - !ruby/object:Gem::Version
92
92
  version: '1.9'
93
93
  required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - '>='
95
+ - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: 1.3.6
98
98
  requirements: []
99
99
  rubyforge_project:
100
- rubygems_version: 2.0.3
100
+ rubygems_version: 2.2.0
101
101
  signing_key:
102
102
  specification_version: 4
103
103
  summary: Background worker to send Apple Push Notifications over a persistent TCP