Dealing with Adobe and Serving Socket Policy Servers via NGiNX and 10 lines of code
Ok, I don't even know where to start, but I guess I'll start by thanking Adobe for a such a high quality product (</sarcasm>). So we are working towards our new Vokle product, dubbed Vokle Everywhere (by me, just now).
So our new product allows for embeddability of chats and to do this we use ActionScript 3. I wouldn't say that AS3 has its short comings, except of course, Flex, but from time to time we have some issues with it. Like not being able to override the 'Accept' header in a UrlRequest and other various dumb things. Instead we moved to a mildly liberating open source AS3 HTTP Client.
AS3HTTPClientLib is pretty sweet, the only thing that sort of sucked was that we needed a Socket Policy Server, and the last thing I want to do is run another server (already have nginx, merb, wowza, yadayadayada) like this butthole suggests.
Instead I figured, there has to be a way to just serve the crossdomain.xml file without being a giant hassle, especially since I am already serving it over HTTP for the same domain. So I said, frig it, I'll do it with NGiNX since NGiNX is already serving my static files. So the upside is when my servers are running, my 'policy server' is serving, the downside is, I had to finagle it so it wasn't a pain in the ass. The problem is that ActionScript makes a 'weird' request when its looking for a policy server, and NGiNX doesn't understand it, so it servers a 400 Bad Request, well, it turns out that you can just do it hackety-hack and send your crossdomain.xml file as the 400 Error Page. This works because AS isn't doing an HTTP Request, so it DOESNT CARE about the HTTP Status. Bam, NGiNX Flash Socket Policy Server in 10 lines of code.
To enable 'socket policy server' functionality in NGiNX simply add another 'server' directive to your nginx.conf and drop your crossdomain.xml file in whatever folder your NGiNX server is serving its statics from.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | server { listen 843; server_name localhost; location / { rewrite ^(.*)$ /crossdomain.xml; } error_page 400 /crossdomain.xml; location = /crossdomain.xml { root html; } } |
Sweet, start your nginx server and use the only useful thing from the Adobe blog post:
1 | perl -e 'printf "<policy-file-request/>%c",0' | nc 127.0.0.1 843 |
And you should see your policy file.
MovingParts: Auto versioning your Javascript and CSS
This tutorial has a few moving parts. It could be easily recreated with any stack, but for this post I used Nginx and Merb.
Why?
We like to keep things fast. Two ways we do that is compress the hell out of our js/css and set the cache time on our assets to max. This is cool to do and obvious, the thing that sucks is when its time to roll out new javascript or css making sure the clients get the updates.
What?
For this example I am using merb and merb-assets (w/ my latest merb patch), nginx and ruby-yui.
Step1 - Add version info to assets with merb-assets
In our code base we have a version for our site, set in a variable like WWW_VERSION. Using this variable it is easy (with the newest merb-assets plugin) to modify the path of all of your assets on the fly.
Stick this in your init.rb...
1 2 3 4 5 6 7 8 9 10 11 12 | WWW_VERSION = "0.1.0" # this will make require_js "application" # => /javascripts/application.0.1.0.js # instead of /javascripts/application.js Merb::Plugins.config[:asset_helpers][:js_suffix] = ".#{WWW_VERSION}" Merb::Plugins.config[:asset_helpers][:css_suffix] = ".#{WWW_VERSION}" |
At this point in time merb is going to start outputting modified paths to your assets that contain version numbers. This is cool because now when we modify javascript or css and bump the version number it will cause the clients that have cached the static assets to redownload them since the file path is different. The part that sucks is renaming a bunch of javascript and css files.
Step2 - Lying about file names
Renaming a bunch of files does suck! So don't do it. Instead have that sneaky russian Nginx lie about it for you.
Stick this in you nginx.conf...
1 2 3 4 5 6 7 8 9 10 | location ~ ^/(javascripts|stylesheets) { rewrite ^/(javascripts|stylesheets)\/([^\d]+)(?:\.[0-9]+)*\.(js|css)$ /$1/$2.$3; expires max; break; } |
This rewrite rule will make nginx route any javascript/stylesheet to the original file if it contains a version number. So a request like to '/javascripts/application.0.1.0.js' will be rewritten to '/javascripts/application.js'. Nifty.
Step3 - Compress those assets
You can compress your assets in a number of ways, I'll go with shameless self promotion and recommend RubyYUI. RubyYUI is a ruby wrapper for the Java-based YUI Compressor. It lets you glob paths instead of doing one file at a time and add suffixes to files. All sorts of things.
You can do something like this in a vlad or capistrano task during deployment.
1 2 3 4 5 6 7 8 9 10 | require 'rubygems' require 'ruby-yui' # this will cause the files to be compressed in place Yui.new("./public/javascripts",:stomp => true, :suffix => nil).compress Yui.new("./public/stylesheets",:stomp => true, :suffix => nil, :type=>:css).compress |
Now you have compressed assets in place in your deployed app and nginx will rewrite any versioned paths to your compressed files. Bump your version number and all your clients will start getting the new compressed files. Wee.