Skip to content

Instantly share code, notes, and snippets.

@octplane
Created December 23, 2010 13:43
Show Gist options
  • Save octplane/752986 to your computer and use it in GitHub Desktop.
Save octplane/752986 to your computer and use it in GitHub Desktop.
require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
require 'event_utils'
require 'singleton'
require 'syslog'
require 'trollop'
require 'yaml'
class Syslogger
include Singleton
def initialize
@s = Syslog.open('http_duplicator', Syslog::LOG_PID, Syslog::LOG_DAEMON)
@s.info("Started logger for http duplicator")
end
def debug(msg)
@s.debug msg
puts "DEBUG #{msg}" if $DEBUG
end
def info(msg)
@s.info msg
puts "INFO #{msg}"
end
end
class Backend
attr_reader :host, :port, :status, :headers, :content
include EM::Deferrable
def initialize(host, port, is_master=false)
@host = host
@port = port
@is_master = false
end
def send_request(params)
client = EventMachine::Protocols::HttpClient.request(params)
client.callback {|response|
@status = response[:status]
@headers = response[:headers]
@content = response[:content]
self.succeed
}
client.errback do
Syslogger.instance.info("Unable to perform request #{params[:host]}:#{params[:port]}#{params[:request]}.")
@status = nil
end
return self
end
end
class MasterSlaveConfiguration
include Singleton
attr_reader :members
attr_reader :listen_port
attr_accessor :filename
DEFAULT_CONFIGURATION = %Q|
---
master:
- www.virtual.ftnz.net
- 2002
slave:
- www.virtual.ftnz.net
- 2002
|
def initialize
@members = {}
@old_conf = nil
end
def refresh
if not File.exists?(@filename)
Syslogger.instance.info("#{@filename} not found, using default configuration.")
conf = YAML.load(DEFAULT_CONFIGURATION)
else
conf = YAML.load(File.open(@filename).read)
if @old_conf != conf
Syslogger.instance.info("Loaded configuration #{@filename}, with configuration #{conf.inspect}.")
end
end
@listen_port = conf['listen_port']
@members[:master] = conf['master']
@members[:slave] = conf['slave']
if @old_conf != conf
@load_time = Time.now
end
@old_conf = conf
end
def master
reload_if_needed
@members[:master]
end
def slave
reload_if_needed
@members[:slave]
end
def reload_if_needed
if Time.now - @load_time > 10
refresh
end
end
end
class HTTP_Duplicator < EM::Connection
include EM::HttpServer
include EventUtils
def post_init
super
no_environment_strings
master = MasterSlaveConfiguration.instance.master
@master = Backend.new(master[0], master[1])
slave = MasterSlaveConfiguration.instance.slave
@slave = Backend.new(slave[0], slave[1])
end
def process_http_request
Syslogger.instance.debug("Processing #{@http_request_method} #{@http_request_uri}.")
in_deferred_loop do
master = @master.send_request(
:host => @master.host,
:port => @master.port,
:request => @http_request_uri,
:verb => @http_request_method,
:content => @http_post_content,
:contenttype => @http_content_type,
:querystring => @http_query_string
)
# Only forward GETS to the slave, ignore the other methods
if @slave.host != nil && @http_request_method == 'GET'
slave = @slave.send_request(
:host => @slave.host,
:port => @slave.port,
:request => @http_request_uri,
:verb => @http_request_method,
:content => @http_post_content,
:contenttype => @http_content_type,
:querystring => @http_query_string
)
end
waiting_for(master) do
response = EM::DelegatedHttpResponse.new(self)
response.status = master.status.to_i
response.content = master.content
# response.headers = master.headers.delete("Content-length")
response.send_response
end
waiting_for(slave) do
if slave.status != 200
Syslogger.instance.info "Something went wrong on the slave: "+slave.status.inspect
end
end
end
end
end
opts = Trollop::options do
opt :configuration_file, "configuration file", :default => "replicator.yml"
opt :port, "Listening port", :default => 10080
end
MasterSlaveConfiguration.instance.filename = opts[:configuration_file]
MasterSlaveConfiguration.instance.refresh
EM::run do
EM::start_server '0.0.0.0', opts[:port], HTTP_Duplicator
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment