Serving .rhtml Files on OS X With Apache and ERB

Martin Dittus · 2006-01-11 · code, osx, software · 5 comments

rhtml-screenshot.png

This documents how Apache 1.3 on OS X can be set up to serve .rhtml files using Ruby's ERB template system. Most of the required configuration is already described in Brian Bugh's article Using ERB/rhtml Templates On Dreamhost, but OS X's basic setup is conservative enough to require a little additional configuration.

However the real reason I'm writing this is that I additionally wanted to limit the handling of .rhtml scripts to my user account's "personal web sharing" directory (which maps to a tilde URL like http://127.0.0.1/~username/), and this isn't as straight-forward, so it took me a while to figure out the specifics.

The reasons for using the personal web sharing directory instead of the server root?

  1. It's more convenient. You can edit scripts without admin permissions.
  2. It's more secure. No need to manually chown/chgrp your scripts to make sure they aren't owned by root.
  3. Aside of that it's a matter of preference -- I like to have everything I'm working on in a subdirectory of my user profile. Easier to backup, etc.

So this article will describe two setups: first a global setup which enables .rhtml for the web server root, and which isn't much different to the setup described by Brian; and then a user setup limited to serve .rhtml only on a user's home directory.

Note that I'm trying to be not too liberal with configuration settings -- it's just a local web server, but better safe than sorry. Also note that I'm trying to use as much of the default OS X setup as possible. Other developers simply install additional web server software and don't bother to use the default Apache installation -- to me that seems overkill. I'm happy to edit configuration files instead.

To keep this article at a manageable size I'm making some assumptions about your admin-fu and knowledge of command line tools -- please ask questions if something isn't clear to you. And I'm sure I don't have to remind you to make backups of configuration files before you edit them.

Preconditions

Of course we'll need the ERB template library; as the Ruby Core Library already comes with ERB any OS X default installation of Ruby should be fine. Apple however chose not to ship the erb command line tool with their OS, and as erb is invaluable when you're debugging code that breaks in your web server you can either install a DarwinPorts/Fink version of Ruby (which will then include erb), or you can simply use something like this as an alternative:

Script 1: erb.rb, a command line ERB parser

#!/usr/local/bin/ruby
# erb.rb -- command line ERB parser 
# usage: ruby erb.rb <template file>
require 'erb'
erb = ERB.new(File.read(ARGV[0]))
print erb.result(binding)

The second precondition involves a bit more admin-fu: you need to setup Apache so that it will execute CGI scripts. Apache on OS X has this disabled by default, but it's possible that you already changed it yourself -- a quick test if this is already working on your server is to check the output of an actual CGI script. Here's a simple one, make it executable and place it somewhere below your local web server's document root, then open it in a browser:

Script 2: test.cgi, a simple Ruby CGI script

#!/usr/local/bin/ruby
puts "Content-type: text/html\n\n"
puts "CGI script handling is set up and working fine."

If this script is working for you (i.e., if the Browser displays the message, and not the script's source code) you can jump ahead to the next section; read on otherwise.

Enabling CGI scripts is a bit different for the global and user setup, but both start off by setting up a CGI handler. The directives for this are already in Apache's configuration file, but we'll need to make sure they aren't commented out.

Open /etc/httpd/httpd.conf in vim (you need superuser privileges for this). First we have to make sure the CGI module is being loaded. Find the following two directives and make sure they're not commented out:

LoadModule cgi_module         libexec/httpd/mod_cgi.so
...
AddModule mod_cgi.c

Then we'll setup the CGI module as script handler for specific file types. Find the following configuration section, and uncomment the AddHandler directive if necessary:

#
# Document types.
#
<IfModule mod_mime.c>
    ...
    # To use CGI scripts:
    #
    AddHandler cgi-script .cgi
    ...
<IfModule>

You can allow additional file extensions for CGI scripts like this:

AddHandler cgi-script .cgi .pl .py .rb

That's the basic setup. Restart Apache gracefully (sudo apachectl graceful) to activate your changes, and to check if you introduced configuration errors. Then make sure that test.cgi is working, and continue with the descriptions for either the global or user setup.

First Alternative: Global Setup

We first will allow .htaccess files the ability to define custom script handlers. To set this up, find and edit the AllowOverride directive in httpd.conf as shown here:

<Directory "/Library/WebServer/Documents">
    ...
    #AllowOverride None
    AllowOverride FileInfo
    ...
</Directory>

For a more liberal setup you could use this version instead (which among other things would enable you to use the ExecCGI directive in .htaccess files):

    AllowOverride All

Restart Apache again to make sure everything worked out.

Your server is configured so that CGI scripts are placed in /Library/WebServer/CGI-Executables/ (which maps to http://127.0.0.1/cgi-bin/), and all other documents including your .rhtml files belong in /Library/WebServer/Documents/ (which maps to http://127.0.0.1/). I've chosen to make use of this, which means we'll deviate from Brian's description.

Place Brian's erb.cgi into /Library/WebServer/CGI-Executables/.

Then create the following .htaccess file somewhere below your server's document root at /Library/WebServer/Documents/ to enable the .rhtml handler for a specific directory:

Script 3: .htaccess, activates .rhtml for the current directory

AddHandler rubypage .rhtml
Action rubypage /cgi-bin/erb.cgi

And that's it. You can use a simple script like this to test if your setup is working:

Script 4: test.rhtml, a simple test template

<%= "The ERB .rhtml parser is up and working fine."%>

Second Alternative: User Setup

The configuration required for our user setup is pretty similar to the global setup described above, with two changes that make it non-obvious to Apache amateurs like me. First, it seems that there is no default CGI script directory for user accounts; instead of allowing CGI script execution everywhere I chose to limit it to a ./cgi-bin/ subdirectory. Second, Brian's erb.cgi needs some changes in its file path logic; I've created a modified version that works even for user directories.

You'll find the server configuration for a user's personal web server at /etc/httpd/users/username.conf. (I'm sure I don't have to tell you to replace every occurrence of username mentioned here with an actual username.)

First edit your username.conf:

<Directory "/Users/username/Sites/">
    ...
    AllowOverride All
    ...
<Directory>

Restart Apache again to make sure everything worked out.

Create the directory /Users/username/Sites/cgi-bin/ and download my version of erb.cgi to this directory (see download links below).

Then create /Users/username/Sites/cgi-bin/.htaccess with the following content:

Script 5: a user's ./cgi-bin/.htaccess, activates CGI script execution

Options +ExecCGI

Then create the following .htaccess file somewhere below your server's document root at /Users/username/Sites/ to enable the .rhtml handler for a specific directory:

Script 6: .htaccess, activates .rhtml for the current directory

AddHandler rubypage .rhtml
Action rubypage /~username/cgi-bin/erb.cgi

And that's it. Test your setup with a simple .rhtml page (e.g., test.rhtml as shown above.)

Why a Modified erb.cgi for the User Setup?

If you tried to use Brian Bugh's erb.cgi as script handler for the user-specific setup you'll have found that even the most basic .rhtml pages exit with an error message. The reason is that the path logic in erb.cgi is too simplistic for this setup.

The script uses Apache's DOCUMENT_ROOT environment variable to transform a URL request into the .rhtml template's local path on disk. This variable however points to /Library/WebServer/Documents/ even within the scope of your personal server, so erb.cgi translates a request of /~username/test.rhtml to the file path /Library/WebServer/Documents/~username/test.rhtml when the real location is in fact /Users/username/Sites/test.rhtml.

I've looked around for information on how to solve this issue, and I'm still not quite sure if this is due to a mistake in the OS X default web server configuration, or where this conflict should usually be handled -- I've however found a way to make it work for my system using Apache's PATH_TRANSLATED environment variable, which according to the last draft of the CGI 1.1 spec is a translation of the requested document's path info to "a location in the server's document repository".

Although this variable isn't guaranteed to exist it worked fine on my system, so we'll modify erb.cgi to first check if PATH_TRANSLATED is defined, and otherwise fall back to the old path logic based on DOCUMENT_ROOT.

Here's the relevant code excerpt of my erb.cgi:

Script 7: excerpt of a modified erb.cgi, see the downloads below for the full implementation.

...
  path = nil
  if (ENV['PATH_TRANSLATED'])
    path = ENV['PATH_TRANSLATED']
  else
    # original code
    file_path = ENV['REDIRECT_URL'].include?(File.basename(__FILE__)) ? ENV['SCRIPT_URL'] : ENV['REDIRECT_URL']
    path = File.expand_path(ENV['DOCUMENT_ROOT'] + '/' + file_path)
    raise "Attempt to access invalid path: #{path}" unless path.index(ENV['DOCUMENT_ROOT']) == 0
  end
...

As I was editing erb.cgi anyway I decided to do a modification of its error handling so that it will show a more descriptive error message and print the values of its environment variables. You wouldn't do this on a production machine, but on a local development server it can help greatly to show more detailed debug information.

Then I changed the top-level rescue statement to catch all types of exceptions; previously, erb.cgi wouldn't catch non-StandardErrors such as SyntaxErrors, which would then lead to an HTTP 500 script error.

Also I've found that Brian's error logging doesn't work on my machine -- error's won't actually be written to disk because the logging code throws an exception. That's probably just an issue with write permissions, but I found that for my purposes I had no use for an error log file anyway and simply removed that code.

rhtml-error.png

Screenshot of erb.cgi's modified error message.

Download


Next article:

Previous article:

Recent articles:

Comments

Hi, just wanted to say thanks.
This was very helpful, - it assisted me getting erb up with lighttpd serving rhtml.
I then tried your script, worked good, used a different env var to get the file path/name to resolve. Oh yeah, the extended error info, Cool!

Dave Ray, 2006-04-05 06:53 CET (+0100) Link


I looked and looked and could not find anything I trusted. Than just as I was giving up, I found your post. Thank you so much!! I don't understand how ruby books can talk about erb and rhtml without explaining how to set up!
You are an angel to have posted all this. I followed the global setting, and the test worked! e-ha. Up and onward with erb.

anne G, 2006-04-29 00:41 CET (+0100) Link


Cool! Great to hear that it worked for you both.

Martin Dittus, 2006-04-29 00:49 CET (+0100) Link


Thanks for the howto. I was able to get the global set up going but not the local. Whenever I attempt to restart apache after altering the username.conf file I get the following error:
/usr/sbin/apachectl graceful: configuration broken, ignoring restart

checking out the errors:
Illegal override option ExecCGI

Any help resolving this problem would be appreciated.

Thanks,
Max

max benjamin, 2006-07-15 07:32 CET (+0100) Link


Ah, good catch. The AllowOverrid directive has no ExecCGI parameter -- use "AllowOverride All" instead.

I updated the article.

Martin Dittus, 2006-07-15 15:01 CET (+0100) Link


Comments are closed. You can contact me instead.