module APN
# Subclass of Resque::Worker which initializes a single TCP socket on creation to communicate with Apple's Push Notification servers.
# Shares this socket with each child process forked off by Resque to complete a job. Socket is closed in the before_unregister_worker
# callback, which gets called on normal or exceptional exits.
#
# End result: single persistent TCP connection to Apple, so they don't ban you for frequently opening and closing connections,
# which they apparently view as a DOS attack.
#
# Accepts :environment
(production vs anything else) and :cert_path
options on initialization. If called in a
# Rails context, will default to RAILS_ENV and RAILS_ROOT/config/certs. :environment will default to development.
# APN::Sender expects two files to exist in the specified :cert_path
directory:
# apn_production.pem
and apn_development.pem
.
#
# If a socket error is encountered, will teardown the connection and retry again twice before admitting defeat.
class Sender < ::Resque::Worker
include APN::Connection::Base
TIMES_TO_RETRY_SOCKET_ERROR = 2
# Send a raw string over the socket to Apple's servers (presumably already formatted by APN::Notification)
def send_to_apple( notification, attempt = 0 )
if attempt > TIMES_TO_RETRY_SOCKET_ERROR
log_and_die("Error with connection to #{apn_host} (retried #{TIMES_TO_RETRY_SOCKET_ERROR} times): #{error}")
end
self.socket.write( notification.to_s )
rescue SocketError => error
log(:error, "Error with connection to #{apn_host} (attempt #{attempt}): #{error}")
# Try reestablishing the connection
teardown_connection
setup_connection
send_to_apple(notification, attempt + 1)
end
protected
def apn_host
@apn_host ||= apn_production? ? "gateway.push.apple.com" : "gateway.sandbox.push.apple.com"
end
def apn_port
2195
end
end
end