Posts Tagged ‘rails’

Dynamic Tunneling for your Facebook App

Sunday, April 19th, 2009

When I was launching Forgiveness, my first attempt at a Facebook app via RoR, I ran into one of the typical developer quandries. How the heck to I actually make local revs to the app and proxy them through the FB back-end while still keeping the app running live for my millions of satisfied customers?

I sought advice from Steve Enzer, and his answer was ‘create a separate FB app id’. Yep, you can do that. I’m sure it works just fine. However, I wanted to come up with a single-app solution, you know, just because.

My app backbone was based upon Facebooker, a great gem for just such purposes. And assuming that you’re using that gem, you’re provided with:

rake facebooker:tunnel:start

Which starts up the traditional server tunnel to your local dev box; Facebook’s proxy won’t know the difference. I’ve also set up my canvas page URLs and the like with a fixed IP — according to FB’s best practices, because they don’t want to deal with all the DNS resolution stuff — and it listens on port 80 with a dedicated context. So, I’ve got my facebooker.yml set up for port 3100:

tunnel:
  public_host: cantremember.com
  public_port: 3100
  local_port: 3100

And here’s my nginx config:

Here I omit the upstream cluster configs, and I’ll let the comments speak for themselves … I always try to keep track of red herrings at least at some level, and there were several I bumped into while trying out this solution. The best approach that I found was to simply set a cookie with a naming convention that nginx could parse (yes, there is a regex performance penalty here). The development context is routed locally, which is where the tunnel is waiting to pick up and run with it.

Setting the cookie, well that’s another trick. Though not much of one. Of course your app is completely masked behind the apps.facebook.com domain, so trying to set a cookie to your internal domain (eg. IP) through your browser isn’t so easy, and it’s repetetive manual work. So, just bake it into a dedicated action in your app:

Then use /:controller/tunnel/1 to engage, and /:controller/tunnel/0 to disengage. A little bit of auth logic, and you’re good to go.