Talk Funnel

Ramin Firoozye’s Public Whisperings

Archive for the ‘push’ tag

Problems with Push

without comments

pushsad.png

I agree with most everything Karl Adam says about the limitations of the Apple Push Notification Service, especially the problem with its failure to stack notifications so they’re not missed.

I posted a bug report a while back (rdar://7054632) offering a simple solution to get around this particular problem: save each incoming push payload into Messages.app as a separate entry. That way if I get a push and don’t have time to get to it I can ignore it and come back to Messages later on and retrieve it and all received Push messages are kept until I choose to get rid of them.

The entry could be in the form of a special URL link that shows the alert message, but when clicked generates the same JSON payload format as a regular push event and invokes the app in the same manner so no extra coding would be needed (OK, maybe just a little bit of code on the server to check against processing duplicate requests). It would take care of a lot of problems with push usability.

An even more pressing issue I have with Push is if you are on a WiFi network behind a bunch of firewalls and more than one NAT server. This happens often in corporations or in homes with multiple routers acting as range-extenders. In these cases pushes fail to reach you — until you get back to a 3G network.

For some people Push is doubling as a remote event timer (since Apple won’t let us access the phone’s alarm database or submit local cron tasks). This makes it really hard to issue reliable time-based alerts.

If Apple would just open up true background tasks and/or timed alerts and let the user decide whether they trust an app to let it access those services (much like location-based or push services) a lot of these hassles would go away.

Also, a European friend brought up that whereas SMS is included in most phone plans, push incurs data usage charges. Could be a hassle if you’re traveling and continue getting pushes.

Over all, I’d say push on the iPhone is a work in progress. As much as I’m intrigued and excited by its potential, I’m frustrated by its current implementation and limitations.

Written by ramin

September 30th, 2009 at 1:51 am

Posted in Apple, iphone

Tagged with , ,

Push Notification and Python (Django)

with 4 comments

I’ve been trying to get the iPhone Push notification services introduced in OS 3.0 working with Python (specifically, Django). It took a while but I figured I’d post my notes so it’ll spare someone the suffering I had to go through.

Other attempts at this (apns-python-wrapper (aka APNSWrapper) and Lee Packham’s code) both use the standard ssl libraries which I couldn’t get to work under Snow Leopard and Python 2.6.

I eventually got it going with Django and pyOpenSSL libraries. The easiest way is to get the prebuilt pyOpenSSL binaries from eGenix. If the test server is running on Snow Leopard the Python 2.6 UCS2 version seems to work best.

The thing about pyOpenSSL is that it expects the certificate and private key to be in separate .PEM files so you have to export each one from KeyChain Access in .P12 format then convert them to .PEM from the command line. For the development key it’s easier to specify the password during export then strip it out on the command line. In production you may not want to do that.

   % openssl pkcs12 -clcerts -nokeys -out devcert.pem -in devcert.p12
   % openssl pkcs12 -nocerts -out devkey.pem -in devkeypw.p12
   % openssl rsa -in devkeypw.pem -out devkey.pem

Now copy devcert.pem and devkey.pem (with no password) into the server directory.

The following bit of code is in a file called PushSender.py and does the actual talking to the push server (I heavily edited out project-specific bits and standard exception and error-handling stuff. Hopefully didn’t bork it too badly ;-)

Also, in this example the actual communication runs in a thread. In some cases it might make more sense to run it as a main routine:

import os, sys
import struct, binascii, ssl, datetime
import threading
import simplejson as json
from socket import socket
from OpenSSL import SSL

class PushSender(threading.Thread):
def __init__(self, sandbox, token, message, badge, sound):
	super(PushSender, self).__init__()
    		self.token = token
    		self.sandbox = sandbox
    		self.message = message
    		self.badge = badge
    		self.sound = sound
    		self.ctx = SSL.Context(SSL.SSLv3_METHOD)
    		if sandbox:
    			self.apnHost = "gateway.sandbox.push.apple.com"
    			self.ctx.use_certificate_file(os.path.join(PROJECT_ROOT, "devcert.pem"))
    			self.ctx.use_privatekey_file(os.path.join(PROJECT_ROOT, "devkey.pem"))
    		else:
    			self.apnHost = "gateway.push.apple.com"
    			self.ctx.use_certificate_file(os.path.join(PROJECT_ROOT, "prodcert.pem"))
    			self.ctx.use_privatekey_file(os.path.join(PROJECT_ROOT, "prodcert.pem"))

    	def run(self):
    		payload = {}
    		aps = {}
    		if (self.message):
    			aps["alert"] = str(self.message)
    		if (self.badge):
    			aps["badge"] = self.badge
    		if (self.sound):
    			aps["sound"] = str(self.sound)

    		payload["aps"] = aps

    		token = binascii.unhexlify(self.token)
    		payloadstr = json.dumps(payload, separators=(',',':'))
    		payloadLen = len(payloadstr)
    		fmt = "!cH32sH%ds" % payloadLen
    		command = '\x00'
    		msg = struct.pack(fmt, command, 32, token, payloadLen, payloadstr)
    		sock = socket()
    		s = SSL.Connection(self.ctx, sock)
    		s.connect((self.apnHost, 2195))
    		s.send(msg)
    		s.shutdown()
    		s.close()

[ PushSender source ]

To invoke it in a thread (the first param is True for sandbox and False for production server) from the main routine:

	from PushSender import PushSender
	...
	pushsender = PushSender(True, token, pushmessage, pushbadge, pushsound)
	pushsender.start()

On the phone the token returned by the didRegisterForRemoteNotificationsWithDeviceToken method is in the form <xxxxxxxx xxxxxxxx ...>. To pass it along to the server you have to strip out the <> and spaces. The unhexlify method then converts this string into a 32-byte hex binary value. The easiest way to strip out the extraneous stuff is to just do it on the client-side:

    NSString *deviceToken = [[[[tokenString description]
			stringByReplacingOccurrencesOfString:@"< " withString:@""]
			stringByReplacingOccurrencesOfString:@">" withString:@""]
			stringByReplacingOccurrencesOfString: @" " withString: @""];

One last thing: at least with the sandbox if the phone is running on WiFi sometimes push notices don’t come through. This may be due to router NAT or firewall configuration issues. One way to check is to go into XCode Organizer while the phone is tethered and run your app, then under the Organizer Console you can check for errors. To get around this you’ll want to turn off the WiFi on the phone and go with 3G. This usually makes push notices arrive as expected (and strangely enough the WiFi method goes back to working for a few minutes).

There’s more that needs to be done to make it production-ready but at least it’s good to know it’s doable:

cp18.png

 

Written by ramin

September 9th, 2009 at 4:36 am

Posted in Tech

Tagged with , , ,