racoon 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 worker."
59
+ puts "Starting racoon firehose."
58
60
  end
59
- Racoon::Firehose.new.start!
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
@@ -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
- tube = 'racoon'
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
- Racoon::Worker.new(beanstalks, tube).start!
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
@@ -8,5 +8,5 @@ require 'racoon/worker'
8
8
  require 'racoon/firehose'
9
9
 
10
10
  module Racoon
11
- VERSION = "0.5.0"
11
+ VERSION = "0.6.0"
12
12
  end
@@ -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 'eventmachine'
9
- require 'ffi-rzmq'
8
+ require 'zmqmachine'
9
+ require 'yaml'
10
10
 
11
11
  module Racoon
12
12
  class Firehose
13
- def initialize(address = "tcp://*:11555", context = ZMQ::Context.new(1), &feedback_callback)
13
+ attr_accessor :connections, :feedback_callback
14
+
15
+ def initialize(reactor, address = ZM::Address.new('*', 11555, :tcp), &feedback_callback)
14
16
  @connections = {}
15
- @context = context
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 start!
22
- EventMachine::run do
23
- @firehose.bind(@address)
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
- apns(packet[:project], packet[:bytes])
49
- end
50
- end
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 = Racoon::APNS::Connection.new(project[:certificate], uri)
60
- @connections[hash] ||= { :connection => connection, :certificate => project[:certificate], :sandbox => project[:sandbox] }
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)
@@ -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 'yaml'
8
+ require 'zmqmachine'
11
9
 
12
10
  module Racoon
13
11
  class Worker
14
- def initialize(beanstalk_uris, tube = "racoon", address = "tcp://*:11555", context = ZMQ::Context.new(1))
15
- @beanstalk_uris = beanstalk_uris
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
- # First packet, send something silly, the firehose ignores it
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
- if @send_batch
29
- @send_batch = false
30
- @firehose.send_string("")
31
- end
18
+ def on_attach(socket)
19
+ @socket = socket
32
20
 
33
- EventMachine::PeriodicTimer.new(0.5) do
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
- private
48
-
49
- def beanstalk
50
- return @beanstalk if @beanstalk
51
- @beanstalk ||= Beanstalk::Pool.new(@beanstalk_uris)
52
- %w{use watch}.each { |s| @beanstalk.send(s, @tube) }
53
- @beanstalk.ignore('default')
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
- # Expects json ala:
58
- # json = {
59
- # "project":{
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.5.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: &70355758737600 !ruby/object:Gem::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: *70355758737600
25
+ version_requirements: *70102154456440
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: beanstalk-client
28
- requirement: &70355758737040 !ruby/object:Gem::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: *70355758737040
36
+ version_requirements: *70102154455980
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: ffi-rzmq
39
- requirement: &70355758736520 !ruby/object:Gem::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: *70355758736520
47
+ version_requirements: *70102154455520
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: bundler
50
- requirement: &70355758735740 !ruby/object:Gem::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: *70355758735740
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