racoon 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.mdown +7 -1
- data/bin/racoon-firehose +26 -2
- data/bin/racoon-worker +56 -5
- data/lib/racoon.rb +1 -1
- data/lib/racoon/firehose.rb +19 -36
- data/lib/racoon/worker.rb +17 -55
- metadata +9 -20
data/README.mdown
CHANGED
@@ -8,7 +8,7 @@ has since taken on a different path. How does it differ from apnserver? By a few
|
|
8
8
|
3. Expects certificates as strings instead of paths to files;
|
9
9
|
4. Does not assume there is only one certificate (read: supports multiple projects); and
|
10
10
|
5. Receives packets containing notifications from beanstalkd instead of a listening socket;
|
11
|
-
6. Operates on a distributed architecture (many parallel workers, one firehose.
|
11
|
+
6. Operates on a distributed architecture (many parallel workers, one firehose).
|
12
12
|
|
13
13
|
The above changes were made because of the need to replace an existing APNs provider with something
|
14
14
|
more robust, and better suited to scaling upwards. This APNs provider had a couple requirements:
|
@@ -20,6 +20,12 @@ more robust, and better suited to scaling upwards. This APNs provider had a coup
|
|
20
20
|
It should be noted that the development of this project is independent of the work bpoweski
|
21
21
|
is doing on apnserver. If you're looking for that project, [go here](https://github.com/bpoweski/apnserver).
|
22
22
|
|
23
|
+
## BIT FAT DEPENDENCY WARNING
|
24
|
+
|
25
|
+
Since version 0.6.0, Racoon uses ZMQMachine. However, the mainline ZMQMachine does not support PUSH/PULL
|
26
|
+
sockets as of the time I wrote this (probably still the same way, else I'd have removed this by now).
|
27
|
+
As such, I strongly urge you to [fork this repository](https://github.com/jeremytregunna/zmqmachine) and install it before installing racoon.
|
28
|
+
|
23
29
|
## Description
|
24
30
|
|
25
31
|
Racoon consists of a firehose, which maintains the connections to Apple's APNs service. It also
|
data/bin/racoon-firehose
CHANGED
@@ -6,6 +6,8 @@ require 'rubygems'
|
|
6
6
|
require 'daemons'
|
7
7
|
require 'racoon'
|
8
8
|
require 'csv'
|
9
|
+
require 'yaml'
|
10
|
+
require 'zmqmachine'
|
9
11
|
|
10
12
|
def usage
|
11
13
|
puts "Usage: racoon-firehose [switches]"
|
@@ -54,6 +56,28 @@ Racoon::Config.logger = Logger.new(@log_file)
|
|
54
56
|
if daemon
|
55
57
|
daemonize
|
56
58
|
else
|
57
|
-
puts "Starting racoon
|
59
|
+
puts "Starting racoon firehose."
|
58
60
|
end
|
59
|
-
|
61
|
+
|
62
|
+
ZM::Reactor.new(:firehose).run do |context|
|
63
|
+
firehose = Racoon::Firehose.new(context) do |feedback_record|
|
64
|
+
Racoon::Config.logger.info "[Feedback] Time: #{feedback_record[:feedback_at]} Device: #{feedback_record[:device_token]} Length: #{feedback_record[:length]}"
|
65
|
+
end
|
66
|
+
context.pull_socket(firehose)
|
67
|
+
|
68
|
+
context.periodical_timer(28800) do
|
69
|
+
firehose.connections.each_pair do |key, data|
|
70
|
+
begin
|
71
|
+
project = data[:project]
|
72
|
+
uri = "gateway.#{project[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
73
|
+
feedback = Racoon::APNS::FeedbackConnection.new(data[:certificate], uri)
|
74
|
+
feedback.connect!
|
75
|
+
feedback.read.each do |record|
|
76
|
+
@feedback_callback.call(record) if @feedback_callback
|
77
|
+
end
|
78
|
+
rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
|
79
|
+
feedback.disconnect!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end.join
|
data/bin/racoon-worker
CHANGED
@@ -6,11 +6,15 @@ require 'rubygems'
|
|
6
6
|
require 'daemons'
|
7
7
|
require 'racoon'
|
8
8
|
require 'csv'
|
9
|
+
require 'yaml'
|
10
|
+
require 'zmqmachine'
|
11
|
+
require 'beanstalk-client'
|
9
12
|
|
10
13
|
def usage
|
11
14
|
puts "Usage: racoon-worker [switches]"
|
12
15
|
puts " --beanstalk <127.0.0.1:11300> csv list of ip:port for beanstalk servers"
|
13
16
|
puts " --tube <racoon> the beanstalk tube to use"
|
17
|
+
puts " --firehose <127.0.0.1:11555> the address of the firehose"
|
14
18
|
puts " --pid </var/run/racoon-worker.pid> the path to store the pid"
|
15
19
|
puts " --log </var/log/racoon-worker.log> the path to store the log"
|
16
20
|
puts " --daemon to daemonize the server"
|
@@ -29,14 +33,16 @@ end
|
|
29
33
|
opts = GetoptLong.new(
|
30
34
|
["--beanstalk", "-b", GetoptLong::REQUIRED_ARGUMENT],
|
31
35
|
["--tube", "-t", GetoptLong::REQUIRED_ARGUMENT],
|
36
|
+
["--firehose", "-f", GetoptLong::REQUIRED_ARGUMENT],
|
32
37
|
["--pid", "-i", GetoptLong::REQUIRED_ARGUMENT],
|
33
38
|
["--log", "-l", GetoptLong::REQUIRED_ARGUMENT],
|
34
39
|
["--help", "-h", GetoptLong::NO_ARGUMENT],
|
35
40
|
["--daemon", "-d", GetoptLong::NO_ARGUMENT]
|
36
41
|
)
|
37
42
|
|
38
|
-
beanstalks = ["127.0.0.1:11300"]
|
39
|
-
|
43
|
+
@beanstalks = ["127.0.0.1:11300"]
|
44
|
+
firehose_address = "127.0.0.1:11555".split(":")
|
45
|
+
@tube = 'racoon'
|
40
46
|
@pid_file = '/var/run/racoon-worker.pid'
|
41
47
|
@log_file = '/var/log/racoon-worker.log'
|
42
48
|
daemon = false
|
@@ -47,9 +53,11 @@ opts.each do |opt, arg|
|
|
47
53
|
usage
|
48
54
|
exit 1
|
49
55
|
when '--beanstalk'
|
50
|
-
beanstalks = CSV.parse(arg)[0]
|
56
|
+
@beanstalks = CSV.parse(arg)[0]
|
51
57
|
when '--tube'
|
52
|
-
tube = arg
|
58
|
+
@tube = arg
|
59
|
+
when '--firehose'
|
60
|
+
firehose_address = arg.split(":")
|
53
61
|
when '--pid'
|
54
62
|
@pid_file = arg
|
55
63
|
when '--log'
|
@@ -61,9 +69,52 @@ end
|
|
61
69
|
|
62
70
|
Racoon::Config.logger = Logger.new(@log_file)
|
63
71
|
|
72
|
+
def beanstalk
|
73
|
+
return @beanstalk if @beanstalk
|
74
|
+
@beanstalk = Beanstalk::Pool.new(@beanstalks)
|
75
|
+
%w{use watch}.each { |s| @beanstalk.send(s, @tube) }
|
76
|
+
@beanstalk.ignore('default')
|
77
|
+
@beanstalk
|
78
|
+
end
|
79
|
+
|
80
|
+
# Expects json ala:
|
81
|
+
# json = {
|
82
|
+
# "project":{
|
83
|
+
# "name":"foobar",
|
84
|
+
# "certificate":"...",
|
85
|
+
# "sandbox":false
|
86
|
+
# },
|
87
|
+
# "bytes":"..."
|
88
|
+
# }
|
89
|
+
def process(job, worker)
|
90
|
+
packet = job.ybody
|
91
|
+
project = packet[:project]
|
92
|
+
|
93
|
+
notification = Racoon::Notification.create_from_packet(packet)
|
94
|
+
|
95
|
+
data = { :project => project, :bytes => notification.to_bytes }
|
96
|
+
worker.send_message(YAML::dump(data))
|
97
|
+
end
|
98
|
+
|
64
99
|
if daemon
|
65
100
|
daemonize
|
66
101
|
else
|
67
102
|
puts "Starting racoon worker."
|
68
103
|
end
|
69
|
-
|
104
|
+
|
105
|
+
ZM::Reactor.new(:worker).run do |context|
|
106
|
+
worker = Racoon::Worker.new(context, ZM::Address.new(firehose_address[0], firehose_address[1], :tcp))
|
107
|
+
context.push_socket(worker)
|
108
|
+
|
109
|
+
context.periodical_timer(0.1) do
|
110
|
+
begin
|
111
|
+
if beanstalk.peek_ready
|
112
|
+
job = beanstalk.reserve(1)
|
113
|
+
process(job, worker)
|
114
|
+
job.delete
|
115
|
+
end
|
116
|
+
rescue Beanstalk::TimedOut
|
117
|
+
Racoon::Config.logger.info "[Beanstalk] Unable to secure job, timed out."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end.join
|
data/lib/racoon.rb
CHANGED
data/lib/racoon/firehose.rb
CHANGED
@@ -5,59 +5,42 @@
|
|
5
5
|
# connections to Apple, and sending data over the right ones.
|
6
6
|
|
7
7
|
require 'digest/sha1'
|
8
|
-
require '
|
9
|
-
require '
|
8
|
+
require 'zmqmachine'
|
9
|
+
require 'yaml'
|
10
10
|
|
11
11
|
module Racoon
|
12
12
|
class Firehose
|
13
|
-
|
13
|
+
attr_accessor :connections, :feedback_callback
|
14
|
+
|
15
|
+
def initialize(reactor, address = ZM::Address.new('*', 11555, :tcp), &feedback_callback)
|
14
16
|
@connections = {}
|
15
|
-
@
|
16
|
-
@firehose = context.socket(ZMQ::PULL)
|
17
|
+
@reactor = reactor
|
17
18
|
@address = address
|
18
19
|
@feedback_callback = feedback_callback
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
EventMachine::PeriodicTimer.new(28800) do
|
26
|
-
@connections.each_pair do |key, data|
|
27
|
-
begin
|
28
|
-
uri = "gateway.#{project[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
29
|
-
feedback = Racoon::APNS::FeedbackConnection.new(data[:certificate], uri)
|
30
|
-
feedback.connect!
|
31
|
-
feedback.read.each do |record|
|
32
|
-
@feedback_callback.call(record) if @feedback_callback
|
33
|
-
end
|
34
|
-
rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
|
35
|
-
feedback.disconnect!
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
EventMachine::PeriodicTimer.new(0.1) do
|
41
|
-
received_message = ZMQ::Message.new
|
42
|
-
@firehose.recv(received_message, ZMQ::NOBLOCK)
|
43
|
-
yaml_string = received_message.copy_out_string
|
44
|
-
|
45
|
-
if yaml_string and yaml_string != ""
|
46
|
-
packet = YAML::load(yaml_string)
|
22
|
+
def on_attach(socket)
|
23
|
+
socket.bind(@address)
|
24
|
+
end
|
47
25
|
|
48
|
-
|
49
|
-
|
50
|
-
|
26
|
+
def on_readable(socket, messages)
|
27
|
+
messages.each do |message|
|
28
|
+
packet = YAML::load(message.copy_out_string)
|
29
|
+
apns(packet[:project], packet[:bytes])
|
51
30
|
end
|
52
31
|
end
|
53
32
|
|
33
|
+
private
|
34
|
+
|
54
35
|
def apns(project, bytes, retries=2)
|
55
36
|
uri = "gateway.#{project[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
56
37
|
hash = Digest::SHA1.hexdigest("#{project[:name]}-#{project[:certificate]}")
|
57
38
|
|
58
39
|
begin
|
59
|
-
connection
|
60
|
-
|
40
|
+
@connections[hash] ||= { :connection => Racoon::APNS::Connection.new(project[:certificate], uri),
|
41
|
+
:certificate => project[:certificate],
|
42
|
+
:sandbox => project[:sandbox] }
|
43
|
+
connection = @connections[hash][:connection]
|
61
44
|
|
62
45
|
connection.connect! unless connection.connected?
|
63
46
|
connection.write(bytes)
|
data/lib/racoon/worker.rb
CHANGED
@@ -4,73 +4,35 @@
|
|
4
4
|
# This module contains the worker which processes notifications before sending them off
|
5
5
|
# down to the firehose.
|
6
6
|
|
7
|
-
require 'beanstalk-client'
|
8
|
-
require 'eventmachine'
|
9
7
|
require 'ffi-rzmq'
|
10
|
-
require '
|
8
|
+
require 'zmqmachine'
|
11
9
|
|
12
10
|
module Racoon
|
13
11
|
class Worker
|
14
|
-
def initialize(
|
15
|
-
@
|
16
|
-
@context = context
|
17
|
-
@firehose = context.socket(ZMQ::PUSH)
|
18
|
-
@tube = tube
|
12
|
+
def initialize(reactor, address)
|
13
|
+
@reactor = reactor
|
19
14
|
@address = address
|
20
|
-
|
21
|
-
@send_batch = true
|
15
|
+
@send_queue = []
|
22
16
|
end
|
23
|
-
|
24
|
-
def start!
|
25
|
-
EventMachine::run do
|
26
|
-
@firehose.connect(@address)
|
27
17
|
|
28
|
-
|
29
|
-
|
30
|
-
@firehose.send_string("")
|
31
|
-
end
|
18
|
+
def on_attach(socket)
|
19
|
+
@socket = socket
|
32
20
|
|
33
|
-
|
34
|
-
begin
|
35
|
-
if beanstalk.peek_ready
|
36
|
-
job = beanstalk.reserve(1)
|
37
|
-
process job
|
38
|
-
job.delete
|
39
|
-
end
|
40
|
-
rescue Beanstalk::TimedOut
|
41
|
-
Config.logger.info "[Beanstalk] Unable to secure job, operation timed out."
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
21
|
+
socket.connect(@address.to_s)
|
45
22
|
end
|
46
23
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
@beanstalk
|
24
|
+
def on_writable(socket)
|
25
|
+
unless @send_queue.empty?
|
26
|
+
message = @send_queue.shift
|
27
|
+
socket.send_message_string(message)
|
28
|
+
else
|
29
|
+
@reactor.deregister_writable(socket)
|
30
|
+
end
|
55
31
|
end
|
56
32
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
# "name":"foobar",
|
61
|
-
# "certificate":"...",
|
62
|
-
# "sandbox":false
|
63
|
-
# },
|
64
|
-
# "bytes":"..."
|
65
|
-
# }
|
66
|
-
def process(job)
|
67
|
-
packet = job.ybody
|
68
|
-
project = packet[:project]
|
69
|
-
|
70
|
-
notification = Notification.create_from_packet(packet)
|
71
|
-
|
72
|
-
data = { :project => project, :bytes => notification.to_bytes }
|
73
|
-
@firehose.send_string(YAML::dump(data))
|
33
|
+
def send_message(message)
|
34
|
+
@send_queue.push(message)
|
35
|
+
@reactor.register_writable(@socket)
|
74
36
|
end
|
75
37
|
end
|
76
38
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: racoon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -14,7 +14,7 @@ default_executable:
|
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: yajl-ruby
|
17
|
-
requirement: &
|
17
|
+
requirement: &70102154456440 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 0.7.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70102154456440
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: beanstalk-client
|
28
|
-
requirement: &
|
28
|
+
requirement: &70102154455980 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 1.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70102154455980
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: ffi-rzmq
|
39
|
-
requirement: &
|
39
|
+
requirement: &70102154455520 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 0.8.0
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70102154455520
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: bundler
|
50
|
-
requirement: &
|
50
|
+
requirement: &70102154455060 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ~>
|
@@ -55,18 +55,7 @@ dependencies:
|
|
55
55
|
version: 1.0.0
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
59
|
-
- !ruby/object:Gem::Dependency
|
60
|
-
name: eventmachine
|
61
|
-
requirement: &70355758734300 !ruby/object:Gem::Requirement
|
62
|
-
none: false
|
63
|
-
requirements:
|
64
|
-
- - ! '>='
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: 0.12.8
|
67
|
-
type: :development
|
68
|
-
prerelease: false
|
69
|
-
version_requirements: *70355758734300
|
58
|
+
version_requirements: *70102154455060
|
70
59
|
description: A distributed Apple Push Notification Service (APNs) provider developed
|
71
60
|
for hosting multiple projects.
|
72
61
|
email: jeremy.tregunna@me.com
|