DBF 0.5.0 Update

May 25th, 2007

A lot of work has gone into this release of the DBF gem. The basic reader code is stable now. I’m in the process of moving the test suite from Test::Unit to Rspec. When I’m done with that I’ll add a couple of additional features, polish the documentation and christen it 1.0.0.

  • New find method
  • Full compatibility with the two flavors of memo file
  • Two modes of operation: In memory, or File I/O
  • Improved documentation and more usage examples

Returning supported HTTP methods with 'verify'

February 12th, 2007

When using Rails’ verify method to protect your ActionController actions, you should return a list of the allowed HTTP methods in the response headers.

Let’s say you have an action called update that you want to protect from anything but a POST. I like to do it like this:

:::ruby
verify :method => :post, 
  :only => :update, 
  :render => {:text => '405 HTTP POST required', :status => 405}, 
  :add_headers => {'Allow' => 'POST'} 

Now if someone tries to hit the update action with anything other than a POST, an error message will be displayed and the response headers will contain (among other things):

:::ruby
{"Status" => "405 Method Not Allowed", "Allow" => "POST"}

In my opinion, this is a better way to go than redirecting to another action because the use of an improper HTTP method is most likely the result of either programmer error or malicious intent. By redirecting to another page, you are making it much easier to for somebody to take your site down with a denial of service attack and if it’s a programming error, you’ll locate the problem faster.

You should also make sure that you have a functional test for this behavior:

:::ruby
def test_invalid_update_methods
  [:get, :put, :delete].each do |http_method|
    send http_method, :update
    assert_response 405
    assert_equal 'POST', @response.headers['Allow']
    assert_equal '405 HTTP POST required', @response.body
  end
end

Additional Resources

Going Nowhere for RailsConf 2007

February 2nd, 2007

RailsConf will be held right here in Portland this year, so no travelling for me this time around. The conference is being held at the Oregon Convention Center (the same place OSCON is held every year), so it’s a huge improvement over the shitty hotel in Chicago where it was held last year.

Registration just opened a couple of hours ago. This year the registration fee is higher and the venue is a lot larger so it probably won’t sell out as quickly as last year. But, if you want to go, I would sign up soon. I already did.

Updates:

10 month old MacBook Pro needs a good home

January 11th, 2007

I had to send my MacBook Pro in to repair a failing fan this week. Since it’s rather difficult for me to earn an income with no development machine, I bought one of the new MacBook Pro’s to replace it.

I’ll be selling the old one as soon as it comes back. It’s an original 15” MacBook Pro running at 2.0GHz with 2GB of RAM and the 100GB hard drive. It’s 10 months old, so it has over 2 years left on the AppleCare warranty. Make me an offer if you’re interested in buying it. Serious offers only please. If I don’t get any acceptable offers by next week, I’ll put it up for sale on ebay.

Update: The MBP just arrived back from Apple and the fan has been fixed. They also replaced the new top assembly in order to fix a “crunchy” trackpad button. This means that, while the keyboard is not new and has had my grubby fingers all over it, the area where your delicate hands rest is beautiful virgin aluminum.

Deploying Rails with Pound in front of Apache and Mongrel on Redhat ES4 with Ensim - Part 1

December 14th, 2006

I have my own dedicated server running Ensim on which I run quite a few sites. Most of these are either static HTML or PHP sites, but I also host a couple of Rails apps on the same box using Lighttpd. That has turned out not to be the most reliable setup. After a year of killing hung dispatchers I decided to give mongrel_cluster and Pound a try. This is how I did it.

Note that these instructions are specific to Redhat ES4 with Ensim, though most of this stuff should work on other flavors of *nix with some slight modifications.

Prerequisites

The only prerequisite for Pound that I am aware of is pcre. You probably already have it installed, but if you don’t, install in via up2date, yum, or whatever.

Installing Pound

Download, configure and install Pound. There are stable and experimental versions to choose from. I’m using the latest experimental release:

wget http://www.apsis.ch/pound/Pound-2.1.8.tgz
tar vxzf Pound-2.1.8.tgz
cd Pound-2.1.8
./configure
make
make install

Test Configuration

I don’t like the default location for Pound’s configuration file, so I’ve created mine file here: /etc/pound/pound.cfg

In order to make sure everything is working as expected, we’re going to start with a test configuration. Something simple. Put this in your /etc/pound/pound.cfg file for now:

LogLevel 0
Alive 30

ListenHTTP
  Address 0.0.0.0
  Port 81

  Service
    BackEnd
      Address YOUR SERVER'S EXTERNAL IP
      Port 80
    End
  End

End

This configuration will proxy all inbound requests to port 81 over to Apache running on port 80. Now try to start up pound:

pound -v -f /etc/pound/pound.cfg

If it started properly, you’ll get no output. You should get an error if it couldn’t start up. Use

ps -ef | grep pound

to see if it’s running. If so, try to browse to one of the sites on your server using port 81. For example: http://www.infused.org:81. If it works we’re in business, so we’ll create a pound start-up script and configure it to start up on boot.

Setup Pound daemon

Create a /etc/init.d/pound file with the following contents:

#!/bin/sh
. /etc/rc.d/init.d/functions

# To add to services:
# chkconfig --add pound

# Enable automatic start on boot
# chkconfig pound on

### BEGIN INIT INFO
# Provides:       pound
# Required-Start: $network $syslog
# Required-Stop:
# Default-Start:  3 5
# Default-Stop:
# Description:    Starts pound reverse proxy
### END INIT INFO

if [ -z "$POUND_CONF_PATH" ]; then
        POUND_CONF_PATH="/etc/pound/pound.conf"
fi

prog="pound"
pound="/usr/local/sbin/pound"
RETVAL=0

start() {
        echo -n $"Starting $prog: "
        daemon $pound -f $POUND_CONF_PATH
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
        return $RETVAL
}

stop() {
        echo -n $"Stopping $prog: "
        killproc $pound
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
        return $RETVAL
}

reload() {
        echo -n $"Reloading $prog: "
        killproc $pound -HUP
        RETVAL=$?
        echo
        return $RETVAL
}

case "$1" in
        start)
                start
                ;;
        stop)
                stop
                ;;
        restart)
                stop
                start
                ;;
        condrestart)
                if [ -f /var/lock/subsys/$prog ]; then
                        stop
                        start
                fi
                ;;
        reload)
                reload
                ;;
        status)
                status $pound
                RETVAL=$?
                ;;
        *)
                echo $"Usage: $0 {start|stop|restart|condrestart|reload|status}"
                RETVAL=1
esac

exit $RETVAL

Now you can configure Pound as a service and set it to start on boot:

chmod +x /etc/init.d/pound
chkconfig --add pound
chkconfig pound on

Use the ps command again to find and then kill the pound instance you started earlier. You will probably see to instances running. Kill the first one, and the other will die with it. Once the test instance is dead, you can start, stop, and restart Pound like any other service. To start it up as a service:

service pound start

The Real Deal

Test it by browsing to a site on port 81 again. If everything looks good, it’s time to do this thing for real. Edit /etc/pound/pound.cfg and swap the ports between the proxy and the backend server. The file should now look like this:

LogLevel 0
Alive 30

ListenHTTP
  Address 0.0.0.0
  Port 80

  Service
    BackEnd 
      Address 67.15.198.2
      Port 81
    End
  End

End

Log into your Ensim control panel as the Appliance Administrator and click on the Web Server link. Change port 80 to 81 and click Update Configuration.

With the appliance updated, we now need to restart Apache and Pound to pick up their new configurations:

service pound stop
service httpd restart
service pound start

All your sites should now be accessible on port 80. Try them out. If you don’t see any problems, there is one important step left to do.

Fix Apache Logging

Right now, all the Apache logs will show you server’s IP address as the remote address. To fix this, you need to swap out %h for "%{X-Forwarded-for}i" in any LogFormat line in /etc/httpd/conf/httpd20_app.conf

You may have LogFormat lines in other files too, such as /etc/httpd/conf/httpd.conf. Change those too.

My LogFormat directive now looks like this (on one line):

LogFormat "%v:\"%{X-Forwarded-for}i\" %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" ecombined

Restart Apache again and you’re done.

service httpd restart

Subversion

If you are running Subversion under Apache, you’ll also need to tell Pound to allow the proper HTTP verbs. Add the following line to /etc/pound/pound.cfg, right under ListenHTTP:

xHTTP 2

By the way, this also tells Pound to allow the PUT and DELETE verbs, which you’ll need if you’re using the map.resources stuff in Rails.

Additional Resources

Coming Up

The stage is now set for Part 2, where I set up Pound to proxy to several Rails apps which are running under mongrel_cluster. Stay tuned.