views:

1042

answers:

2

I have a strange issue. I'm trying to get Apple Push Notifications working with Python. I can connect and send individual messages without a problem. The issues pop up when I start sending more than one message, but it's more bizarre than even that.

I'm testing with multiple devices... some iPhones and some iPod Touches. I can send multiple messages to the iPhones without a hitch, but if I have an iPod Touch device id in the list, any message that goes after will fail.

So if I send 4 messages in sequence like this:

1 - iPhone
2 - iPhone
3 - ipod Touch
4 - iPhone

1 and 2 will be delivered, 3 and 4 fail.

Using the same device ID's, if I move any of the iPod Touch device ID's to be the first message, all messages will fail. Likewise, if I only send to iPhones, all messages will succeed.

Here's the code I'm testing with, in it's current state, I would only get the first two messages, the last two will fail every time.

import struct, ssl, json, sys, time, socket, binascii
from optparse import OptionParser

class PushSender(object):

    def __init__(self, host, cert, key):
        self.apnhost = (host, 2195)
        self.sock = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM),
                                    keyfile = key,
                                    certfile = cert,
                                    do_handshake_on_connect=False)
        self.sock.connect(self.apnhost)
        while True:
            try:
                self.sock.do_handshake()
                break
            except ssl.SSLError, err:
                if err.args[0] == ssl.SSL_ERROR_WANT_READ:
                    select.select([self.sock], [], [])
                elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
                    select.select([], [self.sock], [])
                else:
                    raise

    def send_message(self, token, message):
        payload = {'aps':{'alert':message}}
        token = binascii.unhexlify(token)
        payloadstr = json.dumps(payload, separators=(',',':'))
        payloadLen = len(payloadstr)
        fmt = "!BH32sH%ds" % payloadLen
        notification = struct.pack(fmt, 0, 32, token, payloadLen, payloadstr)
        self.sock.write(notification)
        self.sock.


    def close(self):
        self.sock.close()

def main():
    parser = OptionParser()
    parser.add_option("-c", "--certificate", dest="cert",
                      metavar="FILE",
                      help="Certificate file", )

    parser.add_option("-p", "--privatekey", dest="key",
                      metavar="FILE",
                      help="Key file", )
    parser.add_option("--host", help="apn host", dest='host')
    (options, args) = parser.parse_args()

    sender = PushSender(options.host, options.cert, options.key)

    iphone1 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    print 'Sending iPhone #1 a message.'
    print sender.send_message(iphone1,'Hey iPhone #1.')

    iphone2 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    print 'Sending iPhone #2 a message.'
    print sender.send_message(iphone2,'Hey iPhone #2.')

    ipod1 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    print 'Sending iPod #1 a message.'
    print sender.send_message(ipod1,'Hey iPod #1.')

    iphone3 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    print 'Sending iPhone #3 a message.'
    print sender.send_message(iphone3,'Hey iPhone #3.')

    sender.close()

if __name__=="__main__":
    main()

Any help would be appreciated...

+3  A: 

Apple will silently drop your connection if it receives an invalid device token or a message that's too long. The next couple of messages after that will fail because they're just sent into the ether, essentially - the connection is closed, but the TCP window isn't exhausted.

At Urban Airship, where I work, we have a debug mode to use when people test their applications with push on our service. This will pause for a little bit after sending a message to ensure that this wasn't the issue - if the connection drops, we know it's an issue with the device token and report an error as such. A similar method might be a good way for you to check that this is, or is not, what's going on. Obviously this kills throughput and so we don't recommend it for a production environment.

Michael Richardson
Michael, thanks for the detailed response, I suspect you're correct. This problem stemmed from the fact that we were storing all a user's device token's in an attempt to support multiple devices per user, since we were attaching a device token to a user ID rather than a unique device id, we introduced the possibility of invalid tokens being stored since a device token can change.
Sangraal
Also, thank you for suggesting a way I can test the tokens we already have for validity, that was another problem I was worried about. I have noticed when sending many messages with an invalid device token first, the connection would drop a few messages later. That's the behavior you're describing above.
Sangraal
I should also add that Apple considers a development device token sent to a production server to be an invalid device token (and vice versa). This issue comes up fairly often, then, if you mix development device tokens and production device tokens.Hope this helps!
Michael Richardson
Our problem was resolved, it turned out we had a few development id's that were stored in our production DB that caused the behavior.
Sangraal
A: 

I am also facing the same problem , device tokens that are sent to server are valid, I can confirm that because when I sent a single notification to a particular device token/device it goes perfectly but as I tried to send multiple notifications using single connection it just sends the first notification.

$apnsHost = "gateway.sandbox.push.apple.com"; $apnsPort = 2195; $root_dir = sfConfig::get('sf_root_dir'); $apns_dir = $root_dir."/apns/"; $apnsCert = $apns_dir."apns-dev.pem";

$streamContext = stream_context_create(); stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert); $apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);

foreach($messages as $message) { $deviceToken = $message['device_token']; $alert = $message['alert']; $badge = ''; $city_id = $message['city_id']; $payload['aps'] = array('alert' => $alert, 'badge' => $badge, 'sound' => 'a.aiff','type'=>$type,'city_id'=>$city_id); $payload = json_encode($payload);

$apnsMessage = chr(0).pack('n',32) .pack('H*', str_replace(' ', '',$deviceToken) ).pack('n', strlen($payload)).$payload;

fwrite($apns, $apnsMessage); } fclose($apns);

satyendra