Skip to content

Commit

Permalink
reconnect APN on connection loss
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Korhner committed Jan 26, 2017
1 parent 3040e69 commit e426101
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 16 deletions.
2 changes: 1 addition & 1 deletion pushkin/sender/nordifier/apns2_push_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self, config, log):
self.sandbox = config.getboolean('Messenger', 'apns_sandbox')
self.certificate_path = config.get('Messenger', 'apns_certificate_path')
self.topic = config.get('Messenger', 'apns_topic')
self.apn = APNsClient(self.certificate_path, use_sandbox=self.sandbox)
self.apn = APNsClient(self.certificate_path, use_sandbox=self.sandbox, log=log)
self.canonical_ids = []
self.unregistered_devices = []

Expand Down
47 changes: 33 additions & 14 deletions pushkin/sender/nordifier/pyapn2/client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from json import dumps

import json
import time
from hyper import HTTP20Connection
from hyper.tls import init_context

from hyper.http20.connection import StreamResetError
from errors import exception_class_for_reason


Expand All @@ -12,12 +13,20 @@


class APNsClient(object):
def __init__(self, cert_file, use_sandbox=False, use_alternative_port=False, proto=None):
server = 'api.development.push.apple.com' if use_sandbox else 'api.push.apple.com'
port = 2197 if use_alternative_port else 443
ssl_context = init_context()
ssl_context.load_cert_chain(cert_file)
self.__connection = HTTP20Connection(server, port, ssl_context=ssl_context, force_proto=proto or 'h2')
def __init__(self, cert_file, log, use_sandbox=False, use_alternative_port=False, proto=None):
self.log = log
self.server = 'api.development.push.apple.com' if use_sandbox else 'api.push.apple.com'
self.port = 2197 if use_alternative_port else 443
self.ssl_context = init_context()
self.ssl_context.load_cert_chain(cert_file)
self.proto = proto
self.__connection = None
self.connect_to_apn_if_needed()

def connect_to_apn_if_needed(self):
if self.__connection is None:
self.__connection = HTTP20Connection(self.server, self.port, ssl_context=self.ssl_context,
force_proto=self.proto or 'h2')

def send_notification(self, token_hex, notification, priority=IMMEDIATE_NOTIFICATION_PRIORITY, topic=None, expiration=None):
json_payload = dumps(notification.dict(), ensure_ascii=False, separators=(',', ':')).encode('utf-8')
Expand All @@ -32,10 +41,20 @@ def send_notification(self, token_hex, notification, priority=IMMEDIATE_NOTIFICA
headers['apns-expiration'] = "%d" % expiration

url = '/3/device/{}'.format(token_hex)
stream_id = self.__connection.request('POST', url, json_payload, headers)
resp = self.__connection.get_response(stream_id)
with resp:
if resp.status != 200:
raw_data = resp.read().decode('utf-8')
data = json.loads(raw_data)
raise exception_class_for_reason(data['reason'])
sent = False
while not sent:
try:
self.connect_to_apn_if_needed()
stream_id = self.__connection.request('POST', url, json_payload, headers)
resp = self.__connection.get_response(stream_id)
with resp:
if resp.status != 200:
raw_data = resp.read().decode('utf-8')
data = json.loads(raw_data)
raise exception_class_for_reason(data['reason'])
sent = True
except StreamResetError:
# Connection to APN closed, reconnect
self.log.exception("Connection to APN lost, reconnecting and trying again")
self.__connection = None
time.sleep(5)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
'requests>=2.9.1',
'sqlalchemy>=1.0.12',
'alembic>=0.8.6',
'hyper>=0.6.2',
'hyper==0.7.0',
],
package_data = {
'': ['*.sql', '*.sh', '*.ini', '*.mako']
Expand Down

0 comments on commit e426101

Please sign in to comment.