Sunday, March 23, 2008

Great explanation of Rails auto-loading

I thought Mark Bush did a great job of explaining Rails' auto-loading behavior on the Rails-talk mailing list.  I'm posting it here to help others find it, and so I remember where to find it next time I'm struggling with it.  I've known that Rails automatically requires and loads classes on the fly using standard conventions for module names -> file names, but I had a hard time grasping how it worked.  Mark explains it very concisely.

The Rails list has a low signal-to-noise ratio but it's still worth paying attention to for gems like this.

Saturday, March 15, 2008

Using stunnel to wrap Ruby network operations on the fly

In my current project, we need to be able to connect to POP3 servers. Some POP3 servers, such as Gmail, only allow SSL connections. Unfortunately, the Ruby 1.8.x net/pop library doesn't support SSL (although the 1.9 library does, but that was not an option for us in this project).

The usual answer here is to wrap your connection using stunnel, which acts as an SSL proxy for whatever traffic you want to send over it. Usually you run stunnel as a separate service pointing at the server, but since we'll be connecting to many different POP servers, I needed to be able to set up and tear down stunnels on the fly. The first attempt looked something like this:
system("echo -e 'foreground = yes\npid =\n[mail]\nclient = yes\n \
accept = 127.0.0.1:2000\nconnect = #{server}:#{port}\n' \
| stunnel -fd 0")
Since stunnel doesn't accept command-line options, you have to pipe options to it. The "fd -0" tells stunnel to read its configuration from file descriptor 0, better known as STDIN.

Since I need to run that command in a child process, then have the parent resume and make use of the child service, I embarked on a fun foray of Ruby's forking and threading capabilities.

First, I tried forking, replacing the child process with a call to exec instead of system, then detaching the parent and killing the child process when the POP session was done. This partially worked, but I couldn't figure out how to kill the child process, so I'd end up with multiple copies of stunnel running after the script ran, or the parent process itself would hang.

Looking through the Pickaxe chapter on threads and processes, I discovered IO.popen, which works perfectly. I can pipe input to STDIN, avoiding the ugliness of the "echo -e" above, and I can more easily kill the child process when I'm done.

This is what the final method looks like:
def stunnel_wrap(server, port)
stunnel = IO.popen("stunnel -fd 0",'w+')
stunnel.puts("foreground = yes\npid =\n[mail]\nclient = yes\n \
accept = 127.0.0.1:2000\nconnect = #{server}:#{port}\n"
)
stunnel.close_write
Kernel.sleep(1)
yield
ensure
Process.kill(9,stunnel.pid)
end
I handle exceptions at a higher layer in this class, so here all I do is make sure the stunnel process gets killed no matter what. I'm not sure if the sleep call is needed, but when I was testing this with Gmail it seemed to help to wait one second for the tunnel to activate before trying to use it.

To make the above example work, you just need to point your POP client at the stunnel (in this case 127.0.0.1 port 2000) and you'll be talking SSL to the server.
stunnel_wrap('pop.gmail.com',995) do
Net::POP3.start('127.0.0.1', 2000, account, password) do |pop|
# pop securely
end
end