def test_send(self): # success, retry + include-failed, don't-retry + include-failed backend = DummyBackend(push=(None, 1, 3)) session = Session(pool=backend) msg = Message(["0123456789ABCDEF", "FEDCBA9876543210"], alert="my alert", badge=10, content_available=1, my_extra=15) push_con = session.get_connection("push_production", cert_string="certificate") srv = APNs(push_con) res = srv.send(msg) self.assertEqual(len(res.failed), 0) self.assertEqual(len(res.errors), 0) self.assertFalse(res.needs_retry()) push_con2 = session.get_connection("push_production", cert_string="certificate") srv = APNs(push_con2) self.assertEqual(session.pool.push_result_pos, 0) session.pool.push_result_pos += 1 res = srv.send(msg) self.assertEqual(len(res.failed), 0) self.assertEqual(len(res.errors), 1) self.assertTrue(res.needs_retry()) # indeed, we have used the cache self.assertEqual(session.pool.new_connections, 1) push_con = session.new_connection("push_production", cert_string="certificate") srv = APNs(push_con) res = srv.send(msg) self.assertEqual(len(res.failed), 0) self.assertEqual(len(res.errors), 1) self.assertFalse(res.needs_retry()) # indeed, new connection, we haven't used the cache self.assertEqual(session.pool.new_connections, 2)
def __init__(self): # apns configuration session = Session() if conf.getboolean('application', 'debug'): con = session.new_connection("feedback_sandbox", cert_file=conf.get('apns', 'cert_sandbox')) else: con = session.new_connection("feedback_production", cert_file=conf.get('apns', 'cert_production')) self.srv = APNs(con)
def __init__(self): # apns configuration self.session = Session() # rabbitmq configuration self.cm = PikaConnectionManager(username=conf.get('rabbitmq', 'username'), password=conf.get('rabbitmq', 'password'), host=conf.get('rabbitmq', 'host'), heartbeat_interval=conf.getint('rabbitmq', 'worker_heartbeat_interval')) self.cm.channel.exchange_declare(exchange='pns_exchange', type='direct', durable=True) self.cm.channel.queue_declare(queue='pns_apns_queue', durable=True) self.cm.channel.queue_bind(exchange='pns_exchange', queue='pns_apns_queue', routing_key='pns_apns') self.cm.channel.basic_qos(prefetch_count=1) self.cm.channel.basic_consume(self._callback, queue='pns_apns_queue')
class StdIOBackendTest(Python26Mixin, unittest.TestCase): """ Test stdio features. """ def setUp(self): self.session = Session(pool="apns_clerk.backends.stdio") def test_locking(self): """ Test thread locking mechanism """ lock = self.session.pool.create_lock() self.assertIsNotNone(lock) self.assertTrue(hasattr(lock, "acquire")) self.assertTrue(hasattr(lock, "release")) lock.acquire() lock.release() def test_certificates(self): """ Test pyOpenSSL certificates. """ cert = self.session.pool.get_certificate({ "cert_string": CERTIFICATE, "key_string": PRIVATE_KEY, "passphrase": PRIVATE_PASS }) cert2 = self.session.pool.get_certificate({ "cert_string": (CERTIFICATE + b"\n" + PRIVATE_KEY), "passphrase": PRIVATE_PASS }) self.assertIsNotNone(cert.get_context()) self.assertEqual(cert, cert2) def test_outdate(self): # we are not allowed to do any IO in tests, so no real connections. # however, it is good idea to test utility functions even with empty pool. self.session.outdate(datetime.timedelta(seconds=60)) self.session.shutdown()
def _push_connection(self): if not self.session: self.session = Session() return self.session.get_connection(address=self.push_address, cert_file=self.cert_file)
class APNSProvider(NotificationsProvider): provider_set = 'apns-device-tokens' def __init__(self, cert_file, push_address="push_sandbox", feedback_address="feedback_sandbox"): self.cert_file = cert_file self.push_address = push_address self.feedback_address = feedback_address self.session = None def _feedback_connection(self): return Session().new_connection(address=self.feedback_address, cert_file=self.cert_file) def feedback_server(self): con = self._feedback_connection() return APNs(con) def _push_connection(self): if not self.session: self.session = Session() return self.session.get_connection(address=self.push_address, cert_file=self.cert_file) def push_server(self): con = self._push_connection() return APNs(con) def handles(self, platform): if platform is iOS: return True else: return False def _get_all_device_tokens(self): return kv_store.smembers(self.provider_set) def notify(self, message, alert, device_tokens=None, expiry=DEFAULT_EXPIRY): """Send a push notification through APNs, this can be to specific devices or all devices we have stored in ``self.provider_set`` APNs queues push notifications as devices become available unlike GCM This means you attach an expiry on each push notification. So if a push notification becomes irrelevant it can be considered expired. :param message: User provided string to be pushed to all devices. :param alert: Associated alert :param device_tokens: Pass specific device tokens to push to. If no tokens are passed then all devices found in ``self.provider_set`` will be pushed to. :param expiry: How long should APNS keep this message queued for devices which are not immediately available to receive the push notification. """ if device_tokens is None: device_tokens = self._get_all_device_tokens() logger.info("APNS: Push notification to {0} clients. Message: {1}" .format(len(device_tokens), message)) # Return if there are no device tokens to push to if not device_tokens: logger.warning("APNs: No device_tokens to push to") return False message = Message(device_tokens, alert=message, expiry=expiry) server = self.push_server() result = server.send(message) self.handle_result(result) # Retry if we have some failures if result.needs_retry(): message = result.retry() logger.info("APNs: Retrying for {0} device tokens." .format(len(message.tokens))) result = server.send(message) self.handle_result(result) def handle_result(self, result): """Logs the errors from APNS Removes any tokens which cause failures. """ # Check failures. Check codes in APNs reference docs. for token, reason in result.failed.items(): code, errmsg = reason logger.warning("APNs: Device failed: {0}, reason: {1}".format( token, errmsg)) self.remove_token(token) logger.info("APNs: Removed token: {0}".format(token)) # Check failures not related to devices. for code, errmsg in result.errors: logger.warning("APNs: Error: {0}".format(errmsg)) def feedback(self): """Feedback is a process through which APNs notifies you of clients becoming unavailable to receiving push notifications. Most likely this means they have uninstalled the app. """ server = self.feedback_server() for token, since in server.feedback(): kv_store.srem(self.provider_set, token) logger.info("APNs: Token {0} is unavailable since {1}".format( token, since))
def setUp(self): self.session = Session(pool="apns_clerk.backends.stdio")
def get_session(self): return Session()
def get_session(self, push=None, feedback=None): backend = DummyBackend(push=push, feedback=feedback) return Session(pool=backend)
class APNSWorker(object): def __init__(self): # apns configuration self.session = Session() # rabbitmq configuration self.cm = PikaConnectionManager(username=conf.get('rabbitmq', 'username'), password=conf.get('rabbitmq', 'password'), host=conf.get('rabbitmq', 'host'), heartbeat_interval=conf.getint('rabbitmq', 'worker_heartbeat_interval')) self.cm.channel.exchange_declare(exchange='pns_exchange', type='direct', durable=True) self.cm.channel.queue_declare(queue='pns_apns_queue', durable=True) self.cm.channel.queue_bind(exchange='pns_exchange', queue='pns_apns_queue', routing_key='pns_apns') self.cm.channel.basic_qos(prefetch_count=1) self.cm.channel.basic_consume(self._callback, queue='pns_apns_queue') def start(self): self.cm.channel.start_consuming() def _callback(self, ch, method, properties, body): """ send apns notifications :param ch: :param method: :param properties: :param body: :return: """ self.session.outdate(timedelta(minutes=5)) if conf.getboolean('application', 'debug'): self.apns_con = self.session.get_connection("push_sandbox", cert_file=conf.get('apns', 'cert_sandbox')) else: self.apns_con = self.session.get_connection("push_production", cert_file=conf.get('apns', 'cert_production')) message = loads(body) logger.debug('payload: %s' % message) badge = None sound = 'default' content_available = None # time to live ttl = timedelta(days=5) if 'apns' in message['payload']: if 'badge' in message['payload']['apns']: badge = message['payload']['apns']['badge'] if 'sound' in message['payload']['apns']: sound = message['payload']['apns']['sound'] if 'content_available' in message['payload']['apns']: content_available = message['payload']['apns']['content_available'] if 'ttl' in message['payload']: ttl = timedelta(seconds=message['payload']['ttl']) message = Message(message['devices'], alert=message['payload']['alert'], badge=badge, sound=sound, content_available=content_available, expiry=ttl, extra=message['payload']['data'] if 'data' in message['payload'] else None) try: srv = APNs(self.apns_con) response = srv.send(message) logger.debug('apns response: %s' % response) except Exception as ex: ch.basic_ack(delivery_tag=method.delivery_tag) logger.exception(ex) return # Check failures. Check codes in APNs reference docs. for token, reason in response.failed.items(): code, errmsg = reason # according to APNs protocol the token reported here # is garbage (invalid or empty), stop using and remove it. logger.info('delivery failure apns_token: %s, reason: %s' % (token, errmsg)) device_obj = Device.query.filter_by(platform_id=token).first() if device_obj: db.session.delete(device_obj) try: db.session.commit() except Exception as ex: db.session.rollback() logger.exception(ex) # Check failures not related to devices. for code, errmsg in response.errors: logger.error(errmsg) # Check if there are tokens that can be retried if response.needs_retry(): # repeat with retry_message or reschedule your task srv.send(response.retry()) ch.basic_ack(delivery_tag=method.delivery_tag)
logger = logging.getLogger(__name__) from django.forms.models import model_to_dict from django.contrib.auth import * from service import BaseService import json from apns_clerk import Message, Session, APNs from apns_clerk.apns import Result from apns_clerk.backends.dummy import Backend as DummyBackend from emsg_simple_api.settings import APNS_CERT_PATH,DEBUG ''' emsg_server 回调接口文档 https://github.com/cc14514/emsg_sdk/wiki/emsg_server-%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3 ''' session = Session() # push_sandbox 测试证书 # push_production 生产证书 con = session.get_connection("push_sandbox", cert_file=APNS_CERT_PATH) class user_message(BaseService): def offline(self,body): ''' 离线消息回调,其中 params 是 packet :param body: :return: ''' sn = body['sn'] packet = body['params'] # text image geo audio logger.info('[emsg_offline] packet = %s' % packet)
def send_apns_message(device, app, message_type, data=None): """ Send an Apple Push Notification message. """ token_list = [device.token] unique_key = device.token if message_type == TYPE_CALL: unique_key = data['unique_key'] message = Message(token_list, payload=get_call_push_payload( unique_key, data['phonenumber'], data['caller_id'])) elif message_type == TYPE_MESSAGE: message = Message(token_list, payload=get_message_push_payload(data['message'])) else: logger.warning('{0} | TRYING TO SENT MESSAGE OF UNKNOWN TYPE: {1}', unique_key, message_type) session = Session() push_mode = settings.APNS_PRODUCTION if device.sandbox: # Sandbox push mode. push_mode = settings.APNS_SANDBOX full_cert_path = os.path.join(settings.CERT_DIR, app.push_key) con = session.get_connection(push_mode, cert_file=full_cert_path) srv = APNs(con) try: logger.info( '{0} | Sending APNS \'{1}\' message at time:{2} to {3} Data:{4}'. format( unique_key, message_type, datetime.datetime.fromtimestamp( time()).strftime('%H:%M:%S.%f'), device.token, data)) res = srv.send(message) except Exception: logger.exception('{0} | Error sending APNS message'.format( unique_key, )) else: # Check failures. Check codes in APNs reference docs. for token, reason in res.failed.items(): code, errmsg = reason # According to APNs protocol the token reported here # is garbage (invalid or empty), stop using and remove it. logger.warning( '{0} | Sending APNS message failed for device: {1}, reason: {2}' .format(unique_key, token, errmsg)) # Check failures not related to devices. for code, errmsg in res.errors: logger.warning('{0} | Error sending APNS message. \'{1}\''.format( unique_key, errmsg)) # Check if there are tokens that can be retried. if res.needs_retry(): logger.info('{0} | Could not sent APNS message, retrying...') # Repeat with retry_message or reschedule your task. res.retry()
import django.utils.timezone as dtz logger = logging.getLogger(__name__) from django.forms.models import model_to_dict from django.contrib.auth import * from service import BaseService import json from apns_clerk import Message, Session, APNs from apns_clerk.apns import Result from apns_clerk.backends.dummy import Backend as DummyBackend from emsg_simple_api.settings import APNS_CERT_PATH, DEBUG ''' emsg_server 回调接口文档 https://github.com/cc14514/emsg_sdk/wiki/emsg_server-%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3 ''' session = Session() # push_sandbox 测试证书 # push_production 生产证书 con = session.get_connection("push_sandbox", cert_file=APNS_CERT_PATH) class user_message(BaseService): def offline(self, body): ''' 离线消息回调,其中 params 是 packet :param body: :return: ''' sn = body['sn'] packet = body['params'] # text image geo audio
def test_feedback(self): backend = DummyBackend(feedback=5) session = Session(pool=backend) feed_con = session.new_connection("feedback_production", cert_string="certificate") srv = APNs(feed_con) self.assertEqual(len(list(srv.feedback())), 5)