Sunday, June 17, 2007

Fast CGI Binding for Ruby

I got this originally from Derrick Pallas' website at http://derrick.pallas.us/ruby-cgi/ which I then further modified to beef it up for my use. It was an excellent base, without which I would never be able to even get started.

Notable addition is logging, interrupt catches and getting line number when a Ruby script errors out.



#!/usr/bin/ruby
###############

##########################
# FastCGI Ruby dispatcher
# (C) Derrick Pallas
# (C) Rizal Kertadjaja
# Original Authors: Derrick Pallas http://derrick.pallas.us/ruby-cgi/
# Modified by: Rizal Kertadjaja http://learnwebstuff.blogspot.com
# License: Academic Free License 3.0
# Version: 2007-06-17
#

require "fcgi"
require "mmap"
require "logger"

maxscripts = 128
maxscripts.freeze
@log_file_path = "/path/to/log/file"
@log_file_path.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

def logger
@logger = Logger.new(@log_file_path)
end

def dispatcher_log(level, msg)
time_str = Time.now.strftime("%d/%b/%Y:%H:%M:%S")
logger.send(level, "[#{time_str} :: #{$$}] #{msg}")
rescue Object => log_error
STDERR << "Couldn't write to #{@log_file_path.inspect}: #{msg}\n"
STDERR << " #{log_error.class}: #{log_error.message}\n"
end

def dispatcher_error(e, msg="")
error_message = "Dispatcher failed to catch: #{e} (#{e.class})\n" +
" #{e.backtrace.join("\n ")}\n#{msg}"
dispatcher_log(:error, error_message)
end

begin
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) )
catch (:done) do
eval scripts[script].map, getBinding(cgi,cgi.env_table), script if scripts[script].map
end
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
dispatcher_error bang
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

GC.enable
dispatch_log :info, "terminated gracefully"

rescue Interrupt => interrupt_error
dispatcher_log :info, "terminated by interrupt"
rescue SystemExit => exit_error
dispatcher_log :info, "terminated by explicit exit"
rescue Object => fcgi_error
dispatcher_error fcgi_error, "killed by this error"
end

# END
######

Monday, June 11, 2007

Methods available in CGI (Ruby)

for complete reference



http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/classes/CGI.html


==
===
=~
[]
__id__
__send__
accept
accept_charset
accept_encoding
accept_language
args
auth_type
cache_control
class
clone
content_length
content_type
cookies
cookies=
display
dup

env_table - map of environment variables passed in to the cgi. see earlier post below.

eql?
equal?
extend
freeze
from
frozen?
gateway_interface
has_key?
hash

header - return HTTP header as a String, if you want to add your own headers use cgi.header(yourHashMap)

host
id
include?
inspect
instance_eval
instance_of?
instance_variable_get
instance_variable_set
instance_variables
is_a?
key?
keys
kind_of?
method
methods
multipart?
negotiate
nil?
object_id
out
params - map of parameters keys and values (parsed query string)
params=
path_info
path_translated
pragma
print
private_methods
protected_methods
public_methods
query_string
raw_cookie
raw_cookie2
referer
remote_addr
remote_host
remote_ident
remote_user
request_method
respond_to?
script_name
send
server_name
server_port
server_protocol
server_software
singleton_methods
stdinput
stdoutput
taint
tainted?
to_a
to_s
type
untaint
user_agent

Sunday, June 10, 2007

Sample Ruby FCGI env_table

DOCUMENT_ROOT => /var/www/vhosts/yourhost.com/httpdocs/
FCGI_ROLE => RESPONDER
GATEWAY_INTERFACE => CGI/1.1
HTTP_ACCEPT => image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, */*
HTTP_ACCEPT_ENCODING => gzip, deflate
HTTP_ACCEPT_LANGUAGE => en-us
HTTP_CONNECTION => Keep-Alive
HTTP_HOST => yourhost.com:81
HTTP_USER_AGENT => Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.50727; Media Center PC 4.0; .NET CLR 3.0.04506.30)
PATH_INFO =>
QUERY_STRING =>
REDIRECT_STATUS => 200
REDIRECT_URI => /dispatcher.rb
REMOTE_ADDR => 10.10.10.103
REMOTE_PORT => 1769
REQUEST_METHOD => GET
REQUEST_URI => /AAAA/rubytest.rb?11212345566873
SCRIPT_FILENAME => /var/www/vhosts/yourhost.com/httpdocs/dispatcher.rb
SCRIPT_NAME => /dispatcher.rb
SERVER_ADDR => your.host.ip.address
SERVER_NAME => yourhost.com:81
SERVER_PORT => 81
SERVER_PROTOCOL => HTTP/1.1
SERVER_SOFTWARE => lighttpd/1.4.13