kuboon
12/31/2015 - 11:18 AM

run Rails app as DRb server and access it from CGI process

run Rails app as DRb server and access it from CGI process

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.rb.cgi/$1 [QSA,L]
#!ruby
require 'drb/drb'
require 'logger'
require "rack"
require 'rack/rewindable_input'

module Dispatch
  RAILS_ENV = 'staging'
  DRUBY_URI = "druby://localhost:13141"
  LOCKFILE = "../tmp/pids/dispatch.pid"
  LOGGER = Logger.new('../log/dispatch.log')

  class CGIHandler
    def initialize(app)
      @app = app
    end
    def call(env, stdin, stdout)
      env.update(Rack::SCRIPT_NAME => '', # clear
                 "rack.version" => Rack::VERSION,
                 "rack.input" => stdin,
                 "rack.multithread" => false,
                 "rack.multiprocess" => true,
                 "rack.run_once" => true,
                 "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
                 )

      LOGGER.info "#{DRb.uri} call rack"
      status, headers, body = @app.call(env)
      LOGGER.info "#{DRb.uri} status: #{status}"
      begin
        stdout.print "Status: #{status}\r\n"
        send_headers stdout, headers
        send_body stdout, body
      ensure
        body.close if body.respond_to? :close
      end
    end

    private

    def send_headers(stdout, headers)
      headers.each { |k, vs|
        vs.split("\n").each { |v|
          stdout.print "#{k}: #{v}\r\n"
        }
      }
      stdout.print "\r\n"
      stdout.flush
    end

    def send_body(stdout, body)
      body.each { |part|
        stdout.print part.to_str
        stdout.flush
      }
    end
  end

  class << self
    def main
      spawn_server unless server_alive?

      $stdin.binmode

      DRb.start_service
      cgi_handler = DRbObject.new_with_uri(DRUBY_URI)

      LOGGER.info "#{DRb.uri} call cgi_handler"
      cgi_handler.call(ENV.to_h, Rack::RewindableInput.new($stdin), $stdout)
      LOGGER.info "#{DRb.uri} done"
    rescue => e
      puts "Content-Type: text/plain\nStatus: 500\n\n#{e}"
      puts e.backtrace
      LOGGER.error ([e] + e.backtrace).join("\n")
    end

    private

    def server_alive?
      return false unless File.exist?(LOCKFILE)
      return true if process_alive? File.read(LOCKFILE).to_i

      File.delete(LOCKFILE)
      false
    end

    def process_alive?(pid)
      Process.getpgid( pid )
      true
    rescue Errno::ESRCH
      false
    end

    def spawn_server
      File.open(LOCKFILE, "w") do |f|
        if f.flock(File::LOCK_EX | File::LOCK_NB)
          f.puts fork { start_service }
          sleep 5
        else
          LOGGER.error("lock failed")
        end
      end
    end

    def start_service
      $stdout.reopen("/dev/null", "w")
      $stderr.reopen("/dev/null", "w")
      LOGGER.info 'starting server..'
      DRb.start_service(DRUBY_URI, cgi_handler)
      LOGGER.info 'started server'
      DRb.thread.join
    rescue => e
      LOGGER.error ([e] + e.backtrace).join("\n")
    end

    def cgi_handler
      ENV['RAILS_ENV'] = RAILS_ENV
      require '../config/environment'
      CGIHandler.new(Rails.application)
    end
  end
end

Dispatch.main