Brain Dump: Random Migration Notes

I've been using the migration and some recent side projects as sandboxes to try out new things. Here's a semi-random list of useful tidbits I've picked up along the way:

  • Better mod_rewrite magic: Google turns up plenty of mod_rewrite examples on automatically stripping the dreaded "www." prefix from URLs. Unfortunately, most of them appear to be incorrect. Here's the most common solution:

    RewriteCond %{HTTP_HOST} ^www.example.com$  [NC]
    RewriteRule ^(.*)$  http://example.com/$1   [R=301,L]

    What it's supposed to do is redirect visitors from http://www.example.com/whatever to http://example.com/whatever, but what it actually does is redirect visitors to http://example.com//whatever. It's minor, but it was driving me nuts (Arrrrrrr). Anyway, here is the correct solution:

    RewriteCond %{HTTP_HOST} ^www\.             [NC]
    RewriteRule ^/(.*)$  http://example.com/$1  [R=301,L]

  • mod_deflate: Saves a ton of bandwidth, works great in IE7 and Firefox. The stock settings don't include a couple of common MIME types; here's the list I'm using: text/html text/plain text/xml text/css text/javascript application/x-javascript text/csv

  • XCache: Fast PHP opcode cacher that actually works with recent versions of PHP. I tested several Wordpress, Gallery, and custom PHP sites without incident, and my (incredibly rough) benchmarks showed about a 4-7% increase in mean transfer speed.
  • ExtJS Builder: I decided to test the ExtJS builder for a personal project. The interface is a bit finicky; it took me about 5 tries to get all the dependencies for my project selected. Here are the results:

    FileMinifiedDeflated
    ext-all.js468k125k
    ext-mine.js276k77k

    Note: The "Minified" column is the total file size after being shrunk with Douglas Crockford's excellent jsmin, and the "Deflated" column is the actual transfer size (according to Firebug) after being passed through mod_deflate.

    Not too shabby for 20 minutes of work. I'm a little bit disappointed by the stock mod_deflate compression ratio, so that may need a bit of tweaking.

  • Backgrounding Mercurial Hooks: The Mercurial book has an excellent chapter on hooks. What it doesn't mention, unfortunately, is how to run hooks in the background. I have a semi-lengthy outgoing hook (roughly equivalent to a client-side post-commit for you Subversion weenies) that connects to a web server via ssh and performs some deployment tasks, and all attempts at backgrounding a shell script eluded me. Well, it turns out Mercurial has an extra hidden file descriptor that has to be closed in order to background a hook. So here's my down and dirty client-side background deployment hook:

    #
    # outgoing hook script that connects to web server and deploys
    # the latest site from tip.  It is run in the background after a
    # successful 'hg push'.
    #
    
    # options
    opt = {
      # remote hostname
      'host' => 'web',
    
      # remote command (relative to my home directory)
      'cmd'  => 'bin/update_site.sh',
    
      # client-side log (set to /dev/null to disable)
      # 'log'  => '/dev/null',
      'log'  => '/tmp/site_update.log',
    
      # delay (in seconds) before update
      'delay' => 3,
    }
    
    # fork and run update in background
    pid = fork {
      # close stdin, stdout, and stderr
      $stdin = $stdin.reopen('/dev/null', 'r')
      $stdout = $stdout.reopen(opt['log'], 'a')
      $stderr = $stderr.reopen(opt['log'], 'a')
      $defout = $stdout
    
      # close all other file descriptors
      # NOTE: mercurial appears to have a hidden fd laying 
      # around somewhere, so this evil is necessary...
      (3..99).each { |fd| IO.new(fd).close rescue nil }
    
      # wait for push to finish
      # (this should poll the hg server instead, to handle
      # lengthy pushes)
      sleep opt['delay']
    
      # run update command and exit
      args = ['ssh', opt['host'], opt['cmd']]
      exec(*args)
    
      # never reached
      exit 0
    }
    
    # reap child and exit
    flags = Process::WNOHANG | Process::WUNTRACED
    Process.waitpid(pid, flags)
    

Update: Markdown really mangled my markup this time around. Usually it's pretty tolerant, but apparently this post was just a bit too much. Oh well...