Skip to content

Commit

Permalink
Broke out connections into a new module that opens and closes connect…
Browse files Browse the repository at this point in the history
…ions easily.
  • Loading branch information
markbates committed Jul 30, 2009
1 parent 1a9e4a3 commit e6b7afe
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require 'gemstub'
Gemstub.test_framework = :rspec

Gemstub.gem_spec do |s|
s.version = "0.2.1"
s.version = "0.2.2"
s.rubyforge_project = "magrathea"
s.add_dependency('configatron')
s.email = 'mark@markbates.com'
Expand Down
4 changes: 2 additions & 2 deletions apn_on_rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

Gem::Specification.new do |s|
s.name = %q{apn_on_rails}
s.version = "0.2.1.20090730113724"
s.version = "0.2.2.20090730143010"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["markbates"]
s.date = %q{2009-07-30}
s.description = %q{apn_on_rails was developed by: markbates}
s.email = %q{mark@markbates.com}
s.extra_rdoc_files = ["README", "LICENSE"]
s.files = ["lib/apn_on_rails/apn_on_rails.rb", "lib/apn_on_rails/app/models/apn/device.rb", "lib/apn_on_rails/app/models/apn/notification.rb", "lib/apn_on_rails/tasks/apn.rake", "lib/apn_on_rails/tasks/db.rake", "lib/apn_on_rails.rb", "lib/apn_on_rails_tasks.rb", "README", "LICENSE", "generators/apn_migrations_generator.rb", "generators/templates/apn_migrations/001_create_apn_devices.rb", "generators/templates/apn_migrations/002_create_apn_notifications.rb"]
s.files = ["lib/apn_on_rails/apn_on_rails.rb", "lib/apn_on_rails/app/models/apn/connection.rb", "lib/apn_on_rails/app/models/apn/device.rb", "lib/apn_on_rails/app/models/apn/notification.rb", "lib/apn_on_rails/tasks/apn.rake", "lib/apn_on_rails/tasks/db.rake", "lib/apn_on_rails.rb", "lib/apn_on_rails_tasks.rb", "README", "LICENSE", "generators/apn_migrations_generator.rb", "generators/templates/apn_migrations/001_create_apn_devices.rb", "generators/templates/apn_migrations/002_create_apn_notifications.rb"]
s.homepage = %q{http://www.metabates.com}
s.require_paths = ["lib"]
s.rubyforge_project = %q{magrathea}
Expand Down
14 changes: 12 additions & 2 deletions lib/apn_on_rails/apn_on_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@

configatron.apn.set_default(:passphrase, '')
configatron.apn.set_default(:port, 2195)
configatron.apn.set_default(:host, 'gateway.sandbox.push.apple.com')
configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_development.pem'))

configatron.apn.feedback.set_default(:passphrase, configatron.apn.passphrase)
configatron.apn.feedback.set_default(:port, 2196)

if rails_env == 'production'
configatron.apn.set_default(:host, 'gateway.push.apple.com')
configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_production.pem'))

configatron.apn.feedback.set_default(:host, 'feedback.push.apple.com')
configatron.apn.feedback.set_default(:cert, configatron.apn.cert)
else
configatron.apn.set_default(:host, 'gateway.sandbox.push.apple.com')
configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_development.pem'))

configatron.apn.feedback.set_default(:host, 'feedback.sandbox.push.apple.com')
configatron.apn.feedback.set_default(:cert, configatron.apn.cert)
end

module APN # :nodoc:
Expand Down
65 changes: 65 additions & 0 deletions lib/apn_on_rails/app/models/apn/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module APN
module Connection

class << self

# Yields up an SSL socket to write notifications to.
# The connections are close automatically.
#
# Example:
# APN::Configuration.open_for_delivery do |conn|
# conn.write('my cool notification')
# end
#
# Configuration parameters are:
#
# configatron.apn.passphrase = ''
# configatron.apn.port = 2195
# configatron.apn.host = 'gateway.sandbox.push.apple.com' # Development
# configatron.apn.host = 'gateway.push.apple.com' # Production
# configatron.apn.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
# configatron.apn.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
def open_for_delivery(options = {}, &block)
open(options, &block)
end

# Yields up an SSL socket to receive feedback from.
# The connections are close automatically.
# Configuration parameters are:
#
# configatron.apn.feedback.passphrase = ''
# configatron.apn.feedback.port = 2196
# configatron.apn.feedback.host = 'feedback.sandbox.push.apple.com' # Development
# configatron.apn.feedback.host = 'feedback.push.apple.com' # Production
# configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
# configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
def open_for_feedback(options = {}, &block) # :nodoc:

end

private
def open(options = {}, &block) # :nodoc:
options = {:cert => configatron.apn.cert,
:passphrase => configatron.apn.passphrase,
:host => configatron.apn.host,
:port => configatron.apn.port}.merge(options)
cert = File.read(options[:cert])
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new(cert, options[:passphrase])
ctx.cert = OpenSSL::X509::Certificate.new(cert)

s = TCPSocket.new(options[:host], options[:port])
ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
ssl.sync = true
ssl.connect

yield ssl if block_given?

ssl.close
s.close
end

end

end # Connection
end # APN
26 changes: 8 additions & 18 deletions lib/apn_on_rails/app/models/apn/notification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,15 @@ class << self
# so as to not be sent again.
def send_notifications(notifications = APN::Notification.all(:conditions => {:sent_at => nil}))
unless notifications.nil? || notifications.empty?
logger.info "APN: Attempting to deliver #{pluralize(notifications.size, 'notification')}."
cert = File.read(configatron.apn.cert)
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new(cert, configatron.apn.passphrase)
ctx.cert = OpenSSL::X509::Certificate.new(cert)

s = TCPSocket.new(configatron.apn.host, configatron.apn.port)
ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
ssl.sync = true
ssl.connect

notifications.each do |noty|
ssl.write(noty.message_for_sending)
noty.sent_at = Time.now
noty.save

APN::Connection.open_for_delivery do |conn|
notifications.each do |noty|
conn.write(noty.message_for_sending)
noty.sent_at = Time.now
noty.save
end
end

ssl.close
s.close

end
end

Expand Down
40 changes: 40 additions & 0 deletions spec/apn_on_rails/app/models/apn/connection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper.rb')

describe APN::Connection do

describe 'open_for_delivery' do

it 'should create a connection to Apple, yield it, and then close' do
rsa_mock = mock('rsa_mock')
OpenSSL::PKey::RSA.should_receive(:new).with(apn_cert, '').and_return(rsa_mock)

cert_mock = mock('cert_mock')
OpenSSL::X509::Certificate.should_receive(:new).and_return(cert_mock)

ctx_mock = mock('ctx_mock')
ctx_mock.should_receive(:key=).with(rsa_mock)
ctx_mock.should_receive(:cert=).with(cert_mock)
OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ctx_mock)

tcp_mock = mock('tcp_mock')
tcp_mock.should_receive(:close)
TCPSocket.should_receive(:new).with('gateway.sandbox.push.apple.com', 2195).and_return(tcp_mock)

ssl_mock = mock('ssl_mock')
ssl_mock.should_receive(:sync=).with(true)
ssl_mock.should_receive(:connect)
ssl_mock.should_receive(:write).with('message-0')
ssl_mock.should_receive(:write).with('message-1')
ssl_mock.should_receive(:close)
OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_mock, ctx_mock).and_return(ssl_mock)

APN::Connection.open_for_delivery do |conn|
conn.write('message-0')
conn.write('message-1')
end

end

end

end
21 changes: 1 addition & 20 deletions spec/apn_on_rails/app/models/apn/notification_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,29 +67,10 @@
notify.should_receive(:save)
end

rsa_mock = mock('rsa_mock')
OpenSSL::PKey::RSA.should_receive(:new).with(apn_cert, '').and_return(rsa_mock)

cert_mock = mock('cert_mock')
OpenSSL::X509::Certificate.should_receive(:new).and_return(cert_mock)

ctx_mock = mock('ctx_mock')
ctx_mock.should_receive(:key=).with(rsa_mock)
ctx_mock.should_receive(:cert=).with(cert_mock)
OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ctx_mock)

# TCPSocket.new(configatron.apn.host, configatron.apn.port)
tcp_mock = mock('tcp_mock')
tcp_mock.should_receive(:close)
TCPSocket.should_receive(:new).with('gateway.sandbox.push.apple.com', 2195).and_return(tcp_mock)

ssl_mock = mock('ssl_mock')
ssl_mock.should_receive(:sync=).with(true)
ssl_mock.should_receive(:connect)
ssl_mock.should_receive(:write).with('message-0')
ssl_mock.should_receive(:write).with('message-1')
ssl_mock.should_receive(:close)
OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_mock, ctx_mock).and_return(ssl_mock)
APN::Connection.should_receive(:open_for_delivery).and_yield(ssl_mock)

APN::Notification.send_notifications(notifications)

Expand Down

0 comments on commit e6b7afe

Please sign in to comment.