require 'cgi' require 'posixlock' STDOUT.sync = STDERR.sync = true $VERBOSE = nil require 'nitro/context' require 'nitro/dispatcher' require 'nitro/cgi' require 'glue/flexob' # Speeds things up, more comaptible with OSX. Socket.do_not_reverse_lookup = true # No multi-threading. Og.thread_safe = false module Nitro # ACGI Adaptor. ACGI is a language independent, # scalable, open extension to CGI that provides high # performance without the limitations of server # specific APIs. # # This code is directly from the author. Please go to # http://codeforpeople.com/lib/ruby/acgi/ for more # information about ACGI. # or email: ara [dot] t [dot] howard [at] noaa [dot] gov # # It uses a compiled C cgi program to handle # connections compile the acgi.c by executing # "make -f Makefile.acgi" in your public/ folder. # then make your .htaccess redirect all calls to acgi.cgi # instead of fcgi.rb / cgi.rb # #-- # gmosx: not tested! Use at your own risk! #++ class ACGI < ::CGI THIS = File::expand_path $0 COMMAND_LINE = [THIS, ARGV].join ' ' LOADED = Time::now IPC_DIR = "/tmp/acgi_ipc" CLIENT_LOCK_PATH = File::join IPC_DIR, "client.lock" SERVER_LOCK_PATH = File::join IPC_DIR, "server.lock" SERVER_ENVIRONMENT_PATH = File::join IPC_DIR, "server.environment" SERVER_STDIN_PATH = File::join IPC_DIR, "server.stdin" SERVER_STDOUT_PATH = File::join IPC_DIR, "server.stdout" SERVER_STDERR_PATH = File::join IPC_DIR, "server.stderr" SERVER_PID_PATH = File::join IPC_DIR, "server.pid" RE_ENV = %r/^ ([^=]+) = (.*) $/ox RE_NUL = %r/#{ Regexp::escape "\0" }/ox SERVER = ENV['ACGI_SERVER'] || ARGV.delete('--server') || ARGV.delete('-s') if SERVER trap('HUP'){ exec ::ACGI::COMMAND_LINE } %w( INT KILL TERM ).each{|s| trap(s){ exit }} end class << self def each_cgi(*a, &b) if SERVER start_server(*a, &b) else munge_environment @cgi = new(*a) catching_errors{ b[ @cgi ] } end end def start_server(*a, &b) daemon do ENV.clear # __very__ important for speed! aquire_server_lock record_pid loop do load_environment munge_environment @cgi = redirecting_stdin{ new(*a) } redirecting_stdout_and_stderr{ catching_errors{ b[ @cgi ] }} restart_if_updated end end end def restart_if_updated # put the full path to your run.rb here if it doesn't work #th = "/path/to/app/run.rb" if File::stat(THIS).mtime > LOADED #if File::stat(th).mtime > LOADED exec COMMAND_LINE end end def aquire_server_lock lockfile = File.open(SERVER_LOCK_PATH) lockfile.reopen(SERVER_LOCK_PATH, 'w') at_exit{ lockfile.close } ret = lockfile.posixlock(File::LOCK_EX|File::LOCK_NB) raise("#{ File::basename $0 } already running..") if(!ret or !ret.zero?) end def record_pid begin f = File::open(SERVER_PID_PATH) f.reopen(SERVER_PID_PATH, 'w') f.print Process::pid f.chmod 0777 rescue nil ensure f.close end end def env_table SERVER ? (@environment ||= {}) : ENV end def env env_table end def load_environment begin f = File::open(SERVER_ENVIRONMENT_PATH) f.reopen(SERVER_ENVIRONMENT_PATH, "r") env_table.clear kvs = f.read.chop.split RE_NUL kvs.each do |kv| m = RE_ENV.match(kv) k, v = m[1], m[2] env_table[k] = v end ensure f.close end end def munge_environment # because CGI::Cookie::parse is broken! env_table['HTTP_COOKIE'] = munge_http_cookie env_table['HTTP_COOKIE'] end def munge_http_cookie http_cookie return unless http_cookie kvs = http_cookie.split %r/[;,]\s+/o h = Hash::new{|h,k| h[k] = []} kvs.each do |kv| k, v = kv.split %r/=/o, 2 next unless k k = CGI::unescape k vs = v.to_s.split(%r/&/).map{|v| CGI::unescape v} h[k].push(*vs) end h.sort.map{|k,vs| [k, vs.join('&')].join('=')}.join('; ') end def redirecting_stdin stdin = $stdin begin @stdin = File::open(SERVER_STDIN_PATH) @stdin.reopen(SERVER_STDIN_PATH, "r") $stdin = @stdin yield ensure @stdin.close if @stdin $stdin = stdin end end def redirecting_stdout_and_stderr stdout, stderr = $stdout, $stderr begin @stdout = File::open(SERVER_STDOUT_PATH) @stdout.reopen(SERVER_STDOUT_PATH, "w") @stderr = File::open(SERVER_STDERR_PATH) @stderr.reopen(SERVER_STDERR_PATH, "w") $stdout, $stderr = @stdout, @stderr yield ensure @stdout.close if @stdout @stderr.close if @stderr $stdout, $stderr = stdout, stderr end end def catching_errors @cgi ||= CGI::new begin yield rescue Exception => e m, c, b = e.message, e.class, e.backtrace.join("
\n") @cgi.out{ "
#{ m } (#{ c })

#{ b }" } end end def daemon exit!(0) if fork Process::setsid exit!(0) if fork # Dir::chdir("/") # File::umask(0) # uncomment these if you don't want logs #STDIN.reopen("/dev/null") #STDOUT.reopen("/dev/null", "w") #STDERR.reopen("/dev/null", "w") yield if block_given? end end def env_table self::class.env_table end def env self::class.env end # use this to start the ACGI server. def self.start(server) ACGI.each_cgi do |cgi| begin Cgi.process(server, cgi, $stdin, $stdout) end end end end end # * Ara Howard