diff --git a/pushkin/sender/nordifier/apns2_push_sender.py b/pushkin/sender/nordifier/apns2_push_sender.py index 5d85060..4603180 100644 --- a/pushkin/sender/nordifier/apns2_push_sender.py +++ b/pushkin/sender/nordifier/apns2_push_sender.py @@ -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 = [] diff --git a/pushkin/sender/nordifier/pyapn2/client.py b/pushkin/sender/nordifier/pyapn2/client.py index e1f91e7..2028742 100644 --- a/pushkin/sender/nordifier/pyapn2/client.py +++ b/pushkin/sender/nordifier/pyapn2/client.py @@ -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 @@ -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') @@ -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) diff --git a/setup.py b/setup.py index b90ea4e..fbda1b8 100755 --- a/setup.py +++ b/setup.py @@ -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']