Adam Tuttle

Idiosyncrasies of using CFContent to send files to the client in ColdFusion

I've found some idiosyncrasies in using CFContent to deliver files to the browser with ColdFusion (e.g. shielded downloads), so I thought I would share them here.

I have only tested what I'm about to share in ColdFusion 10, but I have no reason to believe that it will be any different with other versions, as it appears to be entirely how things are handled client-side.

I can never remember what the appropriate header name is for specifying the file name, so the first thing I did was google for it. That took me to Ben Nadel's blog post on streaming files -- a slightly different matter, but it had the answer I was after:

<cfheader name="content-disposition" value="attachment; filename='filename.xlsx'" />

This worked great, at first, in Chrome. When I got around to various browser tests, I found that if my file name contained a space, as in file name.xlsx then Firefox would save it as 'file (no extension -- nothing after the space). Equally as odd, Safari got the name correct (file name.xlsx) but appended .html.

Changing the single quotes to double quotes fixes FireFox's quirk, and adding a Content-Type header fixes Safari's, so the final result looks like this:

<cfheader name="Content-Disposition" value="attachment; filename=""#rc.fileName#""" />
<cfheader name="Content-Type" value="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
<cfcontent reset="true" file="#rc.filePath#" deletefile="true" />

It's 2014... We can make an almost-convincing hoverboard hoax but we still need to test everything in every browser on every platform.

Published 2014-07-18 @ 09:05 in ColdFusion

CFHTTP SSL Stopped Working after OSX Mavericks Upgrade? Here's Why!

Like so many before me, I've recently been banging my head against issues with CFHTTP and SSL, particularly only on Mac OS X. Eventually I landed on the right Google phrase and read enough links to find this gem, which while only tangentially related, brought my memory back in a flash.

Apple, in their infinite wisdom, decided to stop distributing Java with OSX. And I hadn't realized it at the time that I upgraded, because ColdFusion continued to function, but the upgrade deleted much of the Java JDK, including (you're probably guessing right...) the cacerts file. Not even Verisign signed certificates are trusted if you don't have any cacerts file.

It wasn't until I figured out that my cacerts file (and many other bits of the JDK) were missing that I found the Jira ticket that solved the puzzle.

Thanks, Apple!

Here's what you can do about it: ColdFusion 10 now officially supports Java 7 (as of update 8), so you need to upgrade.

Being a fan of minimalism (e.g. not installing tons of useless junk) I opted to download the Java SE 7 JRE package -- because the JRE is for people who want to RUN java apps, while the JDK is for people who want to WRITE java apps -- for OSX, and installed it; though I was perplexed that it didn't let me choose an install location, or even tell me what it was after it was complete. I had to find out via Stack Overflow -- and as indicated in the comments there, using a JRE installed into the Internet Plugins folder to run your app server isn't exactly the brightest idea.

Right then... Thanks, Oracle!

With that great idea flushed down the toilet, I gave up any hope for sanity and downloaded the Java SE 7 JDK. Again, it didn't let me choose an install location, or bother telling me where it installed. Thankfully, someone else in the same Stack Overflow thread mentioned that the command /usr/libexec/java_home -v 1.7 will let you find the location of an installed JDK by version number; and it turned out that mine was installed to: /Library/Java/JavaVirtualMachines/jdk1.7.0_60.jdk/Contents/Home.

Next we need to tell ColdFusion to use this new JDK instead of the kneecapped one that Apple left behind during the Mavericks upgrade. ColdFusion gives us a decent UI for updating the value, but stores it in the file {CF_ROOT}/cfusion/bin/jvm.config, so make a backup of this file before you change anything. And don't delete your old JDK yet. If something goes wrong, you can just put back your backup of jvm.config to point CF back at the old JDK.

With your backup made, open your CF Administrator and navigate to Server Settings > Java and JVM. Next to Java Virtual Machine Path tap the Browse Server button. (Sure, you could paste it in, but why risk a stray space or missing slash, etc screwing things up?) Find the folder listed as the result of the java_home command earlier and select it.

Then just restart ColdFusion and you're good to go! (If you're not, copy your backup of jvm.config back into {CF_ROOT}/cfusion/bin/jvm.config and restart CF again...)

Verify that you're now on Java 7 by clicking the small "i" icon in the upper-right of the CF Administrator, and checking the value of Java Version -- you want to see 1.7.0_60 or later.

I hope I never have to go through that again, and I hope maybe it helps a few of you out there.

Published 2014-06-11 @ 08:25 in Apple ColdFusion

What It's like to Write Event-Companion Mobile Apps

Writing event-companion apps (conference schedule, session reviews, maps, social integrations, etc) can be a fun and rewarding way to make a living. I've been doing it now for about 3 years and I would say without any hesitation that I love my job.

But it can also be HELL. So I thought I would write about it.

I've been told by numerous doctors that women don't remember the pain of child birth, because if they did then nobody would ever have more than one child. (Anecdotally, despite not having any anesthesia at all, my wife can't recall the pain during our first child's birth...) It's a theory that makes a certain amount of sense to me, so I'm just going to run with it.

For the same reasons, I think compartmentalization is an important skill for programmers. If we could remember the frustration and pain of debugging every missing semicolon, every browser inconsistency, every fat-fingered data input, every off-by-one bug, every memory leak... We would all probably go do something else.

But we don't, because the highs of success are so elating.

These are some of the lows.


Apple Sucks

And I don't mean from the perspective of UI/UX, hardware, OS, or any of that crap. That's a flame war better had over drinks and in person. No, what I'm talking about here is the process of getting an app into the app store.

First you have to get everything to compile and code-sign correctly. Certain stuff has to be in your computer's Keychain, other things need to be referenced from XCode / CLI arguments. The documentation is almost as bad as trying to understand the jargon used in the American Legislative process.

Then you've got to configure your app in iTunes Connect, literally the worst Web App ever created. With urls like this: (an actual url to the summary page of one of my apps)

https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/wo/5.0.0.13.7.2.7.9.1.1.3.2.3.3.1.3.0.4.1.1

I don't know what WebObjects is, but I'm assuming it's a web framework, and if I ever see it on a job posting, I'm going to run far, far away.

The policies built into the app are arcane and at times mystifying. For instance, once a binary is available in the App Store, you can not change the name of the app as viewed in the App Store. For example, if you submitted with, "Reunion" and you want it to say, "Reunion 2014," you must upload a new binary. It doesn't have to have changed at all, except you have to change the version number.

So that you can change the App Store listing.

Seriously.

What would make more sense? Allow changing it, but require moderator approval. I get that it's there to protect customers from potential abuse -- so just make sure we're not changing "Reunion" to "OMG Best Flashlight Tethering App Ever" and move on. That approval should take about 30 to 60 seconds, depending on how drastically the name has changed.

And while we're on the moderation queue... Why the heck does testing my app and approving it, a process that should take less than an hour, have to wait in line for between 3 and 10 business days? Google doesn't seem to be suffering too much from their automated heuristics that run in lieu of Apple-style human moderation, and my app updates go out in minutes, or at the worst, hours, after I've uploaded them. That gives me, the developer, the ability to respond to customer feedback quickly. A 3-10 business day review queue makes me look bad.

To their credit, wait times have been trending down, and every now and then you'll get lucky and get one reviewed same-day.

And the review process is not First-In-First-Out, either. I've got an app update "Waiting for Review" right now, which I uploaded on June 1st. I've also got an app update that I uploaded on the morning of the 3rd and that has been "In Review" since 34 minutes after upload. Shouldn't apps uploaded on June 1st be reviewed before apps uploaded on June 3rd?

Sure, you can request an expedited review, but if you're unlucky enough to get someone that's in a bad mood reviewing your request, it will be arbitrarily rejected with no explanation.

Allegedly the review process is for the sake of the security and privacy of end users, but clearly it's a broken system. It's security theater. Apple is the TSA of mobile app clearinghouses.

None of this is to claim that Google is perfect. The Play Store has its share of problems. But it stays out of my way and allows me to get updates to my users in a timely manner; something that Apple just won't allow.


Third Party APIs Suck

And usually each one sucks in a new, different, magical way.

LinkedIn's API only allows the app to use the OAuth connection tokens for 60 days. Unless you want to redirect through the LinkedIn login page every time your app starts, hopeful that the user's cookie will still be logged in, so that the key will be refreshed (guess what: it won't for mobile apps, because they don't set a cookie), then you only have 60 days before the user will need to log into LinkedIn again, if you're to continue using the LinkedIn API to provide value to your user. If your event is only over a single weekend, this isn't a big deal. If you're writing an app that will be useful year-round, don't plan on getting much value out of LinkedIn without annoying your user every other month.

Facebook's API changes more frequently than the site design, and usually breaks stuff. They refuse to acknowledge bugs where things work differently between iOS and Android. And they have their own definition of how OAuth should work, so it's not like anyone else's.

Twitter's API, from a development standpoint, is not terribly bad. They don't allow CORS requests, which I don't think is a problem for native-app or server-side requests, but is a huge problem for PhoneGap apps. Personally I get around this with a copy of CodeBird acting as a CORS-friendly proxy running on Heroku. So you'll launch your app with Twitter support and you'll do ok. Then one day you'll wake up to an email that says your API Key has had its write access disabled because you "broke a rule in the API TOS." Have you looked at the API TOS? There are hundreds of rules and some of them can be broken in multiple ways. And they don't bother to tell you what you did wrong. Oh, and they don't care that your event started last night and users are angrily shaking their fists at you right now because they can't tweet from your app. I did find their response to my appeal to be within a reasonable time frame (a few hours), but I don't know if my request to @Support expedited that review at all. Apparently they use an automated tool to sweep for infringing apps, and it's possible to get picked up as a false-positive. That's what happened to me.

And then there are other APIs, provided by your customers or their 4th party vendors. Nearly always those suck, too. Slow. No support for batch processes or searching. Latitude and Longitude delivered as {longitude},{latitude}. The list goes on and on.


Nobody Tests

Under penalty of death, I still don't think you can get adequate testing from customers. Until the night before the event starts. And then invariably they find some tiny little detail that requires submitting a new build to Apple. So you wait with fingers crossed and hope it goes out in time.


At least for me, these days, programming is about taking numerous other api's and combining them in interesting and useful ways; maybe adding a little sugar to the UI to enhance the experience. When that plan comes together, it's a thing of beauty... But until it does, you'll be cursing like a sailor.

Published 2014-06-05 @ 08:30 in Mobile

Creating Cordova (PhoneGap) Android Prod/Dist Builds on the CLI

My development workflow has been kind of ephemeral lately. We've been using PhoneGap and PhoneGap:Build for quite some time, but we recently started working with Estimote's iBeacon-compatible devices for an upcoming event; and the closed-source Estimote SDK is a dealbreaker for the Build service.

This was a particular pain point for me, having never built PhoneGap apps locally. I was -- and to some extent still am -- a complete Xcode Noob. With enough Google, Stack Overflow, and IRC trolling I was able to get through all of that, but what seemed even less documented was how to make a release build of my android application and automate its signing. Eventually I found that on Stack Overflow too, so I thought I would document it here for you (and future me).

The CLI command to compile a release build for Android is:

cordova build android --release

If you notice, the output of this command, very near the end contains:

-release-nosign:
   [echo] No key.store and key.alias properties found in build.properties.
   [echo] Please sign /Users/adam/.../platforms/android/ant-build/Reunion-release-unsigned.apk manually
   [echo] and run zipalign from the Android SDK tools.

The solution, as I found on Stack Overflow, is to create a file named ant.properties inside the platforms/android/ folder. In it, put two lines:

key.store=/Users/adam/.android/your.keystore
key.alias=your_alias

The first line should have the full path to your .keystore file, and the second line should contain the name of your key alias.

After doing so, the build process will prompt you for your keystore password (twice) during the build and use it to sign and automatically zipalign your binary. Then you'll find it at platforms/android/ant-build/{APPNAME}-release.apk

This .apk file is ready to be submitted to the Play Store.

Published 2014-05-20 @ 08:30 in Android PhoneGap