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 ######