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".
|