Guest Post: Apple Push Notifications From ColdFusion in Ten Minutes or Less

This post is more than 2 years old.

So I'm not a big fan of guest posts (I'll admit to being something of a blog diva), but after talking about this with Erik Madsen last week I thought it would be great to share. He doesn't have a blog so I offered to post his text here. (I get 1% credit for pointing him towards one URL.) Anyway, enjoy, and thank you Erik for sharing!

Assuming yesterdays news from Apple that third-party development tools for iOS app development are no longer banned, the Adobe developer community will probably be a rapidly growing segment of iOS app developers in the coming months. If you're a FlashFlex developer who's excited about the opportunity to develop for iOS, you'll probably run into the need to provide push notifications for your app's users. I assumed that producing these would be an easy process. I quickly discovered I was somewhat wrong.

Now, I consider myself a developer who manages to do pretty well without actually being as big-brained as some of the folks I look up to. This is what led me to ColdFusion 15 years ago in the first place. Since then I've learned a lot but I still run into my limits now and then. When I run into a road-block, I spend a lot of time on the Google, stackoverflow.com and pestering folks like Ray to lend some of that grey matter.

Anyway, the reason I say all of this is because I hit one of those brick walls of mine when I was faced with producing push notifications from ColdFusion. You see, Apple doesn't provide your typical run-of-the-mill type of restful or wsdl service to create these. Nope: it's a binary, streaming interface that requires TLS or SSL and you have to use present a trusted certificate from their iOS developer program in order to make the connection. This really left me stumped. The Google didn't reveal much of anything. I think one other CF developer was asking around for a wrapper to the service but ended up in the same place as me - empty handed. Ray, being all big-brained, Googled for a Java library for Apple Push Notification Service (APNs) and found what I was looking for. About 15 minutes of work later I had my first push notifications being delivered from ColdFusion to my app on my iPhone 4. Gratifying!

The first thing you need to do is enable APNs (development, production of both) for your app on the iOS Provisioning Portal. Push notifications will not work in the iPhone Simulator. They'll only work for on a provisioned device with an app that has been registered with the iOS Developer Program. Generate a Certificate Signing Request with the Certificate Assistant in Keychain Access, save it to disk and have Apple sign it. A few seconds later you can download it and install it on your development machine.

Then you'll need to export the certificate (option-click the certificate) as a Personal Information Exchange (.p12) file. Place this file in a location accessible to ColdFusion. Remember the password you set as well.

Enabling push notifications within the iOS app itself is really easy. You'll need to register the app for push notifications...

[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];

and have your App Delegate respond to successes or failures for this request...

  • (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken { //The device token is available from [devToken description] //Send this token to your ColdFusion server to track in the database or whatever }

  • (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { //The request failed for whatever reason, tell your ColdFusion server if necessary }

Next you'll need the Java library for communicating with APNs. There's actually two libraries to choose from, but this article will only focus on one - notnoop's java-apns available at http://github.com/notnoop/java-apns. I downloaded the jar file with dependencies. Place this file in a location accessible to ColdFusion and create a Java classpath in the Cold Fusion Administrator to the file, then restart CF. You're pretty much ready to go now.

The first line of CFML will instantiate the APNS object from the library using your APNs certificate and either the production or sandbox destination. My app is actually already running in production mode and waiting for Apple review, so I'll use production.

<cfset APNSService = createObject( "java", "com.notnoop.apns.APNS" ).newService() .withCert("THE-PATH-TO-YOUR-P12-CERT", "YOUR-P12-PASSWORD") .withProductionDestination() .build() />

You'll next need to create a payload that defines the push notification to be sent. The badge, alertBody and sound properties are all optional, but you need to provide at least one. My app uses all three...

<cfset payload = createObject( "java", "com.notnoop.apns.APNS" ).newPayload() .badge(3) .alertBody("Hello, world.") .sound("PushNotification.caf") .build()/>

The third and final line will send the notification to APNs using the device token I saved earlier.

<cfset APNSService.push(token, payload) />

Voila! Push notifications! I had already stubbed out some methods related to push notifications in my ColdFusion app's components, so modifying this code to work with my actual application was quick and painless. My app's users were all getting push notifications in seconds of my publishing the update.

The Java library also provides the ability to request inactive device tokens, which you will want to occasionally check for and maintain in your database, as Apple doesn't want you trying to notify devices that have opted out of push notifications for your app or altogether. From stackoverflow : "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." http://stackoverflow.com/questions/1759101/multiple-iphone-apn-messages-single-connection

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Confinedspace posted on 9/14/2010 at 1:50 PM

Don't suppose you know one for Blackberry Push do you?!

Comment 2 by Daniel Budde posted on 9/14/2010 at 5:20 PM

Now, I have to say, this is pretty cool. Ray, we'll give you a little more than 1% since you host all this great information in 1 place for all of us CF-ers. Thanks for the work Erik, I can see where this will come in handy to many of us in the future.

Comment 3 by Nick Hill posted on 9/14/2010 at 7:44 PM

Great Post from 2 of my CF inspirations. I have a feeling this will become quite the search engine fodder in the days to come. Nice work Erik!

Comment 4 by molaro posted on 9/15/2010 at 9:22 PM

Ray,
This is a dumb question, I'm sure but I thought I'd ask anyway! In a multi-home environment, what should be entered as the server web root value when installing plugins? My setup is:
C:\\Jrun4\
\servers\
\cfusion\

D:\\Inetpub\
\apps\
\CFIDE\
\site1\
\site2\
\site3\
\extensions\ (CFCs outside root for security)
\....
\wwwroot\
\{EMPTY}

I've tried 3 or 4 extensions and have the same issue. I've been entering the path to my "apps" directory where the CFIDE lives, and then I usually install the extension inside the apps directory, sometimes in a "site" directory. The extension installs. Then when I try to run the extension (yours or anybody's), I'll get an error that pages can;t be found. In this case I, I'm in site1, right-click and choose JSONView>Do It, and I get: File not found: /site3/JSON View/handlers/getjson.cfm

There doesn't seem to be any info in the extensions docs about multi-home setups. I'm hoping the answer isn;t that I need to install it for every site I want to use it in (b/c I have about 40 sites).

Can you suggest what path I might enter so I can use extensions?

Comment 5 by Raymond Camden posted on 9/15/2010 at 10:15 PM

That isn't on topic for this thread. Please send in the question via my Contact link above.

Comment 6 by molaro posted on 9/15/2010 at 10:21 PM

Ray, no problem. Doing that now. Thx!

Comment 7 by Stéphane posted on 11/16/2010 at 1:03 AM

Hi guys,

this looks indeed really cool (and I thank you for that because we'll need this very soon), but I've heard apple servers'd prefer lot of info at the same time rather than lot of 'calls' with one info at the time. How would you do to group everything in the same payload, or send everything in a 'batch file'?

Thanks in advance,

Stéphane

Comment 8 by Raymond Camden posted on 11/16/2010 at 1:05 AM

You could store the messages in a db and use a scheduled task to send them out. Ie, if you sent them hourly you can get the messages from the past 60 minutes.

Comment 9 by Stéphane posted on 11/16/2010 at 1:34 AM

Thanks for your quick reply Ray!
thus something like
<cfloop query="qMessages">
<cfset APNSService.push(qMessages.token, qMessages.payload) />
</cfloop>
?
I was wondering if it'd do the trick; I'll give it a try.
Thanks again!

Comment 10 by Raymond Camden posted on 11/16/2010 at 3:59 AM

Yeah, that's what I meant. You may also want to include logic to 'mark' the db row when it is sent so you don't accidentally resend.

Comment 11 by Stéphane posted on 11/18/2010 at 5:07 PM

Hello i'm here again : I've tested but get an issue with the push method : errors says it's not defined (actually it is, but when I dump the object initially created, this method has got one argument, whereas I'm trying to call it with two : token and payload).
I've tried to javacast token and payload as strings so that no conflict with datatypes can occur, but doesn't help... Any clue?

Comment 12 by Raymond Camden posted on 11/20/2010 at 1:38 AM

No idea - sorry. This was a guest post - I haven't done it myself.

Comment 13 by Jim Hankins posted on 12/21/2010 at 10:27 AM

Any examples on using coldfusion with this and the request for inactive devices poll?

Comment 14 by eyemkent posted on 1/4/2011 at 12:41 AM

I'm also wondering if anyone has successfully used this with the request for inactive devices. Any example would be very much appreciated.

Comment 15 by Erik Madsen posted on 1/12/2011 at 1:14 AM

You can poll for inactive devices with this method...

APNSService.getInactiveDevices()

You'll get back a struct of inactive APNsTokens you can use to manage any records you are managing on those devices.

Comment 16 by Tim posted on 2/10/2011 at 1:49 AM

Does this work with CF8? Our CF9 test box works fine, CF8 says it can't find the push method on the last send line.

Comment 17 by Raymond Camden posted on 2/10/2011 at 2:14 AM

Are you sure the Java ob is correct? Try dumping it to see if the push method is there, and if so, what args it takes.

Comment 18 by Tim posted on 2/10/2011 at 2:16 AM

We have tried it on 2 CF9's and 2 CF8's. 9's work fine. 8's give us this:

The push method was not found.

Either there are no methods with the specified method name and argument types, or the push method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that matched the provided arguments. If this is a Java object and you verified that the method exists, you may need to use the javacast function to reduce ambiguity.

Looking further into it. I'll post if I find it.

Comment 19 by Tim posted on 2/11/2011 at 12:38 AM

Seems CF8 needs things a little differently. Team effort, one of our guys and aqx over at experts-exchange.

<cfset APNS = createObject( "java", "com.notnoop.apns.APNS" )>
<cfset APNSService = createObject( "java", "com.notnoop.apns.APNS" ).newService()
.withCert("c:\websites\apns-dev-cert.p12", "joemeade")
.withSandboxDestination()
.build() />

<cfset payload = createObject( "java", "com.notnoop.apns.APNS" ).newPayload()
.badge(0)
.alertBody("Just Checking.")
.sound("PushNotification.caf")
.build()/>

<cfset token = "264ffac53653a20ef657dcff02d5ce70aeb7c53bce0f517a8af08952539987f2">
<cfset APNSNotification = createObject ("java", "com.notnoop.apns.SimpleApnsNotification").init(javacast("string",token),javacast("string",payload)) />
<cfset APNSService.push(APNSNotification)>

Comment 20 by Tim posted on 2/11/2011 at 12:44 AM

My bad: AGX over at experts-exchanged helped debug this.

Comment 21 by Benn posted on 2/15/2011 at 6:33 AM

@Tim
Thanks a lot! That seems to work.

It looks like CF8 isn't recognizing the methods of the abstract class (AbstractApnsService), though, because getInactiveDevices() is also returning a "method not found" error.

Is this a problem unique to this library or is there a better workaround for CF8? My next step will be to re-compile it with an overriding method in ApnsServiceImpl that just calls the abstract class' getInactiveDevices().

Comment 22 by Brandon posted on 5/10/2011 at 12:48 AM

Any updates to the getInactiveDevices() "method not found" issue?

Comment 23 by Benn posted on 5/10/2011 at 2:39 AM

I did some additional research and found that the issue wasn't with CF8. There were some changes with an update to the Java-apns library that caused it to stop working.

I wrote a UDF that works around it, though. You're free to use it if you'd like, although I can't offer any support. At the very least, I hope it gets you closer. I've uploaded the code to my personal site here:

http://www.bennlinger.com/u...

It should return a struct with a mapping of device tokens to dates (representing when they became inactive).

Comment 24 by Brandon posted on 5/10/2011 at 8:18 PM

Just wanted to pass on my gratitude to you all for this post and the speedy response to my question!!!

Special thanks to Raymond Camden, Tim, Benn.

One suggestion would be to incorporate Tim's CF8 code in the article or note that CF8 code is below.

Comment 25 by R. Graf posted on 5/11/2011 at 7:00 AM

Hello, thank you for this interesting topic and also for the comments. I'm working with CF 8 on Win 2008 / JRE 1.6.24 and tried the code from comments no 19+23, but keep getting this error when performing the final function call:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

What am I missing here? Thanks in advance!

Comment 26 by Benn posted on 5/11/2011 at 7:07 AM

I got that message when my certificates were bad. In my case, they were either expired or invalidated through the provisioning portal.

In your situation, I'd first try re-downloading the certificates from the provisioning portal (and converting to .p12 as shown above). If that doesn't work, I'd create an entirely new App ID with push notifications enabled and try its certificates as a sanity check. If that works, then you know it's an issue with the other app's configuration on the provisioning portal.

Aside from that, I'm not sure what to tell you. That error is just telling you that Apple is rejecting your certificate.

Comment 27 by R. Graf posted on 5/13/2011 at 12:26 AM

Thank you - turred out that i had the wrong certificate, now everything is running fine!

Comment 28 by Carey Richardson posted on 5/19/2011 at 2:26 AM

I have been struggling getting this to work. I finally have all my classpaths correct and I'm getting this error:

The push method was not found.

Either there are no methods with the specified method name and argument types...

Here is all the code in my testPush.cfm file:
<cfset APNSService = createObject( "java", "com.notnoop.apns.APNS" ).newService()
.withCert("C:\apns-dev-cert.p12", "000000")
.withProductionDestination()
.build() />

<cfset payload = createObject( "java", "com.notnoop.apns.APNS" ).newPayload()
.badge(3)
.alertBody("Hello, world.")
.sound("PushNotification.caf")
.build()/>

<cfset APNSService.push("8367252c 52b31eee 9fcd2c00 7500725f 42520649 c5d8889a e10cc066 6bd73efd", payload) />

Help, please...

Comment 29 by Carey Richardson posted on 5/19/2011 at 2:56 AM

I actually didn't read a few posts up. I copied their code and it worked perfect. Thanks.

Comment 30 by RKA posted on 5/23/2011 at 1:59 PM

Hi,
What will be the element names in the structure returned by getInactiveDevices() method? Does anybody implemented this?

Comment 31 by RKA posted on 5/24/2011 at 4:43 PM

With regard to comment 23,
Benn,
I tried to implement the inactive device code posted in your site(bennlinger.com).
A push notification was sent initially and uninstalled the application from the device later. Then , I just passed the certificate path and password used to the function for checking inactive device, but it is returning an empty structure.
Any idea?

Comment 32 by Andrew posted on 6/4/2011 at 8:58 PM

Has anyone tried using the "deviceTokens" parameter to send to multiple devices at once? The JavaDocs show that it is possible by passing in a "Collection<String>".

Comment 33 by Kenny posted on 6/28/2011 at 7:26 AM

What encoding is the CF api expecting for the device token? On the Objective-C side, I Base64 encoded the NSData and then passed that off to my server. Should the CF decode it and re-encode it as something else? The api accepts a String token and String payload, how should the String token be encoded?

Comment 34 by Benn Linger posted on 6/28/2011 at 4:33 PM

You could check the JavaDocs for the third-party library to be sure, but if it's accepting strings, I'm fairly sure they're Base64 encoded strings.

Comment 35 by Kenny posted on 6/28/2011 at 8:57 PM

It turns out that the API above is expecting hex encoded strings.

http://stackoverflow.com/qu...

Comment 36 by mael94420 posted on 7/19/2011 at 1:22 PM

Hello,
First, thanks for your code.
Second, I had a problem with the push method. I used the Tim code and it work better. However, now, i've got this error :
"java.net.NoRouteToHostException: No route to host: connect "
and I really don't know why. It's first time for me to use a java class and I don't understand this error. Is there a network problem?
Path of my p12 is right. I tried the easy way with c:\mycert.p12, I tried a path accessible directly from my website and an other a little bit more secure but no way...
If someone have any idea, i'll be very grateful.
Thanks for your answers
Maël

Comment 37 by Eric Tippin posted on 8/2/2011 at 8:47 PM

Assumed if I have an alerts table with message (checked = 1 or 0 = new). Can you please show me to write a simple Apple App would would display the message with checked flag = 0 that would call APN to send push notification.

Would be highly appreciate if you could show us how to write this simple app, assume that we have everything is setup or as needed (as far as the development concern).

Thank you in advance. Awesome works on the APN push notification via iPhone app.

Comment 38 by John Prince posted on 2/4/2012 at 7:12 PM

Did anyone figure out APNSService.getInactiveDevices()?

I'm still getting back method not found.

Comment 39 by John P posted on 2/9/2012 at 6:44 PM

Finally got inactive devices to work!! Thanks to this blog and the link posted above: http://www.bennlinger.com/u...

Also, if it returns an empty struct, that's probably good -- It won't return anything until after you attempt a notification. IE, You send notifications out and then they will return which ones fail. They don't collect those ids as the person uninstalls, they collect them after you attempt to deliver a message.

Comment 40 by Dheeraj Wadhwa posted on 4/9/2012 at 12:35 PM

we can't call this method mapper.writeValueAsString() in org.codehaus.jackson.ObjectCodec package
please tell some solution what i will do

Comment 41 by Talal M. posted on 9/13/2012 at 7:35 PM

How to do the same for android please? Is there any tutorial same as above for android push using coldfusion ?

Comment 42 by bindupavan posted on 3/19/2013 at 4:52 PM

Thanks a lot Raymond

Comment 43 by Adil posted on 4/4/2013 at 9:57 PM

I couldn't find any JAR file at: http://github.com/notnoop/j...
Can you please help me with that?

Comment 44 by Jim Hankins posted on 4/4/2013 at 11:05 PM
Comment 45 by Adil posted on 4/9/2013 at 8:03 PM

@Jim Hankins Thanks
Can you please let me know if I have to do anything in CFAdmin except creating classpath? Do I need to import p12 file as we do in case of pem files?
Because currently I am getting the following error:
Security: The requested template has been denied access to ../JAR/DevPushCertificates.p12

Comment 46 by Adil posted on 4/10/2013 at 3:51 PM

I fixed the Access Rights issue. But now when I run the CF code mentioned above, it shows me plain while screen and no code bellow this code is executed. It seems there is some exception but I cannot see that. I have written the code in try catch but still I cannot see the exception. Can you please let me know how to debug this issue?

Comment 47 by Adam posted on 2/21/2014 at 5:39 AM

Just a note incase anyone is having the same problem I had. When you export your .p12 file from the Keychain Access. You may need to select both the certificate and the key at the same time and then select 'Export' 2 items to make a .p12 file.

Comment 48 by Eric Belair posted on 8/8/2014 at 5:11 PM

I'm just starting a new project to enable Push Notifications on an existing iOS app, that another consulting firm developed. In addition, the app is actually compiled and distributed as a enterprise app by one of our clients. So, this is not "my app", and I'm not sure where to start. I also need to be able to send certain messages to certain users - how can I link device ids to users?

Comment 49 by Eric Belair posted on 9/18/2014 at 12:50 AM

Ignore my last question. I'm actually starting to develop this now, and right off the bat I'm getting a strange exception:

<cfset LOCAL.APNS = createObject( "java", "com.notnoop.apns.APNS" ) />

<cfset LOCAL.APNSService =
CreateObject(
"java",
"com.notnoop.apns.APNS"
).newService(
).withCert(
"MyCert.p12",
""
).withSandboxDestination().build() />

java.io.IOException: failed to decrypt safe contents entry: java.lang.ArithmeticException: / by zero

Stack Trace:

com.notnoop.exceptions.InvalidSSLConfig: java.io.IOException: failed to decrypt safe contents entry: java.lang.ArithmeticException: / by zero
at com.notnoop.apns.internal.Utilities.newSSLContext(Utilities.java:102)
at com.notnoop.apns.ApnsServiceBuilder.withCert(ApnsServiceBuilder.java:139)
at com.notnoop.apns.ApnsServiceBuilder.withCert(ApnsServiceBuilder.java:114)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at coldfusion.runtime.StructBean.invoke(StructBean.java:511)
at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2300)

Any ideas?

Comment 50 by Eric Belair posted on 9/18/2014 at 1:22 AM

Nevermind, it was due to the fact that the apns lib requires the cert to have a password.

Comment 51 by Eric Belair posted on 9/30/2014 at 7:38 PM

I am unable to call getInactiveDevices() in ColdFusion 8. The link above no longer works. I've started a SO post (http://stackoverflow.com/qu.... Really need help here.

Comment 52 by Jerry Aguilar posted on 10/10/2014 at 10:41 PM

My test doesn't work :(

Just in the line where I try to initialize the pusher component:

<cfset pusher = createObject("component","dasien.componentes.pushservices.pusher").init(
mode = "development",
appleCertificatePath = "#EXPANDPATH('componentes/pushservices/Certificados.p12')#",
appleCertificatePassword = "XXXXXX",
googleAPIKey = "XXXXXX"
)/>

The script stops and the server returns a blank page with no detail about the fail but with error code 500.

When I go to the Application server log I find this:

"Could not initialize class com.notnoop.apns.internal.Utilities"

However if I extract all the content of the jar file, the Utilities class is actually there. Hope you can help me :)

Comment 53 by Ian O'Sullivan posted on 9/24/2015 at 1:07 PM

Erik and Ray. Thank you so much! Heros! ;-)

Comment 54 (In reply to #4) by dawesi posted on 2/11/2018 at 11:19 PM

I know this is old (i need to write a new article with new libs), but this response may confuse people who still see this through google searches. #sigh ..

Before coding coldfusion apps, you should read the 101 docs for coldfusion about where to put things.

No such thing as extensions directories (as in a fixed place). In this case just setup 'custom tag paths' perhaps (not necessary but for 40 sites might be worth it). BTW path should be either relative to executing dir or a mapped path. All in basic CF docs.