Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to configure their own redis server bedsides localhost #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ APN Service Provider. More powerful than a push...

A quick look at [The Ruby Toolbox](https://www.ruby-toolbox.com/search?utf8=✓&q=apns) reveals a ton of pre-existing APNS gems. Why recreate the wheel?

We needed an APNS package for use with a many-tenant MDM platform. Specifically, we needed the ability to quickly push many notifications to devices spanning across *many* push certificates.
We needed an APNS package for use with a many-tenant MDM platform. Specifically, we needed the ability to quickly push many notifications to devices spanning across *many* push certificates.

### What about [arthurnn/apn_sender](https://github.com/arthurnn/apn_sender)?

Expand Down Expand Up @@ -73,6 +73,22 @@ Need it to be a sandbox notification?
payload: payload,
sandbox: true

### Run on target redis server

You can set redis server via config:

AppleShove::CONFIG[:redis_server] = REDIS # REDIS is your initialized redis connection

E.g. in Rails initializer with redistogo:

redis_url = URI.parse(ENV["REDISTOGO_URL"])
REDIS = Redis.new(host: redis_url.host, port: redis_url.port, password: redis_url.password)
AppleShove::CONFIG[:redis_server] = REDIS

E.g. Procfile in heroku

apn_push: bundle exec rake apple_shove:run config=config/initializers/redis_loader.rb

### Checking the Feedback Service

We also have a feedback mechanism in place:
Expand All @@ -95,7 +111,7 @@ We also have a feedback mechanism in place:
#### Optional Command Line Arguments

log_dir: specify an absolute path if you want to log
pid_dir: specify an absolute or relative path where the PID file
pid_dir: specify an absolute or relative path where the PID file
is to be stored. Defaults to the current directory.
connection_limit: maximum number of simultaneous connections to Apple
allowed.
Expand Down Expand Up @@ -124,7 +140,7 @@ Or install it yourself as:

### TCP Keep-Alives

AppleShove has the ability to maintain connections to Apple for long durations of time without sending a notification. These connections will generally stay open, however, intermediate NATs and firewalls may expire and close the connection prematurely.
AppleShove has the ability to maintain connections to Apple for long durations of time without sending a notification. These connections will generally stay open, however, intermediate NATs and firewalls may expire and close the connection prematurely.

To combat this, AppleShove enables keep-alive on all connections to Apple. AppleShove is not able to set the interval between keep-alives however, as this is generally managed by the operating system. If you are aware of a relatively short NAT or firewall timer, you can manually shorten your OS's keep-alive timer to be shorter than the timer. As this likely breaks the portability of your code, you can alternatively change the `AppleShove::CONFIG[:reconnect_timer]` to a value less than the NAT/firewall timer. This will force AppleShove to re-establish the SSL connection after enough idle time has passed.

Expand Down
11 changes: 4 additions & 7 deletions lib/apple_shove/apple_shove.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module AppleShove
def self.notify(params = {})
notification = Notification.new params

queue = NotificationQueue.new(CONFIG[:redis_key])
queue = NotificationQueue.new(CONFIG[:redis_key], CONFIG[:redis_server])
queue.add(notification)

true
Expand All @@ -16,13 +16,10 @@ def self.feedback_tokens(p12, sandbox = false)
end

def self.stats
redis = ::Redis.new
queue = NotificationQueue.new(CONFIG[:redis_key], redis)
queue = NotificationQueue.new(CONFIG[:redis_key], CONFIG[:redis_server])

size = queue.size

redis.quit

"queue size:\t#{size}"
end

Expand All @@ -31,5 +28,5 @@ def self.try_p12(p12)
OpenSSLHelper.pkcs12_from_pem(p12)
true
end
end

end
28 changes: 14 additions & 14 deletions lib/apple_shove/demultiplexer.rb
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
module AppleShove
class Demultiplexer

def initialize(opts = {})
unless opts[:max_apns_connections]
raise ArgumentError, 'max_apns_connections must be specified'
end

@max_connections = opts[:max_apns_connections].to_i

@connections = {}
@queue = NotificationQueue.new(CONFIG[:redis_key])
@queue = NotificationQueue.new(CONFIG[:redis_key], CONFIG[:redis_server])
end

def start

while true

if notification = @queue.get
conn = get_connection(notification)
conn.pending_notifications += 1
conn.async.send(notification)
else
sleep 1
end

end

end

private

def get_connection(notification)
key = APNS::NotifyConnection.generate_name(notification.p12, notification.sandbox)
connection = @connections[key]
connection = @connections[key]

unless connection
retire_oldest_connection if @connections.count >= @max_connections

connection = APNS::NotifyConnection.new notification.p12, notification.sandbox
@connections[key] = connection
Logger.info "created connection to APNS (#{@connections.count} total)", connection
Logger.info "created connection to APNS (#{@connections.count} total)", connection
end

connection
end

def retire_oldest_connection
if oldest = @connections.min_by { |_k, v| v.last_used }
key, conn = oldest[0], oldest[1]
conn_name = conn.name
@connections.delete key
conn.shutdown

Logger.info "destroyed connection to APNS (#{@connections.count} total)", conn_name
end
end

end
end
end
13 changes: 7 additions & 6 deletions lib/apple_shove/notification_queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@

module AppleShove
class NotificationQueue

def initialize(key, redis = Redis.new)

def initialize(key, redis = nil)
redis = Redis.new unless redis
@redis = redis
@key = key
end

def add(notification)
@redis.rpush @key, notification.to_json
end

def get
element = @redis.lpop @key
element ? Notification.parse(element) : nil
end

def size
@redis.llen @key
end
end

end
end
end
11 changes: 5 additions & 6 deletions lib/apple_shove/tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
include Rake::DSL

namespace :apple_shove do

desc 'Display service statistics every second'
task :stats do
require 'apple_shove'
Expand Down Expand Up @@ -39,18 +39,17 @@
exec "ruby #{path_to_daemon} status"
end

private
private

def path_to_daemon
File.join(File.dirname(__FILE__), '..', '..', 'script', 'daemon')
end

def argument_string
watched_args = ['log_dir', 'pid_dir', 'connection_limit']
watched_args = ['log_dir', 'pid_dir', 'connection_limit', 'config']
arg_str = watched_args.collect { |a| ENV[a] ? "--#{a}=#{ENV[a]}" : nil }.compact.join(' ')

arg_str.empty? ? nil : " -- #{arg_str}"
end


end
end
10 changes: 6 additions & 4 deletions script/daemon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

require 'daemons'
require 'apple_shove'

# process command line arguments

args = {}
Expand All @@ -13,7 +12,7 @@ ARGV.each do |arg|
end
end

options = {
options = {
stop_proc: Proc.new { puts "Stopping daemon" },
dir_mode: :script
}
Expand All @@ -25,14 +24,17 @@ if args[:log_dir]
options[:log_dir] = args[:log_dir]
end

Daemons.run_proc('apple_shove', options) do
require File.expand_path(args[:config], Dir.pwd) if args[:config]

Daemons.run_proc('apple_shove', options) do
# max of 15 connections recommended by Apple: http://bit.ly/YNHTfE
# note: this may be per-certificate, in which case we can crank this number
# up much higher.
conn_limit = args[:connection_limit] || 100

puts "Starting daemon with a APNS connection limit of #{conn_limit}"
puts AppleShove::CONFIG

dmp = AppleShove::Demultiplexer.new max_apns_connections: conn_limit
dmp.start
end
end