A small ruby fastcgi stub which can evaluate ruby scripts in much the same way PHP does.
Based heavily off Derrick Pallas' work, at
http://derrick.pallas.us/ruby-cgi/ with additional work to support logging and (optionally) displaying ruby exceptions in the browser, instead 500ing all over the place.
#!/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
#
# Remember to add 'puts cgi.header' at the beginning of your script!
require "fcgi"
require "mmap"
require "thread"
maxscripts = 128
maxscripts.freeze
ERROR_NOISY = true
ERROR_BACKTRACE = true
ERROR_LOGFILE = '/srv/www/log/ruby-cgi.log'
#def myLog(*args)
# line = self.send(:sprintf, *args)
# line = sprintf("%s: %s\n", Time.now.strftime("%Y-%m-%dT%H:%M:%S"), line)
# $logfilemutex ||= Mutex.new
# $logfilemutex.synchronize {
# $logfile ||= File.open("/srv/www/log/ruby-cgi.log", "a")
# $logfile.flock(File::LOCK_EX)
# $logfile.write(line)
# $logfile.flush
# $logfile.flock(File::LOCK_UN)
# }
#end
def htmlspecialchars(string)
string = string.to_s unless string.is_a?(String)
string.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
end
def error_log(*args)
nao = Time.now.strftime('%Y-%m-%dT%H:%M:%S')
begin
line = self.send(:sprintf, *args)
rescue Exception => err
line = sprintf('Cannot format message: %s (%s)', err.message, args.inspect);
ensure
line = sprintf("%s %s(%u): %s\n", nao, File.basename(__FILE__), Process.pid, line);
end
begin
$logfilemutex ||= Mutex.new
$logfilemutex.synchronize {
$logfile ||= File.open(ERROR_LOGFILE, 'a')
$logfile.flock(File::LOCK_EX)
$logfile.write(line)
$logfile.flush
$logfile.flock(File::LOCK_UN)
}
rescue Exception => err
nil
end
end
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
error_log("Starting")
FCGI.each_cgi do |cgi|
error_log("CGI: %s", cgi.inspect)
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
error_log('Uncaught exception: %s', bang.inspect)
error_log("Backtrace:\n%s\n----", bang.backtrace.join("\n"))
if ERROR_NOISY
puts cgi.header
puts "<hr><b>Uncaught exception:</b>", "<em>", htmlspecialchars(bang), "</em>\n"
puts "<pre>Backtrace:\n", htmlspecialchars(bang.backtrace.join("\n")), "</pre>\n" if ERROR_BACKTRACE
end
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
######
