Git Product home page Git Product logo

aapns's Introduction

AAPNS

CircleCI Documentation Status

Asynchronous Apple Push Notification Service client.

  • Requires TLS 1.2 or better
  • Requires Python 3.8 or better

Quickstart

from aapns.api import Server
from aapns.config import Priority
from aapns.models import Notification, Alert, Localized

async def send_hello_world():
    client = await Server.production('/path/to/push/cert.pem').create_client()
    apns_id = await client.send_notification(
        'my-device-token',
        Notification(
            alert=Alert(
                body=Localized(
                    key='Hello World!',
                    args=['foo', 'bar']
                ),
            ),
            badge=42
        ),
        priority=Priority.immediately
    )
    print(f'Sent push notification with ID {apns_id}')
    await client.close()

aapns's People

Contributors

dimaqq avatar freedomofkeima avatar furqanhabibi avatar ojii avatar st3fan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aapns's Issues

NPN protocol does not work

NPN protocol does not initialize even if openssl version is > 1.0.1 (default initialization without explicit ssl_context passing)

aapns version 19.1

Error log:

The NPN extension requires OpenSSL 1.0.1 or later.

System:
Python 3.7.2 Docker, alpine 3.9 and alpine 3.8
openssl versions tested 1.0.2q on alpine 3.8
1.1.1a on alpine 3.9

NPN successfully initialized on mac with LibreSSL 2.6.5

Invalid input ConnectionInputs.RECV_HEADERS in state ConnectionState.CLOSED

Got this error and traceback in production:

ProtocolError
Invalid input ConnectionInputs.RECV_HEADERS in state ConnectionState.CLOSED

h2/connection.py in process_input at line 228

h2/connection.py in _receive_headers_frame at line 1554

h2/connection.py in _receive_frame at line 1486

h2/connection.py in receive_data at line 1463

aapns/connection.py in background_read at line 300

Current code in question is:

while not self.closed:
data = await self.read_stream.read(2 ** 16)
if not data:
raise ConnectionError("Server closed the connection")
for event in self.protocol.receive_data(data):
logger.debug("APN: %s", event)
stream_id = getattr(event, "stream_id", 0)
error = getattr(event, "error_code", None)
channel = self.channels.get(stream_id)

Also, looks like that prod was not running the newest aapns ๐Ÿค”

APNS token-based authentication support (*.p8 file)

Instead of certificate-based authentication Apple also offers stateless token-based authentication, which is recommended way of connecting to APNS. It does not require a client certificate, so it never expires.

Hereโ€™s an article describing this method:
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns

And a post by python-apns author showing the implementation:
http://gobiko.com/blog/token-based-authentication-http2-example-apns/

typo in interpolation

[aapns] Exception: ('Request timed out: %s', 'timeout')

Looks like it should be "xx %s" % foo and not "xx %s", foo ๐Ÿ˜…

Separate request timeout for request and pool

Sometimes, the connection to APN just dies with much data in the pipe, in which case the last sent request is way ahead of last processed response:

Connection(host='api.push.apple.com', port=443, แ  channels={38907: Channel(wakeup=<asyncio.locks.Event object at 0x7f135abdb7f0 [unset, waiters:1]>,แ }, แ closed=False, outcome=None, max_concurrent_streams=1000, last_stream_id_got=38907, last_stream_id_sent=38907

vs.

Channel(wakeup=<asyncio.locks.Event แ  [set]>, events=[<ResponseReceived stream_id:38645, headers:[(':status', '200'), แ ]>, ]

Thus, 131 requests were lost in the void (ฮ”/2 as outbound requests have odd id's).

raw-h2: handle server closing connection

Today

Bad error <ConnectionTerminated error_code:ErrorCodes.NO_ERROR, last_stream_id:0, additional_data:7b22726561736f6e223a22426164436572746966>

The hex translates to {"reason":"BadCertif...

To do

  • report connection being closed
  • decode reason in additional data
  • [possibly] treat this as graceful shutdown, when h2 is ready python-hyper/h2#1181
  • [possibly] refuse to open new connections with same cert, or
  • [possibly] throttle connection open rate, as not to flood the server

raw-h2: pool test / fixture fails

/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py:1854:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <selectors.KqueueSelector object at 0x7fde205bddc0>, timeout = 0.6137869281683797

    def select(self, timeout=None):
        timeout = None if timeout is None else max(timeout, 0)
        max_ev = len(self._fd_to_key)
        ready = []
        try:
>           kev_list = self._selector.control(None, max_ev, timeout)
E           KeyboardInterrupt

/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/selectors.py:558: KeyboardInterrupt
==================================== 13 deselected in 5.90 seconds =====================================
Task was destroyed but it is pending!
task: <Task pending name='bg-write' coro=<Connection.background_write() running at /Users/dima.tisnek/misc/aapns/src/aapns/connection.py:373> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fde18134550>()]>>
Task was destroyed but it is pending!
task: <Task pending name='Task-16' coro=<test_termination() running at /Users/dima.tisnek/misc/aapns/tests/stress/test_pool.py:71> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fde18126070>()]>>
Exception ignored in: <coroutine object test_termination at 0x7fde1811de40>
Traceback (most recent call last):
  File "/Users/dima.tisnek/misc/aapns/tests/stress/test_pool.py", line 72, in test_termination
  File "/Users/dima.tisnek/Library/Caches/pypoetry/virtualenvs/aapns-qVMfpW3G-py3.9/lib/python3.9/site-packages/_pytest/python_api.py", line 715, in __exit__
  File "/Users/dima.tisnek/Library/Caches/pypoetry/virtualenvs/aapns-qVMfpW3G-py3.9/lib/python3.9/site-packages/_pytest/_code/code.py", line 405, in __init__
ImportError: sys.meta_path is None, Python is likely shutting down

raw-h2: connection pool design

Background

@ojii and I have 2 use cases for aapns:

  • batch app badge update (latency is not important; throughput could be, but weirdly is quite small)
  • real-time actionable notification (latency is key)

I've tested 1 connection using the new raw-h2 branch against apple's development server. Here are the results from my home:

  • start python, establish 1 connection, send a few notifications, close connections, quit: 0.83s
  • same as above, +100 requests: 0.98s, thus ๐šซ is 0.15s / 100 req
  • latency for first few requests: 0.12s
  • +100, latency for last few requests: 0.36s

Apple allows up to 1000 concurrent requests per connection, but I can't test that because above ~400 requests to the same device, I start getting 429 status code back ๐Ÿ˜“. The latency in that case jumps to ~1.2s.

The results could be skewed by my having only 1 device and thus testing with 1 device token. It's possible that Apple server serialises requests per device. Apple responds much faster when token is bogus, typically ~0.54s for me.

Specifications

  1. simple use case - few notifications now and then
  2. batch use case - throughput over latency
  3. real-time use case - latency trumps all

I'm thinking following connection [pool] design for these cases:

Simple, connection on demand

  • initialise and enter context, nothing created yet
  • on first request create a connection (this makes 1st request slow)
  • connection is kept around
  • subsequent requests reuse the connection and are fast
  • eventually connection dies (today that means pending requests error out)
  • on next request, create a new connection
  • exit context, connection, if any, is closed

Batch use case

  • initialise and enter context, that creates N connections (perhaps N=1)
  • requests are assigned to connections round-robin
  • a connection may be blocked (has 1000 concurrent requests), then another is used
  • when connections die, they are replaced in the background
  • exit the context, all connections are closed

Real-time use case

  • initialise and enter context, that creates N connections (N>=2, ideally largish N)
  • requests are assigned to connections weighted round-robin
  • a connection does not allow more than e.g. 10 concurrent requests
  • if a connection is busy, another is used
  • [perhaps] there should be a skew to round-robin mechanism, i.e. small preference for older connections, lest perfect round-robin allocation result in simultaneous exhaustion of all connections, resulting in latency spike when all connections are to be replaced at the same time
  • [perhaps] alternatively, connections are retired well before they are exhausted
  • when connections die they are replaced in the background
  • exit the context, all connections are closed

Add readme to pypi description

Today pypi:aapns states:

Project description
The author of this package has not provided a project description

Meanwhile, we've got a nice readme...

Make `click` an optional dep.

Docs state:

Installation
pip install aapns

If you wish to use the command line interface, use pip install aapns[cli]

However, pyproject.toml add click regardless today.
The [cli] part needs to be done.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.