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

Add some stuff to prevent connection errors #88

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
46 changes: 34 additions & 12 deletions apns.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@

from binascii import a2b_hex, b2a_hex
from datetime import datetime
from socket import socket, timeout, AF_INET, SOCK_STREAM
from socket import socket, timeout
from socket import error as socket_error
from socket import AF_INET, SOCK_STREAM, SOL_SOCKET, IPPROTO_TCP, SO_KEEPALIVE

import os

if os.uname()[0] == u'Darwin':
pass
else:
from socket import TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT

from struct import pack, unpack
import sys
import ssl
Expand Down Expand Up @@ -139,7 +148,7 @@ def unpacked_uint_big_endian(bytes):
Returns an unsigned int from a packed big-endian (network) byte array
"""
return unpack('>I', bytes)[0]

@staticmethod
def unpacked_char_big_endian(bytes):
"""
Expand Down Expand Up @@ -194,7 +203,20 @@ def _connect(self):
try:
self._socket = socket(AF_INET, SOCK_STREAM)
self._socket.settimeout(self.timeout)

# 'keep alive' for OS X
if os.uname()[0] == u'Darwin':
TCP_KEEPALIVE = 0x10
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self._socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPALIVE, 3)
else: # and others
self._socket.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1)
self._socket.setsockopt(IPPROTO_TCP, TCP_KEEPIDLE, 1)
self._socket.setsockopt(IPPROTO_TCP, TCP_KEEPINTVL, 3)
self._socket.setsockopt(IPPROTO_TCP, TCP_KEEPCNT, 5)

self._socket.connect((self.server, self.port))

break
except timeout:
pass
Expand Down Expand Up @@ -491,7 +513,7 @@ def _get_enhanced_notification(self, token_hex, payload, identifier, expiry):
notification = pack(fmt, ENHANCED_NOTIFICATION_COMMAND, identifier, expiry,
TOKEN_LENGTH, token, len(payload), payload)
return notification

def send_notification(self, token_hex, payload, identifier=0, expiry=0):
"""
in enhanced mode, send_notification may return error response from APNs if any
Expand All @@ -506,7 +528,7 @@ def send_notification(self, token_hex, payload, identifier=0, expiry=0):
self.write(message)
except socket_error as e:
_logger.info("sending notification with id:" + str(identifier) + " to APNS failed: " + str(type(e)) + ": " + str(e))

else:
self.write(self._get_notification(token_hex, payload))

Expand All @@ -524,21 +546,21 @@ def _wait_resending(self, timeout):

def send_notification_multiple(self, frame):
return self.write(frame.get_frame())

def register_response_listener(self, response_listener):
self._response_listener = response_listener

def close_read_thread(self):
self._close_read_thread = True

def _read_error_response(self):
while not self._close_read_thread:
time.sleep(0.1) #avoid crazy loop if something bad happened. e.g. using invalid certificate
while not self.connection_alive:
time.sleep(0.1)

rlist, _, _ = select.select([self._connection()], [], [], 1)

if len(rlist) > 0: # there's error response from APNs
try:
buff = self.read(ERROR_RESPONSE_LENGTH)
Expand All @@ -562,13 +584,13 @@ def _read_error_response(self):
with self._send_lock:
self._reconnect()
self._resend_notifications_by_id(identifier)

def _resend_notifications_by_id(self, failed_identifier):
fail_idx = Util.getListIndexFromID(self._sent_notifications, failed_identifier)
#pop-out success notifications till failed one
self._resend_notification_by_range(fail_idx+1, len(self._sent_notifications))
return

def _resend_notification_by_range(self, start_idx, end_idx):
self._sent_notifications = collections.deque(itertools.islice(self._sent_notifications, start_idx, end_idx))
self._last_resent_qty = len(self._sent_notifications)
Expand All @@ -586,7 +608,7 @@ def _resend_notification_by_range(self, start_idx, end_idx):
class Util(object):
@classmethod
def getListIndexFromID(this_class, the_list, identifier):
return next(index for (index, d) in enumerate(the_list)
return next(index for (index, d) in enumerate(the_list)
if d['id'] == identifier)
@classmethod
def convert_error_response_to_dict(this_class, error_response_tuple):
Expand Down