apn_sender 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,17 +1,20 @@
1
- = CURRENT STATUS
1
+ == Synopsis
2
2
 
3
- This gem is still in development. It is functional for me, but I'm still working on getting the background daemon running properly (read: haven't tested it at all, expect it to fail).
4
-
5
- I'll be working on this soon, though, and expect to have a stable version released in the next few weeks (or maybe days).
6
-
3
+ Need to send background notifications to an iPhone application over a <em>persistent</em> connection in Ruby? Keep reading...
4
+
5
+ == The Story
6
+
7
+ 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/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/WhatAreRemoteNotif/WhatAreRemoteNotif.html#//apple_ref/doc/uid/TP40008194-CH102-SW7] 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 started looking more complicated.
8
+
9
+ 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.
7
10
 
8
- = apn_sender
11
+ == Yet another ApplePushNotification interface?
9
12
 
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, which doesn't seem too bad. But then you read in the {Apple Documentation}[https://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/WhatAreRemoteNotif/WhatAreRemoteNotif.html#//apple_ref/doc/uid/TP40008194-CH102-SW7] (<em>Apple Dev account required</em>) that Apple's servers may treat non-persistent connections as a Denial of Service attack, and things started looking more complicated.
13
+ 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.
11
14
 
12
- If this doesn't sound familiar, this gem probably isn't anything you need. If it does, though...
15
+ == Current Status
13
16
 
14
- 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 helper methods for enqueueing your jobs and a sample monit config to make sure the background worker is around when you need it.
17
+ This gem has been in production use since early May, 2010. There are no guarantees of complete functionality, of course, but it's working for us. :)
15
18
 
16
19
 
17
20
  == Usage
@@ -39,14 +42,17 @@ Once this is done, you can fire off a background worker with
39
42
 
40
43
  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:
41
44
 
42
- # To run standard daemon
43
- ./script/apn_sender
44
-
45
- # To pass in options
46
- ./script/apn_sender -- --cert-path=PATH_TO_NONSTANDARD_FOLDER_WITH_PEM_FILES --environment=production
45
+ # To generate daemon
46
+ ./script/generate apn_sender
47
+
48
+ # To run daemon. Pass --help to print all options
49
+ ./script/apn_sender --environment=production --verbose start
47
50
 
48
51
  Note the --environment must be explicitly set (separately from your <code>RAILS_ENV</code>) to production in order to send messages via the production APN servers. Any other environment sends messages through Apple's sandbox servers at <code>gateway.sandbox.push.apple.com</code>.
49
52
 
53
+ Check <code>RAILS_ROOT/logs/apn_sender.log</code> 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.
54
+
55
+
50
56
  === 3. Checking Apple's Feedback Service
51
57
 
52
58
  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.
@@ -71,7 +77,7 @@ If you're interested in knowing exactly <em>when</em> Apple determined each toke
71
77
  # ... custom logic here
72
78
  end
73
79
 
74
- The Feedback Service works as a big queue, and when you connect it pops off all its data and sends it over the wire. This means that connecting a second time will return an empty array. For ease of use, a call to either +tokens+ or +data+ will connect once and cache the data, so if you call either one again it'll continue to use its cached version rather than connecting to Apple a second time to retrieve an empty array.
80
+ The Feedback Service works as a big queue. When you connect it pops off all its data and sends it over the wire at once, which means connecting a second time will return an empty array, so for ease of use a call to either +tokens+ or +data+ will connect once and cache the data. If you call either one again it'll continue to use its cached version (rather than connecting to Apple a second time to retrieve an empty array, which is probably not what you want).
75
81
 
76
82
  Forcing a reconnect is as easy as calling either method with the single parameter +true+, but be sure you've already used the existing data because you'll never get it back.
77
83
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 1.0.0
data/apn_sender.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{apn_sender}
8
- s.version = "0.0.7"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kali Donovan"]
@@ -1,13 +1,21 @@
1
- # an example Monit configuration file for running the apple_push_notification background sender
1
+ # An example Monit configuration file for running the apn_sender background daemon
2
2
  #
3
- # To use:
4
- # 1. copy to /var/www/apps/{app_name}/shared/apn_sender.monitrc
5
- # 2. replace {app_name} as appropriate
6
- # 3. add this to your /etc/monit/monitrc
7
- #
8
- # include /var/www/apps/{app_name}/shared/apn_sender.monitrc
3
+ # 1. Replace #{app_name} with the path to your configuration
4
+ # 2. Add any arguments between apn_sender the and start/stop command
5
+ # 3. Install as a monitrc file
6
+
7
+ check process redis
8
+ with pidfile /tmp/redis.pid
9
+ group apn_sender
10
+ start program = "/usr/bin/redis-server /etc/redis.conf"
11
+ stop program = "/bin/echo SHUTDOWN | nc localhost 6379"
12
+ if failed host 127.0.0.1 port 6379 then restart
13
+ if 5 restarts within 5 cycles then timeout
14
+
9
15
 
10
16
  check process apn_sender
11
- with pidfile /var/www/apps/{app_name}/shared/pids/apn_sender.pid
12
- start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/apn_sender start"
13
- stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/apn_sender stop"
17
+ with pidfile /var/www/apps/#{app_name}/shared/pids/apn_sender.pid
18
+ group apn_sender
19
+ start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/apn_sender --environment=production start"
20
+ stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/apn_sender --environment=production stop"
21
+ depends_on redis
@@ -4,6 +4,8 @@ require 'resque'
4
4
 
5
5
  module APN
6
6
  module Connection
7
+ # APN::Connection::Base takes care of all the boring certificate loading, socket creating, and logging
8
+ # responsibilities so APN::Sender and APN::Feedback and focus on their respective specialties.
7
9
  module Base
8
10
  attr_accessor :opts, :logger
9
11
 
@@ -25,6 +27,7 @@ module APN
25
27
 
26
28
  protected
27
29
 
30
+ # Default to Rails or Merg logger, if available
28
31
  def setup_logger
29
32
  @logger = if defined?(Merb::Logger)
30
33
  Merb.logger
@@ -59,13 +59,14 @@ module APN
59
59
  # Extracts :alert, :badge, and :sound keys into the 'aps' hash, merges any other hash data
60
60
  # into the root of the hash to encode and send to apple.
61
61
  def packaged_message
62
+ opts = @options.clone # Don't destroy our pristine copy
62
63
  hsh = {'aps' => {}}
63
- hsh['aps']['alert'] = @options.delete(:alert).to_s if @options[:alert]
64
- hsh['aps']['badge'] = @options.delete(:badge).to_i if @options[:badge]
65
- if sound = @options.delete(:sound)
64
+ hsh['aps']['alert'] = opts.delete(:alert).to_s if opts[:alert]
65
+ hsh['aps']['badge'] = opts.delete(:badge).to_i if opts[:badge]
66
+ if sound = opts.delete(:sound)
66
67
  hsh['aps']['sound'] = sound.is_a?(TrueClass) ? 'default' : sound.to_s
67
68
  end
68
- hsh.merge!(@options)
69
+ hsh.merge!(opts)
69
70
  hsh.to_json
70
71
  end
71
72
 
@@ -9,7 +9,7 @@ module APN
9
9
  # Build a notification from arguments and send to Apple
10
10
  def self.perform(token, opts)
11
11
  msg = APN::Notification.new(token, opts)
12
- raise "Invalid notification options: #{opts.inspect}" unless msg.valid?
12
+ raise "Invalid notification options (did you provide :alert, :badge, or :sound?): #{opts.inspect}" unless msg.valid?
13
13
 
14
14
  worker.send_to_apple( msg )
15
15
  end
data/lib/apn/sender.rb CHANGED
@@ -54,23 +54,23 @@ __END__
54
54
  ## To enqueue test job
55
55
  k = 'ceecdc18 ef17b2d0 745475e0 0a6cd5bf 54534184 ac2649eb 40873c81 ae76dbe8'
56
56
  c = '0f58e3e2 77237b8f f8213851 c835dee0 376b7a31 9e0484f7 06fe3035 7c5dda2f'
57
- Resque.enqueue APN::NotificationJob, k, {:alert => 'Resque Test'}
58
57
  APN.notify k, 'Resque Test'
59
58
 
60
59
  # If you need to really force quit some screwed up workers
61
60
  Resque.workers.map{|w| Resque.redis.srem(:workers, w)}
62
61
 
63
- ## To run worker from rake task
62
+ # To run worker from rake task
64
63
  CERT_PATH=/Users/kali/Code/insurrection/certs/ ENVIRONMENT=production rake apn:work
65
64
 
66
- ## To run worker from IRB
65
+ # To run worker from IRB
67
66
  Resque.workers.map(&:unregister_worker)
68
67
  require 'ruby-debug'
69
68
  worker = APN::Sender.new(:cert_path => '/Users/kali/Code/insurrection/certs/', :environment => :production)
70
69
  worker.very_verbose = true
71
70
  worker.work(5)
72
71
 
73
- ## To run worker as daemon - NOT YET TESTED
74
- APN::SenderDaemon.new(ARGV).daemonize
72
+ # To run worker as daemon
73
+ args = ['--environment=production', '--cert-path=/Users/kali/Code/insurrection/certs/']
74
+ APN::SenderDaemon.new(args).daemonize
75
75
 
76
76
 
@@ -4,12 +4,17 @@ require 'daemons'
4
4
  require 'optparse'
5
5
 
6
6
  module APN
7
+ # A wrapper designed to daemonize an APN::Sender instance to keep in running in the background.
8
+ # Connects worker's output to the Rails logger, if available. Creates a pid file suitable for
9
+ # monitoring with {monit}[http://mmonit.com/monit/].
10
+ #
11
+ # Based off delayed_job's great example. To use in a Rails app, <code>script/generate apn_sender</code>.
7
12
  class SenderDaemon
8
13
 
9
14
  def initialize(args)
10
- @options = {:quiet => true, :worker_count => 1, :environment => :development}
15
+ @options = {:quiet => true, :worker_count => 1, :environment => :development, :delay => 5}
11
16
 
12
- opts = OptionParser.new do |opts|
17
+ optparse = OptionParser.new do |opts|
13
18
  opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
14
19
 
15
20
  opts.on('-h', '--help', 'Show this message') do
@@ -38,8 +43,7 @@ module APN
38
43
 
39
44
  # If no arguments, give help screen
40
45
  @args = optparse.parse!(args.empty? ? ['-h'] : args)
41
-
42
- puts "OPTS: #{@options.inspect}"
46
+ @options[:verbose] = true if @options[:very_verbose]
43
47
  end
44
48
 
45
49
  def daemonize
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: 0.0.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kali Donovan