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:
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:
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.
I recently double-checked my nginx configuration against the one that Elastic Dog has so proudly featured. I’m very glad that I did — they provided me with a better understanding of the if / test capabilities of the syntax.
That being said, it still needed some adjustments …
I’m currently running WordPress 2.7 under nginx 0.7.27. Here’s my end configuration:
I’ll provide the content of extra/proxy.conf and fastcgi_params down below — they won’t surprise you — plus the configuration for my upstream fastcgi_cluster.
The purpose of DOMAIN.NAME and LOGFILE is obvious, so let’s skip to the useful stuff.
This is simply the fully-qualified path to the directory where you have installed WordPress. Big shock, I know. I put mine in ‘/var/www/wordpress’.
Specifically, I’m referencing the ‘WordPress address (URL)’ capture block.
WordPress 2.7 supports a differentiation between the root context of your blog and the root context of the WordPress resources themselves. I’ve taken this approach … the URL of this blog post is root-relative to my virtual hostname, but if you do a View Source you’ll see:
Blog address (URL) = http://blog.cantremember.localhost
So my WP-CONTEXT is ‘wordpress’.
Here are the core differentiations between my config and the Elastic Dog one:
My core two sections are the ones with WP-CONTEXT. Before doing anything, I make the $request_filename context-less, so that it’s corrected relative to root. Granted, I could have skipped that step because I used ‘wordpress’ for each, but that doesn’t make for as good an example, and regex’s aren’t that expensive (don’t they have dedicated chips for them by now?).
I was having issues when WordPress wanted to take me to the Admin screen. It used the shortcut ‘/WP-CONTEXT/wp-admin‘, which is great if you’re not doing all this fancy re-writing and fastcgi_index can take over. But we are being fancy. That’s why the $request_filename/index.php text exists. It works like a charm, although there may be a more efficient way to do this.
And here is where it became an advantage to differentiate between blog URLs and WordPress resources. I’ve chosen to make my permalinks dateless — /%postname%/ . Call me crazy, but I like the way it looks on Laughing Squid. Given that’s the case, it’s hard to differentiate between ‘/some-permalink/’ and ‘/wp-admin/’. Splitting them off with the ‘wordpress’ context made this possible.
The final context-less ‘Blog address (URL)’ capture block is exactly what you’d expect.
I liked the simplicity and capabilities of PmWiki 2.2.0. It’s an easier decision, since I have no intention of being a grand-scale collective document facility. PmWiki is a powerful and flexible implementation with a lot of great processing directives that you can embed in a page. Yet that also makes security something of a concern (as some reviewers will point out as well). Global multi-tier password auth is available, and user-based auth is available as necessary.
This configuration is a natural extension of the WordPress one above:
Everything here is obvious, including /PATH/TO/PMWIKI-DIR. Mine is ‘/var/www/pmwiki’. Here’s the lowdown:
In the *.php capture block, you’ll see that the default script is pmwiki.php. I had created a symlink to rename it index.php, but after my config re-adjustment, that became obsolete.
The non-existing file test will be triggered by the following requests:
Those URLs exist because I’m leveraging a feature called $EnablePathInfo. The referenced documentation doesn’t do it justice … this feature allows me to have bare Group/Name URLs, much like I’m doing with my bare blog URLs. I’ll just say that I’m being SEO-minded and leave it at that.
Turning on that feature informs PmWiki to generate the URLs in that format, and it also makes the PHP script capable of parsing the CGI headers to do-the-right-thing. My original configuration required me to perform the following override hack:
fastcgi_param SCRIPT_NAME '';
But the revised configuration above simply re-writes the URL into the standard '?n=‘ format and the script never has to deal with CGI headers. The only other rewrite considerations were to transform any querystring ‘?’ into ‘&’ and to remove the leading ‘/’ from the Group/Name combo.
For all means and purposes, I’m using nginx’s default fastcgi_params.
This is extra/proxy.conf, derived from their NginxFullExample, with notes-to-self intact:
This is fastcgi_cluster, which is just a simple example of how to do clustering:
I tried using some of the additional server setting features — commented out above — but they weren’t working in my build of 0.7.27. I can live without them at the moment, but the upstream block capabilities are quite powerful.
I’m running 3 FastCGI instances, each with 5 worker threads. Again, good enough for government work. I custom-built fcgi on OS X, but for my AWS Fedora Core 8 image I just went with spawn-fcgi that comes along with the lighttpd package.
This cluster config is also a nice starter reference for adding load-balancing capabilities to external AWS instances. Given the volatile nature of VM image mappings, I’ve split the cluster config off into its own file for scripted generation.
I’m very pleased with nginx. It has been very stable — the only time I’ve taken it down is when setting it up for infinte HTTP 302 redirects, and even then it took several hours of user activity to knock it over. The configuration syntax is very powerful, and I haven’t once been wistful for my old Apache habits :) .