apnserver 0.1.10 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -5,48 +5,54 @@ h1. Apple Push Notification Server Toolkit
5
5
  h2. Description
6
6
 
7
7
  apnserver is a server and set of command line programs to send push notifications to the iPhone.
8
- Apple recomends to maintain an open connection to the push notification service and refrain from
9
- opening up and tearing down SSL connections reapeated. To solve this problem an intermediate
10
- network server is introduced that queues are requests to the APN service and sends them via a
8
+ Apple recommends to maintain an open connection to the push notification service and refrain from
9
+ opening up and tearing down SSL connections repeated. To solve this problem an intermediate
10
+ network server is introduced that queues are requests to the APN service and sends them via a
11
11
  persistent connection.
12
12
 
13
13
  h2. Remaining Tasks
14
-
14
+
15
15
  * Implement feedback service mechanism
16
16
  * Implement robust notification sending in reactor periodic scheduler
17
-
17
+
18
18
  h2. Issues Fixed
19
19
 
20
20
  * second attempt at retry logic, SSL Errors close down sockets now
21
21
  * apnsend --badge option correctly sends integer number rather than string of number for aps json payload
22
22
  * connections are properly closed in Notification#push method now
23
- * better compatibility with Rails
23
+ * removed json gem in favor of ActiveSupport
24
+ * Rails 3.x support
25
+ * drop the erroneous puts statements in favor a configurable logger
26
+ * moved to Rspec
24
27
 
25
28
  h2. APN Server Daemon
26
-
29
+
27
30
  <pre>
28
31
  <code>
29
32
  Usage: apnserverd [options] --pem /path/to/pem
30
33
  --bind-address bind address (defaults to 0.0.0.0)
31
34
  bind address of the server daemon
32
-
33
- --proxy-port port
35
+
36
+ --proxy-port port
34
37
  the port that the daemon will listen on (defaults to 22195)
35
-
38
+
36
39
  --server server
37
40
  APN Server (defaults to gateway.push.apple.com)
38
-
41
+
39
42
  --port port of the APN Server
40
43
  APN server port (defaults to 2195)
41
-
44
+
42
45
  --pem pem file path
43
46
  The PEM encoded private key and certificate.
44
- To export a PEM ecoded file execute
47
+ To export a PEM ecoded file execute
45
48
  # openssl pkcs12 -in cert.p12 -out cert.pem -nodes -clcerts
46
-
49
+
50
+ --pem-passphrase passphrase
51
+ The PEM passphrase to decode key.
52
+
47
53
  --help
48
- usage message
49
-
54
+ usage message
55
+
50
56
  --daemon
51
57
  Runs process as daemon, not available on Windows
52
58
  </code>
@@ -54,7 +60,7 @@ Usage: apnserverd [options] --pem /path/to/pem
54
60
 
55
61
  h2. APN Server Client
56
62
 
57
- With the APN server client script you can send push notifications directly to
63
+ With the APN server client script you can send push notifications directly to
58
64
  Apple's APN server over an SSL connection or to the above daemon using a plain socket.
59
65
  To send a notification to Apple's APN server using SSL the *--pem* option must be used.
60
66
 
@@ -63,8 +69,9 @@ To send a notification to Apple's APN server using SSL the *--pem* option must b
63
69
  Usage: apnsend [switches] (--b64-token | --hex-token) <token>
64
70
  --server <localhost> the apn server defaults to a locally running apnserverd
65
71
  --port <2195> the port of the apn server
66
- --pem <path> the path to the pem file, if a pem is supplied the server
72
+ --pem <path> the path to the pem file, if a pem is supplied the server
67
73
  defaults to gateway.push.apple.com:2195
74
+ --pem-passphrase <passphrase> the pem passphrase
68
75
  --alert <message> the message to send"
69
76
  --sound <default> the sound to play, defaults to 'default'
70
77
  --badge <number> the badge number
@@ -94,6 +101,7 @@ To configure the client to send to the local apnserverd process configure the Ap
94
101
  # configured for a using the apnserverd proxy
95
102
  ApnServer::Config.host = 'localhost'
96
103
  ApnServer::Config.port = 22195
104
+ ApnServer::Config.logger = Rails.logger
97
105
  </code>
98
106
  </pre>
99
107
 
@@ -123,11 +131,11 @@ Finally within we can send a push notification using the following code
123
131
 
124
132
  h2. Installation
125
133
 
126
- To install apnserver execute the following gem command:
134
+ Apnserver is hosted on "rubygems":https://rubygems.org/gems/apnserver
127
135
 
128
136
  <pre>
129
137
  <code>
130
- $ gem install bpoweski-apnserver --source http://gems.github.com
138
+ $ gem install apnserver
131
139
  </code>
132
140
  </pre>
133
141
 
@@ -135,15 +143,16 @@ Adding apnserver to your Rails application
135
143
 
136
144
  <pre>
137
145
  <code>
138
- config.gem "bpoweski-apnserver", :lib => 'apnserver', :source => "http://gems.github.com"
146
+ gem 'apnserver', '>= 0.2.0
139
147
  </code>
140
148
  </pre>
141
149
 
150
+
142
151
  h2. License
143
152
 
144
153
  (The MIT License)
145
154
 
146
- Copyright (c) 2009 Ben Poweski
155
+ Copyright (c) 2011 Ben Poweski
147
156
 
148
157
  Permission is hereby granted, free of charge, to any person obtaining
149
158
  a copy of this software and associated documentation files (the
data/bin/apnsend CHANGED
@@ -13,12 +13,13 @@ def usage
13
13
  puts " --server <localhost> the apn server defaults to a locally running apnserverd"
14
14
  puts " --port <2195> the port of the apn server"
15
15
  puts " --pem <path> the path to the pem file, if a pem is supplied the server defaults to gateway.push.apple.com:2195"
16
+ puts " --pem-passphrase <passphrase> the pem passphrase"
16
17
  puts " --alert <message> the message to send"
17
18
  puts " --sound <default> the sound to play, defaults to 'default'"
18
19
  puts " --badge <number> the badge number"
19
- puts " --custom <json string> a custom json string to be added to the main object"
20
+ puts " --custom <json string> a custom json string to be added to the main object"
20
21
  puts " --b64-token <token> a base 64 encoded device token"
21
- puts " --hex-token <token> a hex encoded device token"
22
+ puts " --hex-token <token> a hex encoded device token"
22
23
  puts " --help this message"
23
24
  end
24
25
 
@@ -26,10 +27,11 @@ opts = GetoptLong.new(
26
27
  ["--server", "-s", GetoptLong::REQUIRED_ARGUMENT],
27
28
  ["--port", "-p", GetoptLong::REQUIRED_ARGUMENT],
28
29
  ["--pem", "-c", GetoptLong::REQUIRED_ARGUMENT],
30
+ ["--pem-passphrase", "-C", GetoptLong::REQUIRED_ARGUMENT],
29
31
  ["--alert", "-a", GetoptLong::REQUIRED_ARGUMENT],
30
32
  ["--sound", "-S", GetoptLong::REQUIRED_ARGUMENT],
31
33
  ["--badge", "-b", GetoptLong::REQUIRED_ARGUMENT],
32
- ["--custom", "-j", GetoptLong::REQUIRED_ARGUMENT],
34
+ ["--custom", "-j", GetoptLong::REQUIRED_ARGUMENT],
33
35
  ["--b64-token", "-B", GetoptLong::REQUIRED_ARGUMENT],
34
36
  ["--hex-token", "-H", GetoptLong::REQUIRED_ARGUMENT],
35
37
  ["--help", "-h", GetoptLong::NO_ARGUMENT]
@@ -48,6 +50,8 @@ opts.each do |opt, arg|
48
50
  ApnServer::Config.port = arg.to_i
49
51
  when '--pem'
50
52
  ApnServer::Config.pem = arg
53
+ when '--pem-passphrase'
54
+ ApnServer::Config.password = arg
51
55
  when '--alert'
52
56
  notification.alert = arg
53
57
  when '--sound'
@@ -55,16 +59,16 @@ opts.each do |opt, arg|
55
59
  when '--badge'
56
60
  notification.badge = arg.to_i
57
61
  when '--custom'
58
- notification.custom = arg
62
+ notification.custom = ActiveSupport::JSON.decode(arg)
59
63
  when '--b64-token'
60
64
  notification.device_token = Base64::decode64(arg)
61
65
  when '--hex-token'
62
- notification.device_token = arg.unpack('H*')
66
+ notification.device_token = arg.scan(/[0-9a-f][0-9a-f]/).map {|s| s.hex.chr}.join
63
67
  end
64
68
  end
65
69
 
66
70
  if notification.device_token.nil?
67
- usage
71
+ usage
68
72
  exit
69
73
  else
70
74
  notification.push
data/bin/apnserverd CHANGED
@@ -5,24 +5,28 @@ require 'getoptlong'
5
5
  require 'rubygems'
6
6
  require 'daemons'
7
7
  require 'apnserver'
8
+ require 'apnserver/server'
8
9
 
9
10
  def usage
10
11
  puts "Usage: apnserverd [switches] --pem <path>"
12
+ puts " --pem-passphrase <passphrase> pem passphrase"
11
13
  puts " --bind-address [0.0.0.0] bind address of proxy"
12
14
  puts " --proxy-port [22195] port proxy listens on"
13
15
  puts " --server <gateway.push.apple.com> the apn server to send messages to"
14
16
  puts " --port <2195> the port of the apn server"
17
+ puts " --pid </var/run/apnserverd.pid the path to store the pid"
18
+ puts " --log </var/log/apnserverd.log the path to store the log"
19
+ puts " --daemon to daemonize the server"
15
20
  puts " --help this message"
16
21
  end
17
22
 
18
23
  def daemonize
19
- Daemonize.daemonize('/var/log/apnserverd.log', 'apnserverd')
20
- @pid_file = '/var/run/apnserverd.pid'
21
- open(@pid_file,"w") {|f| f.write(Process.pid) } # copied from mongrel
24
+ Daemonize.daemonize(@log_file, 'apnserverd')
25
+ open(@pid_file,"w") { |f| f.write(Process.pid) }
22
26
  open(@pid_file,"w") do |f|
23
27
  f.write(Process.pid)
24
28
  File.chmod(0644, @pid_file)
25
- end
29
+ end
26
30
  end
27
31
 
28
32
  opts = GetoptLong.new(
@@ -30,7 +34,10 @@ opts = GetoptLong.new(
30
34
  ["--proxy-port", "-P", GetoptLong::REQUIRED_ARGUMENT],
31
35
  ["--server", "-s", GetoptLong::REQUIRED_ARGUMENT],
32
36
  ["--port", "-p", GetoptLong::REQUIRED_ARGUMENT],
37
+ ["--pid", "-i", GetoptLong::REQUIRED_ARGUMENT],
38
+ ["--log", "-l", GetoptLong::REQUIRED_ARGUMENT],
33
39
  ["--pem", "-c", GetoptLong::REQUIRED_ARGUMENT],
40
+ ["--pem-passphrase", "-C", GetoptLong::REQUIRED_ARGUMENT],
34
41
  ["--help", "-h", GetoptLong::NO_ARGUMENT],
35
42
  ["--daemon", "-d", GetoptLong::NO_ARGUMENT]
36
43
  )
@@ -40,22 +47,32 @@ proxy_port = 22195
40
47
  host = 'gateway.push.apple.com'
41
48
  port = 2195
42
49
  pem = nil
50
+ pem_passphrase = nil
51
+ @pid_file = '/var/run/apnserverd.pid'
52
+ @log_file = '/var/log/apnserverd.log'
43
53
  daemon = false
44
54
 
45
55
  opts.each do |opt, arg|
46
56
  case opt
47
57
  when '--help'
48
58
  usage
59
+ exit 1
49
60
  when '--bind-address'
50
61
  bind_address = arg
51
62
  when '--proxy-port'
52
63
  proxy_port = arg.to_i
53
64
  when '--server'
54
- host = arg
65
+ host = arg
55
66
  when '--port'
56
- port = arg.to_i
67
+ port = arg.to_i
68
+ when '--pid'
69
+ @pid_file = arg
70
+ when '--log'
71
+ @log_file = arg
57
72
  when '--pem'
58
73
  pem = arg
74
+ when '--pem-passphrase'
75
+ pem_passphrase = arg
59
76
  when '--daemon'
60
77
  daemon = true
61
78
  end
@@ -69,5 +86,6 @@ else
69
86
  server = ApnServer::Server.new(pem, bind_address, proxy_port)
70
87
  server.client.host = host
71
88
  server.client.port = port
89
+ server.client.password = pem_passphrase
72
90
  server.start!
73
91
  end
File without changes
@@ -0,0 +1,116 @@
1
+ #! /bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: apnserverd
4
+ # Required-Start: $remote_fs
5
+ # Required-Stop: $remote_fs
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: 0 1 6
8
+ # Short-Description: Apple Push Notification Server Daemon
9
+ ### END INIT INFO
10
+
11
+ # Author: Philipp Schmid <philipp.schmid@openresearch.com>
12
+
13
+ PATH=/sbin:/usr/sbin:/bin:/usr/bin
14
+ DESC="Apple Push Notification Server Daemon"
15
+ NAME=apnserverd
16
+ DAEMON=/usr/bin/$NAME
17
+ PEMPATH=""
18
+ DAEMON_ARGS="--daemon --pem $PEMPATH"
19
+ PIDFILE=/var/run/$NAME.pid
20
+ SCRIPTNAME=/etc/init.d/$NAME
21
+
22
+ # Exit if the package is not installed
23
+ [ -x "$DAEMON" ] || exit 0
24
+
25
+ # Load the VERBOSE setting and other rcS variables
26
+ . /lib/init/vars.sh
27
+
28
+ # Define LSB log_* functions.
29
+ # Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
30
+ . /lib/lsb/init-functions
31
+
32
+ #
33
+ # Function that starts the daemon/service
34
+ #
35
+ do_start()
36
+ {
37
+ # Return
38
+ # 0 if daemon has been started
39
+ # 1 if daemon was already running
40
+ # 2 if daemon could not be started
41
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
42
+ || return 1
43
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
44
+ $DAEMON_ARGS \
45
+ || return 2
46
+ }
47
+
48
+ #
49
+ # Function that stops the daemon/service
50
+ #
51
+ do_stop()
52
+ {
53
+ # Return
54
+ # 0 if daemon has been stopped
55
+ # 1 if daemon was already stopped
56
+ # 2 if daemon could not be stopped
57
+ # other if a failure occurred
58
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
59
+ RETVAL="$?"
60
+ [ "$RETVAL" = 2 ] && return 2
61
+ # Wait for children to finish too if this is a daemon that forks
62
+ # and if the daemon is only ever run from this initscript.
63
+ # If the above conditions are not satisfied then add some other code
64
+ # that waits for the process to drop all resources that could be
65
+ # needed by services started subsequently. A last resort is to
66
+ # sleep for some time.
67
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
68
+ [ "$?" = 2 ] && return 2
69
+ # Many daemons don't delete their pidfiles when they exit.
70
+ rm -f $PIDFILE
71
+ return "$RETVAL"
72
+ }
73
+
74
+
75
+ case "$1" in
76
+ start)
77
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
78
+ do_start
79
+ case "$?" in
80
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
81
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
82
+ esac
83
+ ;;
84
+ stop)
85
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
86
+ do_stop
87
+ case "$?" in
88
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
89
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
90
+ esac
91
+ ;;
92
+ restart|force-reload)
93
+ log_daemon_msg "Restarting $DESC" "$NAME"
94
+ do_stop
95
+ case "$?" in
96
+ 0|1)
97
+ do_start
98
+ case "$?" in
99
+ 0) log_end_msg 0 ;;
100
+ 1) log_end_msg 1 ;; # Old process is still running
101
+ *) log_end_msg 1 ;; # Failed to start
102
+ esac
103
+ ;;
104
+ *)
105
+ # Failed to stop
106
+ log_end_msg 1
107
+ ;;
108
+ esac
109
+ ;;
110
+ *)
111
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
112
+ exit 3
113
+ ;;
114
+ esac
115
+
116
+ :
data/lib/apnserver.rb CHANGED
@@ -1,8 +1,4 @@
1
1
  require 'logger'
2
- require 'eventmachine'
3
2
  require 'apnserver/payload'
4
3
  require 'apnserver/notification'
5
- require 'apnserver/protocol'
6
4
  require 'apnserver/client'
7
- require 'apnserver/server_connection'
8
- require 'apnserver/server'
@@ -3,40 +3,39 @@ require 'socket'
3
3
 
4
4
  module ApnServer
5
5
  class Client
6
-
7
6
  attr_accessor :pem, :host, :port, :password
8
-
7
+
9
8
  def initialize(pem, host = 'gateway.push.apple.com', port = 2195, pass = nil)
10
9
  @pem, @host, @port, @password = pem, host, port, pass
11
10
  end
12
-
11
+
13
12
  def connect!
14
13
  raise "The path to your pem file is not set." unless self.pem
15
14
  raise "The path to your pem file does not exist!" unless File.exist?(self.pem)
16
-
15
+
17
16
  @context = OpenSSL::SSL::SSLContext.new
18
17
  @context.cert = OpenSSL::X509::Certificate.new(File.read(self.pem))
19
18
  @context.key = OpenSSL::PKey::RSA.new(File.read(self.pem), self.password)
20
-
19
+
21
20
  @sock = TCPSocket.new(self.host, self.port.to_i)
22
21
  @ssl = OpenSSL::SSL::SSLSocket.new(@sock, @context)
23
22
  @ssl.connect
24
-
23
+
25
24
  return @sock, @ssl
26
25
  end
27
-
26
+
28
27
  def disconnect!
29
28
  @ssl.close
30
29
  @sock.close
31
30
  @ssl = nil
32
31
  @sock = nil
33
32
  end
34
-
33
+
35
34
  def write(notification)
36
- puts "#{Time.now} [#{host}:#{port}] Device: #{notification.device_token.unpack('H*')} sending #{notification.json_payload}"
35
+ Config.logger.debug "#{Time.now} [#{host}:#{port}] Device: #{notification.device_token.unpack('H*')} sending #{notification.json_payload}"
37
36
  @ssl.write(notification.to_bytes)
38
37
  end
39
-
38
+
40
39
  def connected?
41
40
  @ssl
42
41
  end