class Pushr::Daemon::ApnsSupport::ConnectionApns

Constants

APN_ERRORS
ERROR_TUPLE_BYTES
IDLE_PERIOD
SELECT_TIMEOUT

Attributes

configuration[R]
last_write[RW]
name[R]

Public Class Methods

new(configuration, i = nil) click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 25
def initialize(configuration, i = nil)
  @configuration = configuration
  if i
    # Apns push connection
    @name = "#{@configuration.app}: ConnectionApns #{i}"
    @host = "gateway.#{configuration.sandbox ? 'sandbox.' : ''}push.apple.com"
    @port = 2195
  else
    @name = "#{@configuration.app}: FeedbackReceiver"
    @host = "feedback.#{configuration.sandbox ? 'sandbox.' : ''}push.apple.com"
    @port = 2196
  end
  written
end

Public Instance Methods

check_for_error(notification) click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 91
def check_for_error(notification)
  # check for true, because check_for_error can be nil
  return if @configuration.skip_check_for_error == true

  if select(SELECT_TIMEOUT)
    error = nil

    if tuple = read(ERROR_TUPLE_BYTES)
      _, code, notification_id = tuple.unpack('ccN')

      if code.to_i == 8
        Pushr::FeedbackApns.create(app: @configuration.app, device: notification.device, follow_up: 'delete',
                                   failed_at: Time.now)
        Pushr::Daemon.logger.info("[#{@name}] Invalid device (error 8), feedback sent, message delivery failed"\
                                  " to #{notification.to_json}")
      else
        description = APN_ERRORS[code.to_i] || 'Unknown error. Possible push bug?'
        error = Pushr::Daemon::DeliveryError.new(code, notification, description, 'APNS')
      end
    else
      error = DisconnectionError.new
    end

    begin
      Pushr::Daemon.logger.error("[#{@name}] Error received, reconnecting...")
      reconnect
    ensure
      fail error if error
    end
  end
end
close() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 47
def close
  @ssl_socket.close if @ssl_socket
  @tcp_socket.close if @tcp_socket
rescue IOError
end
connect() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 40
def connect
  @ssl_context = setup_ssl_context
  @tcp_socket, @ssl_socket = connect_socket
rescue
  Pushr::Daemon.logger.error("#{@name}] Error connection to server, invalid certificate?")
end
read(num_bytes) click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 53
def read(num_bytes)
  @ssl_socket ? @ssl_socket.read(num_bytes) : false
end
reconnect() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 86
def reconnect
  close
  @tcp_socket, @ssl_socket = connect_socket
end
select(timeout) click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 57
def select(timeout)
  IO.select([@ssl_socket], nil, nil, timeout)
end
write(data) click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 61
def write(data)
  reconnect_idle if idle_period_exceeded?

  retry_count = 0

  begin
    write_data(data)
  rescue Errno::EPIPE, Errno::ETIMEDOUT, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
    retry_count += 1

    if retry_count == 1
      Pushr::Daemon.logger.error("[#{@name}] Lost connection to #{@host}:#{@port} (#{e.class.name}), reconnecting...")
    end

    if retry_count <= 3
      reconnect
      sleep 1
      retry
    else
      raise ConnectionError, "#{@name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name})."
    end
  end
  check_for_error(data)
end

Protected Instance Methods

connect_socket() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 151
def connect_socket
  tcp_socket = TCPSocket.new(@host, @port)
  tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
  tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
  ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
  ssl_socket.sync = true
  ssl_socket.connect
  Pushr::Daemon.logger.info("[#{@name}] Connected to #{@host}:#{@port}")
  [tcp_socket, ssl_socket]
end
idle_period_exceeded?() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 130
def idle_period_exceeded?
  Time.now - last_write > IDLE_PERIOD
end
reconnect_idle() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 125
def reconnect_idle
  Pushr::Daemon.logger.info("[#{@name}] Idle period exceeded, reconnecting...")
  reconnect
end
setup_ssl_context() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 144
def setup_ssl_context
  ssl_context = OpenSSL::SSL::SSLContext.new
  ssl_context.key = OpenSSL::PKey::RSA.new(configuration.certificate, configuration.certificate_password)
  ssl_context.cert = OpenSSL::X509::Certificate.new(configuration.certificate)
  ssl_context
end
write_data(data) click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 134
def write_data(data)
  @ssl_socket.write(data.to_message)
  @ssl_socket.flush
  written
end
written() click to toggle source
# File lib/pushr/daemon/apns_support/connection_apns.rb, line 140
def written
  self.last_write = Time.now
end