FastCGI Ruby dispatcher back

Ruby, FastCGI, and lighttpd
The first webserver I set up was a large Apache monster that came preinstalled. After dropping that in favor of the much more lightweight thttpd, I found that there were many features lacking. Luckily, that was about the time lighttpd, began to mature. One of the reasons for my switch was the modular architecture and the inclusion of a FastCGI module. PHP is the primary language I use for web development, even with all of its problems. However, I quite enjoy Ruby and all of its benefits, both from aesthetic and technical standpoints. Unlike PHP, Ruby is not primarily for web apps, and so it doesn't come with anything like php-cgi. There was always Ruby on Rails, but I didn't want an application server, just applications. To that end, I created a very lightweight FastCGI server that executes Ruby scripts referenced by lighttpd directly in a binding context that only imports the CGI object for the request.

Warning: This script is provided as is with no warranty implied or otherwise. Users must determine whether it is fit for any particular purpose, as we, the authors, make no claim to that end. Use it at your own risk.

ruby-cgi is licensed under the Academic Free License v3.0.

That being said..

ruby-cgi


You can download ruby-cgi here
#!/usr/bin/ruby
###############

##########################
# FastCGI Ruby dispatcher
# (C) Derrick Pallas
#
# Authors: Derrick Pallas
# Website: http://derrick.pallas.us/ruby-cgi/
# License: Academic Free License 3.0
# Version: 2005-12-23a
#

require "fcgi"
require "mmap"

maxscripts = 128
maxscripts.freeze


class Script
  attr_accessor :map
  attr_accessor :mod
  attr_accessor :use
end

scripts = {}
mytime  = File.stat(__FILE__).mtime


def getBinding(cgi,env)
  return binding
end


FCGI.each_cgi do |cgi|
  script = cgi.env_table['SCRIPT_FILENAME']
  script.freeze

  begin
    if ( not scripts.key?script or scripts[script].mod < File.stat(script).mtime )
      if scripts.key?script
        scripts[script].map.munmap
      else
        scripts[script] = Script.new
      end
      scripts[script].mod = File.stat(script).mtime
      scripts[script].map = Mmap.new script, "r"
    end
    scripts[script].use = Time.now

    Dir.chdir( File.dirname(script) )
    eval scripts[script].map, getBinding(cgi,cgi.env_table) if scripts[script].map

    if scripts.length > maxscripts
      begin
        killme = scripts.min { |a,b| a[1].use <=> b[1].use } [0]
        scripts[killme].map.munmap
        scripts.delete(killme)
      rescue Exception
      end
    end

  rescue Exception => bang
    puts "<hr><b>Exception:</b>", "<em>", CGI::escapeHTML(bang), "</em>\n"
  end if (script && File.stat(script).readable?)

  if (File.stat(__FILE__).mtime > mytime)
    Process.kill 'SIGHUP', Process.pid
    mytime = File.stat(__FILE__).mtime
  end
end

# END
######

Configuration


In the lighttpd configuration directory, you have to change lighttpd.conf or mod_fastcgi.conf to enable FastCGI and include ruby-cgi in fastcgi.server. For instance, mine looks like this:
server.modules += ("mod_fastcgi")
fastcgi.server = (
  ".php" =>
  ( "localhost" => (
    "socket"    => "/tmp/php.socket",
    "bin-path"  => "/usr/bin/php-cgi"
  ) ),
  ".rb" =>
  ( "localhost" => (
    "socket"    => "/tmp/ruby.socket",
    "bin-path"  => "/usr/bin/ruby-cgi"
  ) )
)
  
This allows both Ruby and PHP FastCGI instances to be spawned by the server.

The following is optional but useful. In order to get .rb (and .php) files to run without the #! header, which used to be a problem in PHP, the PHP and Ruby executables can be registered with binfmt_misc. The following should be run in a startup script if you want this functionality.

echo ':PHP:E::php::/usr/bin/php:'    > /proc/sys/fs/binfmt_misc/register
echo ':Ruby:E::rb::/usr/bin/ruby:' > /proc/sys/fs/binfmt_misc/register
  

How do you know it's working?


This page is generated by ruby-cgi, automatically incorporating changes from the downloadable ruby-cgi. Remember, that unlike PHP or eRuby, you are writing in Ruby, not HTML with filtered <%code%>. To that end, you should remember to include puts cgi.header before sending any HTML. cgi.rb is included automatically, but if you want to test the script from the command-line, you should require "cgi".

 
Anyone attempting to generate random numbers by deterministic means is, of course, living in a state of sin.

 
   
org DOT telperion AT pallas