Beispiel #1
0
    def _process_reply(self, reply):
        """Process GCM send reply"""
        # acks:
        #  for reg_id, msg_id in reply.success.items():
        # updates
        for old_id, new_id in reply.canonical.items():
            log.msg("GCM id changed : %s => " % old_id, new_id)
            return RouterResponse(status_code=503,
                                  response_body="Please try request again.",
                                  router_data=dict(token=new_id))
        # naks:
        # uninstall:
        for reg_id in reply.not_registered:
            log.msg("GCM no longer registered: %s" % reg_id)
            return RouterResponse(
                status_code=410,
                response_body="Endpoint requires client update",
                router_data={},
            )

        #  for reg_id, err_code in reply.failed.items():
        if len(reply.failed.items()) > 0:
            log.msg("GCM failures: %s" % json.dumps(reply.failed.items()))
            raise RouterException("GCM failure to deliver", status_code=503,
                                  response_body="Please try request later.")

        # retries:
        if reply.needs_retry():
            raise RouterException("GCM failure to deliver", status_code=503,
                                  response_body="Please try request later.")

        return RouterResponse(status_code=200, response_body="Message Sent")
Beispiel #2
0
 def _process_reply(self, reply, notification, router_data, ttl):
     """Process FCM send reply"""
     # acks:
     #  for reg_id, msg_id in reply.success.items():
     # updates
     result = reply.get('results', [{}])[0]
     if reply.get('canonical_ids'):
         old_id = router_data['token']
         new_id = result.get('registration_id')
         self.log.debug("FCM id changed : {old} => {new}",
                        old=old_id, new=new_id)
         self.metrics.increment("notification.bridge.error",
                                tags=make_tags(self._base_tags,
                                               reason="reregister"))
         return RouterResponse(status_code=503,
                               response_body="Please try request again.",
                               router_data=dict(token=new_id))
     if reply.get('failure'):
         self.metrics.increment("notification.bridge.error",
                                tags=make_tags(self._base_tags,
                                               reason="failure"))
         reason = result.get('error', "Unreported")
         err = self.reasonTable.get(reason)
         if err.get("crit", False):
             self.log.critical(
                 err['msg'],
                 nlen=notification.data_length,
                 regid=router_data["token"],
                 senderid=self.senderID,
                 ttl=notification.ttl,
             )
             raise RouterException("FCM failure to deliver",
                                   status_code=err['err'],
                                   response_body="Please try request "
                                                 "later.",
                                   log_exception=False)
         creds = router_data["creds"]
         self.log.debug("{msg} : {info}",
                        msg=err['msg'],
                        info={"senderid": creds.get('registration_id'),
                              "reason": reason})
         return RouterResponse(
             status_code=err['err'],
             errno=err['errno'],
             response_body=err['msg'],
             router_data={},
         )
     self.metrics.increment("notification.bridge.sent",
                            self._base_tags)
     self.metrics.gauge("notification.message_data",
                        notification.data_length,
                        tags=make_tags(self._base_tags,
                                       destination="Direct"))
     location = "%s/m/%s" % (self.ap_settings.endpoint_url,
                             notification.version)
     return RouterResponse(status_code=201, response_body="",
                           headers={"TTL": ttl,
                                    "Location": location},
                           logged_status=200)
Beispiel #3
0
    def _process_reply(self, reply, uaid_data, ttl, notification):
        """Process GCM send reply"""
        # acks:
        #  for reg_id, msg_id in reply.success.items():
        # updates
        for old_id, new_id in reply.canonical.items():
            self.log.info("GCM id changed : {old} => {new}",
                          old=old_id, new=new_id)
            self.metrics.increment("updates.client.bridge.gcm.failed.rereg",
                                   self._base_tags)
            return RouterResponse(status_code=503,
                                  response_body="Please try request again.",
                                  router_data=dict(token=new_id))
        # naks:
        # uninstall:
        for reg_id in reply.not_registered:
            self.metrics.increment("updates.client.bridge.gcm.failed.unreg",
                                   self._base_tags)
            self.log.info("GCM no longer registered: %s" % reg_id)
            return RouterResponse(
                status_code=410,
                response_body="Endpoint requires client update",
                router_data={},
            )

        #  for reg_id, err_code in reply.failed.items():
        if len(reply.failed.items()) > 0:
            self.metrics.increment("updates.client.bridge.gcm.failed.failure",
                                   self._base_tags)
            self.log.info("GCM failures: {failed()}",
                          failed=lambda: repr(reply.failed.items()))
            raise RouterException("GCM unable to deliver", status_code=410,
                                  response_body="GCM recipient not available.",
                                  log_exception=False,
                                  )

        # retries:
        if reply.needs_retry():
            self.metrics.increment("updates.client.bridge.gcm.failed.retry",
                                   self._base_tags)
            self.log.warn("GCM retry requested: {failed()}",
                          failed=lambda: repr(reply.failed.items()))
            raise RouterException("GCM failure to deliver, retry",
                                  status_code=503,
                                  response_body="Please try request later.",
                                  log_exception=False)

        self.metrics.increment("updates.client.bridge.gcm.succeeded",
                               self._base_tags)
        location = "%s/m/%s" % (self.ap_settings.endpoint_url,
                                notification.version)
        return RouterResponse(status_code=201, response_body="",
                              headers={"TTL": ttl,
                                       "Location": location},
                              logged_status=200)
Beispiel #4
0
    def test_router_returns_data_without_detail(self):
        self.ap_settings.parse_endpoint = Mock(return_value=dict(
            uaid=dummy_uaid,
            chid=dummy_chid,
            public_key="asdfasdf",
        ))
        self.fernet_mock.decrypt.return_value = dummy_token
        self.router_mock.get_uaid.return_value = dict(
            uaid=dummy_uaid,
            router_type="webpush",
            router_data=dict(uaid="uaid"),
            current_month=self.ap_settings.current_msg_month,
        )
        self.wp_router_mock.route_notification.return_value = RouterResponse(
            status_code=503,
            router_data=dict(),
        )

        def handle_finish(result):
            eq_(result, True)
            self.wp.set_status.assert_called_with(503, reason=None)
            ok_(self.router_mock.drop_user.called)

        self.finish_deferred.addCallback(handle_finish)

        self.wp.post("v1", dummy_token)
        return self.finish_deferred
Beispiel #5
0
 def stored_response(self, notification):
     location = "%s/m/%s" % (self.ap_settings.endpoint_url,
                             notification.location)
     return RouterResponse(status_code=201, response_body="",
                           headers={"Location": location,
                                    "TTL": notification.ttl},
                           logged_status=202)
Beispiel #6
0
    def _route(self, notification, router_data):
        """Blocking APNS call to route the notification"""
        token = router_data["token"]
        custom = {
            "Chid": notification.channel_id,
            "Ver": notification.version,
        }
        if notification.data:
            custom["Msg"] = notification.data
            custom["Con"] = notification.headers["content-encoding"]
            custom["Enc"] = notification.headers["encryption"]

            if "crypto-key" in notification.headers:
                custom["Cryptokey"] = notification.headers["crypto-key"]
            elif "encryption-key" in notification.headers:
                custom["Enckey"] = notification.headers["encryption-key"]

        payload = apns.Payload(alert=router_data.get("title",
                                                     self.default_title),
                               content_available=1,
                               custom=custom)
        now = int(time.time())
        self.messages[now] = {"token": token, "payload": payload}
        # TODO: Add listener for error handling.
        self.apns.gateway_server.register_response_listener(self._error)
        self.apns.gateway_server.send_notification(token, payload, now)

        # cleanup sent messages
        if self.messages:
            for time_sent in self.messages.keys():
                if time_sent < now - self.config.get("expry", 10):
                    del self.messages[time_sent]
        return RouterResponse(status_code=200, response_body="Message sent")
Beispiel #7
0
    def test_router_needs_update(self):
        self.ap_settings.parse_endpoint = Mock(return_value=dict(
            uaid=dummy_uaid,
            chid=dummy_chid,
            public_key="asdfasdf",
        ))
        self.fernet_mock.decrypt.return_value = dummy_token
        self.router_mock.get_uaid.return_value = dict(
            router_type="webpush",
            router_data=dict(),
            uaid=dummy_uaid,
            current_month=self.ap_settings.current_msg_month,
        )
        self.wp_router_mock.route_notification.return_value = RouterResponse(
            status_code=503,
            router_data=dict(token="new_connect"),
        )

        def handle_finish(result):
            eq_(result, True)
            self.wp.set_status.assert_called_with(503, reason=None)
            ru = self.router_mock.register_user
            ok_(ru.called)
            eq_('webpush', ru.call_args[0][0].get('router_type'))

        self.finish_deferred.addCallback(handle_finish)

        self.wp.post("v1", dummy_token)
        return self.finish_deferred
Beispiel #8
0
    def test_router_needs_update(self):
        self.conf.parse_endpoint = Mock(return_value=dict(
            uaid=dummy_uaid,
            chid=dummy_chid,
            public_key="asdfasdf",
        ))
        self.fernet_mock.decrypt.return_value = dummy_token
        self.db.router.get_uaid = Mock(return_value=dict(
            router_type="webpush",
            router_data=dict(),
            uaid=dummy_uaid,
            current_month=self.db.current_msg_month,
        ))
        self.db.router.register_user = Mock(return_value=False)
        self.wp_router_mock.route_notification.return_value = RouterResponse(
            status_code=503,
            router_data=dict(token="new_connect"),
        )
        self.db.message_tables.append(self.db.current_msg_month)

        resp = yield self.client.post(
            self.url(api_ver="v1", token=dummy_token), )
        assert resp.get_status() == 503
        ru = self.db.router.register_user
        assert ru.called
        assert 'webpush' == ru.call_args[0][0].get('router_type')
Beispiel #9
0
 def stored_response(self, notification):
     self.metrics.increment("notification.message_data",
                            notification.data_length,
                            tags=make_tags(destination='Direct'))
     location = "%s/m/%s" % (self.conf.endpoint_url, notification.location)
     return RouterResponse(status_code=201, response_body="",
                           headers={"Location": location,
                                    "TTL": notification.ttl},
                           logged_status=202)
Beispiel #10
0
    def test_webpush_uaid_lookup_no_crypto_headers_without_data(self):
        fresult = dict(router_type="webpush")
        frouter = self.settings.routers["webpush"]
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            assert(frouter.route_notification.called)

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #11
0
    def test_put_default_router(self):
        self.fernet_mock.decrypt.return_value = "123:456"
        self.router_mock.get_uaid.return_value = dict()
        self.sp_router_mock.route_notification.return_value = RouterResponse()

        def handle_finish(result):
            self.assertTrue(result)
            self.endpoint.set_status.assert_called_with(200)
        self.finish_deferred.addCallback(handle_finish)

        self.endpoint.put(dummy_uaid)
        return self.finish_deferred
Beispiel #12
0
    def test_webpush_uaid_lookup_no_crypto_headers_with_data(self):
        fresult = dict(router_type="webpush")
        frouter = self.settings.routers["webpush"]
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.request_mock.body = b"stuff"
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            self.endpoint.set_status.assert_called_with(400)

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #13
0
 def delivered_response(self, notification):
     self.metrics.gauge("notification.message_data",
                        notification.data_length,
                        tags=make_tags(destination='Stored'))
     location = "%s/m/%s" % (self.ap_settings.endpoint_url,
                             notification.location)
     return RouterResponse(status_code=201,
                           response_body="",
                           headers={
                               "Location": location,
                               "TTL": notification.ttl or 0
                           },
                           logged_status=200)
Beispiel #14
0
 def stored_response(self, notification):
     self.metrics.increment("notification.message_data",
                            notification.data_length,
                            tags=make_tags(destination='Stored'))
     location = "%s/m/%s" % (self.conf.endpoint_url, notification.location)
     # RFC https://tools.ietf.org/html/rfc8030#section-5
     # all responses should be 201, unless this is a push reciept request,
     # which requires a 202 and a URL that can be checked later for UA
     # acknowledgement. (We don't support that yet. See autopush-rs#244)
     return RouterResponse(status_code=201, response_body="",
                           headers={"Location": location,
                                    "TTL": notification.ttl},
                           logged_status=201)
Beispiel #15
0
    def test_other_uaid_lookup_no_crypto_headers(self):
        fresult = dict(router_type="test")
        frouter = Mock(spec=Router)
        frouter.route_notification = Mock()
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.endpoint.ap_settings.routers["test"] = frouter
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            assert(frouter.route_notification.called)

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #16
0
    def test_uaid_lookup_results(self):
        fresult = dict(router_type="test")
        frouter = Mock(spec=Router)
        frouter.route_notification = Mock()
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.request_mock.headers["encryption"] = "stuff"
        self.request_mock.headers["content-encoding"] = "aes128"
        self.endpoint.ap_settings.routers["test"] = frouter
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            assert(frouter.route_notification.called)

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #17
0
 def _process_reply(self, reply, notification, router_data, ttl):
     """Process FCM send reply"""
     # Failures are returned as non-200 messages (404, 410, etc.)
     self.metrics.increment("notification.bridge.sent",
                            tags=self._base_tags)
     self.metrics.increment("notification.message_data",
                            notification.data_length,
                            tags=make_tags(self._base_tags,
                                           destination="Direct"))
     location = "%s/m/%s" % (self.conf.endpoint_url, notification.version)
     return RouterResponse(status_code=201,
                           response_body="",
                           headers={
                               "TTL": ttl,
                               "Location": location
                           },
                           logged_status=200)
Beispiel #18
0
    def test_put_router_needs_update(self):
        self.fernet_mock.decrypt.return_value = "123:456"
        self.router_mock.get_uaid.return_value = dict(
            router_type="simplepush",
            router_data=dict(),
        )
        self.sp_router_mock.route_notification.return_value = RouterResponse(
            status_code=503,
            router_data=dict(token="new_connect"),
        )

        def handle_finish(result):
            self.assertTrue(result)
            self.endpoint.set_status.assert_called_with(503)
            assert(self.router_mock.register_user.called)
        self.finish_deferred.addCallback(handle_finish)

        self.endpoint.put(dummy_uaid)
        return self.finish_deferred
Beispiel #19
0
    def test_webpush_payload_encoding(self):
        fresult = dict(router_type="webpush")
        frouter = self.settings.routers["webpush"]
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.request_mock.headers["encryption"] = "stuff"
        self.request_mock.headers["content-encoding"] = "aes128"
        self.request_mock.body = b"\xc3\x28\xa0\xa1"
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            calls = frouter.route_notification.mock_calls
            eq_(len(calls), 1)
            (_, (notification, _), _) = calls[0]
            eq_(notification.channel_id, "fred")
            eq_(notification.data, b"wyigoQ==")

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #20
0
    def test_other_payload_encoding(self):
        fresult = dict(router_type="test")
        frouter = Mock(spec=Router)
        frouter.route_notification = Mock()
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.endpoint.ap_settings.routers["test"] = frouter

        self.request_mock.body = b"stuff"
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            calls = frouter.route_notification.mock_calls
            eq_(len(calls), 1)
            (_, (notification, _), _) = calls[0]
            eq_(notification.channel_id, "fred")
            eq_(notification.data, b"stuff")

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #21
0
    def test_webpush_ttl_too_large(self):
        from autopush.endpoint import MAX_TTL
        fresult = dict(router_type="test")
        frouter = Mock(spec=Router)
        frouter.route_notification = Mock()
        frouter.route_notification.return_value = RouterResponse()
        self.endpoint.chid = "fred"
        self.request_mock.headers["ttl"] = MAX_TTL + 100
        self.request_mock.headers["encryption"] = "stuff"
        self.request_mock.headers["content-encoding"] = "aes128"
        self.endpoint.ap_settings.routers["test"] = frouter
        self.endpoint._uaid_lookup_results(fresult)

        def handle_finish(value):
            assert(frouter.route_notification.called)
            args, kwargs = frouter.route_notification.call_args
            notif = args[0]
            eq_(notif.ttl, MAX_TTL)

        self.finish_deferred.addCallback(handle_finish)
        return self.finish_deferred
Beispiel #22
0
    def test_router_returns_data_without_detail(self):
        self.conf.parse_endpoint = Mock(return_value=dict(
            uaid=dummy_uaid,
            chid=dummy_chid,
            public_key="asdfasdf",
        ))
        self.fernet_mock.decrypt.return_value = dummy_token
        self.db.router.get_uaid.return_value = dict(
            uaid=dummy_uaid,
            router_type="webpush",
            router_data=dict(uaid="uaid"),
            current_month=self.db.current_msg_month,
        )
        self.wp_router_mock.route_notification.return_value = RouterResponse(
            status_code=503,
            router_data=dict(),
        )

        resp = yield self.client.post(
            self.url(api_ver="v1", token=dummy_token), )
        assert resp.get_status() == 503
        assert self.db.router.drop_user.called
Beispiel #23
0
    def test_post_webpush_with_headers_in_response(self):
        self.fernet_mock.decrypt.return_value = "123:456"
        self.endpoint.set_header = Mock()
        self.request_mock.headers["encryption"] = "stuff"
        self.request_mock.headers["content-encoding"] = "aes128"
        self.router_mock.get_uaid.return_value = dict(
            router_type="webpush",
            router_data=dict(),
        )
        self.wp_router_mock.route_notification.return_value = RouterResponse(
            status_code=201,
            headers={"Location": "Somewhere"}
        )

        def handle_finish(result):
            self.assertTrue(result)
            self.endpoint.set_status.assert_called_with(201)
            self.endpoint.set_header.assert_called_with(
                "Location", "Somewhere")
        self.finish_deferred.addCallback(handle_finish)

        self.endpoint.post(dummy_uaid)
        return self.finish_deferred
Beispiel #24
0
 def test_router_response_client_error(self):
     from autopush.router.interface import RouterResponse
     response = RouterResponse(headers=dict(Location="http://a.com/"),
                               status_code=400)
     self.base._router_response(response, None, None)
     self.status_mock.assert_called_with(400, reason=None)
Beispiel #25
0
 def test_router_response(self):
     from autopush.router.interface import RouterResponse
     response = RouterResponse(headers=dict(Location="http://a.com/"))
     self.base._router_response(response)
     self.status_mock.assert_called_with(200, reason=None)
Beispiel #26
0
    def _route(self, notification, uaid_data):
        """Blocking ADM call to route the notification"""
        router_data = uaid_data["router_data"]
        # THIS MUST MATCH THE CHANNELID GENERATED BY THE REGISTRATION SERVICE
        # Currently this value is in hex form.
        data = {"chid": notification.channel_id.hex}
        # Payload data is optional. The endpoint handler validates that the
        # correct encryption headers are included with the data.
        if notification.data:
            data['body'] = notification.data
            data['con'] = notification.headers['encoding']

            if 'encryption' in notification.headers:
                data['enc'] = notification.headers.get('encryption')
            if 'crypto_key' in notification.headers:
                data['cryptokey'] = notification.headers['crypto_key']

        # registration_ids are the ADM instance tokens (specified during
        # registration.
        ttl = min(self.MAX_TTL, max(notification.ttl or 0, self.min_ttl))

        try:
            adm = self.profiles[router_data['creds']['profile']]
            adm.send(reg_id=router_data.get("token"),
                     payload=data,
                     collapseKey=notification.topic,
                     ttl=ttl)
        except RouterException:
            raise  # pragma nocover
        except Timeout as e:
            self.log.warn("ADM Timeout: %s" % e)
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(self._base_tags,
                                                  reason="timeout"))
            raise RouterException("Server error",
                                  status_code=502,
                                  errno=902,
                                  log_exception=False)
        except ConnectionError as e:
            self.log.warn("ADM Unavailable: %s" % e)
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(
                                       self._base_tags,
                                       reason="connection_unavailable"))
            raise RouterException("Server error",
                                  status_code=502,
                                  errno=902,
                                  log_exception=False)
        except ADMAuthError as e:
            self.log.error("ADM unable to authorize: %s" % e)
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(self._base_tags,
                                                  reason="auth failure"))
            raise RouterException("Server error",
                                  status_code=500,
                                  errno=902,
                                  log_exception=False)
        except Exception as e:
            self.log.error("Unhandled exception in ADM Routing: %s" % e)
            raise RouterException("Server error", status_code=500)
        location = "%s/m/%s" % (self.conf.endpoint_url, notification.version)
        return RouterResponse(status_code=201,
                              response_body="",
                              headers={
                                  "TTL": ttl,
                                  "Location": location
                              },
                              logged_status=200)
Beispiel #27
0
 def stored_response(self, notification):
     return RouterResponse(202, "Notification Stored")
Beispiel #28
0
 def delivered_response(self, notification):
     return RouterResponse(200, "Delivered")
Beispiel #29
0
    def _process_reply(self, reply, uaid_data, ttl, notification):
        """Process GCM send reply"""
        # acks:
        #  for reg_id, msg_id in reply.success.items():
        # updates
        for old_id, new_id in reply.canonicals.items():
            self.log.debug("GCM id changed : {old} => {new}",
                           old=old_id, new=new_id)
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(self._base_tags,
                                                  reason="reregister"))
            return RouterResponse(status_code=503,
                                  response_body="Please try request again.",
                                  router_data=dict(token=new_id))
        # naks:
        # uninstall:
        for reg_id in reply.not_registered:
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(self._base_tags,
                                                  reason="unregistered"))
            self.log.debug("GCM no longer registered: %s" % reg_id)
            return RouterResponse(
                status_code=410,
                response_body="Endpoint requires client update",
                router_data={},
            )

        #  for reg_id, err_code in reply.failed.items():
        if len(reply.failed.items()) > 0:
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(self._base_tags,
                                                  reason="failure"))
            self.log.debug("GCM failures: {failed()}",
                           failed=lambda: repr(reply.failed.items()))
            raise RouterException("GCM unable to deliver", status_code=410,
                                  response_body="GCM recipient not available.",
                                  log_exception=False,
                                  )

        # retries:
        if reply.retry_after:
            self.metrics.increment("notification.bridge.error",
                                   tags=make_tags(self._base_tags,
                                                  reason="retry"))
            self.log.warn("GCM retry requested: {failed()}",
                          failed=lambda: repr(reply.failed.items()))
            raise RouterException("GCM failure to deliver, retry",
                                  status_code=503,
                                  headers={"Retry-After": reply.retry_after},
                                  response_body="Please try request "
                                                "in {} seconds.".format(
                                       reply.retry_after
                                  ),
                                  log_exception=False)

        self.metrics.increment("notification.bridge.sent",
                               tags=self._base_tags)
        self.metrics.increment("notification.message_data",
                               notification.data_length,
                               tags=make_tags(self._base_tags,
                                              destination='Direct'))
        location = "%s/m/%s" % (self.conf.endpoint_url, notification.version)
        return RouterResponse(status_code=201, response_body="",
                              headers={"TTL": ttl,
                                       "Location": location},
                              logged_status=200)
Beispiel #30
0
    def _route(self, notification, router_data):
        """Blocking APNS call to route the notification

        :param notification: Notification data to send
        :type notification: dict
        :param router_data: Pre-initialized data for this connection
        :type router_data: dict

        """
        router_token = router_data["token"]
        rel_channel = router_data["rel_channel"]
        apns_client = self.apns[rel_channel]
        # chid MUST MATCH THE CHANNELID GENERATED BY THE REGISTRATION SERVICE
        # Currently this value is in hex form.
        payload = {
            "chid": notification.channel_id.hex,
            "ver": notification.version,
        }
        if notification.data:
            payload["body"] = notification.data
            payload["con"] = notification.headers["encoding"]
            payload["enc"] = notification.headers["encryption"]

            if "crypto_key" in notification.headers:
                payload["cryptokey"] = notification.headers["crypto_key"]
            elif "encryption_key" in notification.headers:
                payload["enckey"] = notification.headers["encryption_key"]
            payload['aps'] = router_data.get(
                'aps', {
                    "mutable-content": 1,
                    "alert": {
                        "loc-key": "SentTab.NoTabArrivingNotification.body",
                        "title-loc-key":
                        "SentTab.NoTabArrivingNotification.title",
                    }
                })
        apns_id = str(uuid.uuid4()).lower()
        # APNs may force close a connection on us without warning.
        # if that happens, retry the message.
        success = False
        try:
            apns_client.send(router_token=router_token,
                             payload=payload,
                             apns_id=apns_id)
            success = True
        except ConnectionError:
            self.metrics.increment("notification.bridge.connection.error",
                                   tags=make_tags(self._base_tags,
                                                  application=rel_channel,
                                                  reason="connection_error"))
        except (HTTP20Error, socket.error):
            self.metrics.increment("notification.bridge.connection.error",
                                   tags=make_tags(self._base_tags,
                                                  application=rel_channel,
                                                  reason="http2_error"))
        if not success:
            raise RouterException(
                "Server error",
                status_code=502,
                response_body="APNS returned an error processing request",
                log_exception=False,
            )
        location = "%s/m/%s" % (self.conf.endpoint_url, notification.version)
        self.metrics.increment("notification.bridge.sent",
                               tags=make_tags(self._base_tags,
                                              application=rel_channel))

        self.metrics.increment("updates.client.bridge.apns.{}.sent".format(
            router_data["rel_channel"]),
                               tags=self._base_tags)
        self.metrics.increment("notification.message_data",
                               notification.data_length,
                               tags=make_tags(self._base_tags,
                                              destination='Direct'))
        return RouterResponse(status_code=201,
                              response_body="",
                              headers={
                                  "TTL": notification.ttl,
                                  "Location": location
                              },
                              logged_status=200)