Configure Apache To Serve Any Ruby Erb File (Like a mod_ruby or mod_erb)

Over the years there have been lots of cries for a mod_ruby on Apache that allowed users to ftp upload single ruby files to anywhere on their website and have Apache interpret the files and serve the resulting html, just like you can do with php files.  Granted, there has been a project called mod_ruby, but that project has long been unmaintained and plagued with issues.  Whether or not it is a good idea to copy the php method of writing webpages gets discussed at length every other week, so I will not get in to that here.

As it turns out, it’s pretty easy to configure Apache to do this, thanks to cgi and mod_actions.  This guide will show you how to configure Apache, so you can upload any erb file to anywhere on your Apache website and serve it up like a php file.  These erb files are html files embedded with ruby script inside special tags, just like php files are html files with php script embedded in them.

Install Apache

For this I’m using Ubuntu 10.04, the latest LTS release of Ubuntu.  There should be little or no difference doing this on Debian or other versions of Ubuntu.

sudo aptitude install apache2

Install Ruby

You’ll need ruby and an eruby library.  We’re keeping things simple and just installing what is in apt.

sudo aptitude install ruby1.9.1 liberubis-ruby1.9.1

Configure Apache

You just need ScriptAlias, AddHandler, and Action directives to tie everything together.  They can go anywhere in your Apache config files, but here is how I do it.

First, you need to enable the Apache Actions modules.

sudo a2enmod actions

Then put this in /etc/apache2/conf.d/erb-cgi

<IfModule mod_mime.c>
        AddHandler application/x-httpd-erb .erb
</IfModule>
<IfModule mod_actions.c>
        Action application/x-httpd-erb /cgi-bin/erb-cgi.rb
</IfModule>

In the default site configuration under /etc/apache2/sites-available/default there is already a ScriptAlias directive, so I just left it there.  Here it is in case you don’t have it in your site config.

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

Now restart apache.

sudo service apache2 restart

The CGI Script

We’ve configured Apache so anytime a file ending with “.erb” is requested, Apache instead executes the erb-cgi.rb script and displays the output.  Lets write this script so it processes and outputs the requested file.

Put the following in /usr/lib/cgi-bin/erb-cgi.rb

#!/usr/bin/env ruby

require 'erubis'

puts "Content-type: text/html\n"

# Check that the script is being executed through a redirect
if ENV["REQUEST_URI"] =~ /^#{ENV["SCRIPT_NAME"]}.*/
  puts "<html><body><p>Script can not be executed directly!</p></body></html>"
else
  # Get the file location from the ENV hash, read it, and process it through erubis
  puts Erubis::FastEruby.new(File.read(ENV["PATH_TRANSLATED"])).result(binding())
end

Make sure the file is executable by Apache.

sudo chgrp www-data /usr/lib/cgi-bin/erb-cgi.rb
sudo chmod ug+x /usr/lib/cgi-bin/erb-cgi.rb

Try It Out

You are now ready to upload erb embedded html files to anywhere under the DocumentRoot of your website.  This will handle any file with a .erb extension, however I prefer to use a .html.erb extension to differentiate these from other filetypes embedded with erb, such as .xml.erb.  Note:  this will actually handle erb embedded xml files, but you will have to modify the cgi script to check the file extension and change the content type.

Here is a quick example of an erb embedded html file.  Assuming you are using Ubuntu’s default site with the default DocumentRoot, save this in /var/www/test.html.erb

<html>
<body>
  <h1>Hello <%= ENV["REMOTE_ADDR"] %>!</h1>
  <p>The time is now <%= Time.now.strftime("%r") %>.</p>
</body>
</html>

You should now be able to open a browser  and go to http://your_server/test.html.erb and see the results.  You can now upload and serve erb files just like php files!

Feel free to ask any questions or make any suggestions in the comments!

Sources And More Information

For high traffic sites, using fastcgi (fcgid) instead of cgi would eventually become a necessity.  I don’t plan on using this for anything that gets much traffic, though, so I’m not exploring the configuration for fastcgi at the moment.  I also don’t know how this will handle form data through a POST request.  Also keep in mind that this will not allow you upload a Rails application and have it just work through this cgi script. It would probably be pretty easy to modify this to use rack, but running a full Rails app through cgi is a pretty bad idea, and should probably just be done with mod_passenger.

, , , ,

  1. #1 by Dan Carper (@dan_carper) on November 14, 2011 - 10:56 AM

    Cool stuff! My not-very-ruby-oriented supervisor will appreciate this 😉

    Thanks,
    Dan

  2. #2 by Jamie R Robillard Sr. on February 12, 2012 - 1:50 AM

    Great tutorial except that erb-cgi.rb will fail as written above. modify the line that reads — puts “Content-type: text/html\n” to puts “Content-type: text/html\n\n” and it will work with no issue. Note: CGI requires two \n (Carriage Return characters) after the Content-type deceleration in order to work.

    • #3 by James on February 12, 2012 - 12:50 PM

      `puts` already adds a carriage return to the end of the line, so the resulting string from `puts “Content-type: text/html\n”` has two carriage returns at the end and the result of `puts “Content-type: text/html\n\n”` would actually have three carriage returns. If you don’t want a carriage return automatically added to the end of the string, you use `print` instead of `puts`.

      • #4 by Jamie R Robillard Sr. on February 13, 2012 - 9:55 PM

        Yes I considered that later. Though when I tested the results of

        #!/usr/bin/env ruby

        require ‘erubis’

        puts “Content-type: text/html\n”

        # file truncated to check Content-type Header

        ./erb-cgi.bin > test.txt

        GHex gave me these results for text.txt

        43 6F 6E 74 65 6E 74 2D 74 79 70 65 3A 20 74 65 78 74 2F 68 74 6D 6C 0A

        and

        -rw-r–r– 1 jamie jamie 24 2012-02-13 22:36 test.txt

        the file only has 24 bytes in it. I double checked my ruby book, though it doesn’t cover the versions of ruby I have installed.

        ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
        ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
        on Ubuntu 10.10.
        Unless ruby 1.8.x => has changed the output of puts and I am not aware of it I agree that line of code should have generated a well formed Content-type header and I apologise for coming of like you didn’t know what you where talking about. Though on my system, with the versions of ruby I have installed (test with both), it generates a malformed Content-type header. The solution is as I described in my previous comment.

        Have A Great Day!
        Jamie

  3. #5 by Jamie R Robillard Sr. on February 13, 2012 - 10:31 PM

    In previous comment ./erb-cgi.bin > test.txt is a typo it should have read

    ./erb-cgi.rb > test.txt

    that part I didn’t cut and paste :-), guess I should’ve.

    Have A Great Day!
    Jamie

  4. #6 by Jamie R Robillard Sr. on February 15, 2012 - 2:28 PM

    I am not trying to be a nuance. I simply wanted to resolve what is going on with puts not emitting a linefeed (which I previously and erroneously referred to as a carriage return in my haste) in the case of this ruby program (script if you would prefer) at least in regards to my system and the versions of ruby I have installed, which is the extent of my knowledge in this regard, as I feel that the information may also be of use to others using this tutorial if they end up having the same issues I have had.

    I was not able to find anything through searches on the internet of any value, and was to lazy to heavily scrutinies the advanced techniques portion of the only ruby book I have, so I resorted to analysing the source code of:

    ruby 1.9.3-p0

    What I found in io.c where puts is implemented is this:

    VALUE
    rb_io_puts(int argc, VALUE *argv, VALUE out)
    {
    int i;
    VALUE line;

    /* if no argument given, print newline. */
    if (argc == 0) {
    rb_io_write(out, rb_default_rs);
    return Qnil;
    }
    for (i=0; i<argc; i++) {
    if (TYPE(argv[i]) == T_STRING) {
    line = argv[i];
    goto string;
    }
    line = rb_check_array_type(argv[i]);
    if (!NIL_P(line)) {
    rb_exec_recursive(io_puts_ary, line, out);
    continue;
    }
    line = rb_obj_as_string(argv[i]);
    string:
    rb_io_write(out, line);
    if (RSTRING_LEN(line) == 0 ||
    !str_end_with_asciichar(line, '\n')) {
    rb_io_write(out, rb_default_rs);
    }
    }

    return Qnil;
    }

    this is fairly straight forward so I won't go into a detailed analysis. The code of interest is:

    string:
    rb_io_write(out, line);
    if (RSTRING_LEN(line) == 0 ||
    !str_end_with_asciichar(line, '\n')) {
    rb_io_write(out, rb_default_rs);
    }

    From this we can observe that puts only emits an OS specific EOL sequence if no arguments have been provided or there isn't a linefeed already provided at the end of the string to be output.

    I have not personally used ruby in enough instances to verify that this has not always been the behaviour of the language. I have noticed though that even in the other tutorial you reference from 2006 they use the puts statement ending with 2 linefeed characters to form the Content-type header. Which isn't conclusive in and by itself but would indicate that this is, at least to some degree a known behaviour.

    In any case I am comfortable with the statement that in ruby puts only outputs a OS specific EOL character sequence when no argument is passed to puts or when the string passed to puts does not explicitly provide a linefeed. So in regards to this program (script) we will be required to provide 2 linefeed characters in the Content-type header eg..

    puts “Content-type: text/html\n\n”

    In order to not produce a malformed Content-type header. Let me know if you find any information that disagrees or disproves this.

    Have a Great Day!
    Jamie

  5. #7 by john on July 7, 2012 - 6:23 PM

    Thanks for the write up. One question though, I cannot seem to require any gems using this setup. I’m using Ruby 1.9.1 and have even tried requiring rubygems first. Erubis always throws an error in evaluator.rb.

  6. #8 by Josh Belanger on April 25, 2013 - 5:01 PM

    Would I be correct in guessing that with this line:
    “Then put this in /etc/apache2/conf.d/erb-cgi”
    that last part should have a .conf on the end?

  7. #9 by phillips1012 on September 28, 2013 - 4:14 PM

    This has successfully cured the horrors of php for me. Thank you.

  1. Configure Apache To Serve Any Ruby Erb File (Like a mod_ruby or mod_erb) « I Am James’ Blog | new ExceptionalBlogging();

Leave a comment