diff --git a/bin/thin b/bin/thin index 444fdbc..b56eb34 100755 --- a/bin/thin +++ b/bin/thin @@ -32,6 +32,7 @@ opts = OptionParser.new do |opts| opts.on("-a", "--address HOST", "bind to HOST address (default: 0.0.0.0)") { |host| options[:address] = host } opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port.to_i } opts.on("-S", "--socket PATH", "bind to unix domain socket") { |file| options[:socket] = file } + opts.on("-B", "--swiftiply", "Run using swiftiply") { |key| options[:swiftiply] = (key || true) } opts.on("-e", "--environment ENV", "Rails environment (default: development)") { |env| options[:environment] = env } opts.on("-c", "--chdir PATH", "Change to dir before starting") { |dir| options[:chdir] = File.expand_path(dir) } opts.on("-t", "--timeout SEC", "Request or command timeout in sec", @@ -95,9 +96,10 @@ def start(options) server = Thin::Server.new(options[:address], options[:port]) end - server.pid_file = options[:pid] - server.log_file = options[:log] - server.timeout = options[:timeout] + server.pid_file = options[:pid] + server.log_file = options[:log] + server.timeout = options[:timeout] + server.swiftiply = options[:swiftiply] if options[:daemonize] server.daemonize diff --git a/lib/thin/cluster.rb b/lib/thin/cluster.rb index 9d5e57c..cdc8921 100644 --- a/lib/thin/cluster.rb +++ b/lib/thin/cluster.rb @@ -29,11 +29,12 @@ module Thin end end - def first_port; @options[:port] end - def address; @options[:address] end - def socket; @options[:socket] end - def pid_file; @options[:pid] end - def log_file; @options[:log] end + def first_port; @options[:port] end + def address; @options[:address] end + def socket; @options[:socket] end + def swiftiply; @options[:swiftiply] end + def pid_file; @options[:pid] end + def log_file; @options[:log] end # Start the servers def start @@ -97,6 +98,8 @@ module Thin cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number)) if socket cmd_options.merge!(:socket => socket_for(number)) + elsif swiftiply + cmd_options.merge!(:port => first_port) else cmd_options.merge!(:port => number) end @@ -108,7 +111,7 @@ module Thin yield @only else @size.times do |n| - yield socket ? n : (first_port + n) + yield((socket||swiftiply) ? n : (first_port + n)) end end end diff --git a/lib/thin/connection.rb b/lib/thin/connection.rb index 29bbb93..0d3a7fc 100644 --- a/lib/thin/connection.rb +++ b/lib/thin/connection.rb @@ -13,14 +13,24 @@ module Thin # Server owning the connection attr_accessor :server + # nil, true, or Swiftiply auth key + attr_accessor :swiftiply + def post_init @request = Request.new @response = Response.new end + def connection_completed + return unless @swiftiply + key = (@swiftiply == true) ? '' : @swiftiply.to_s + send_data swiftiply_handshake(key) + end + def receive_data(data) trace { data } process if @request.parse(data) + post_init if @swiftiply rescue InvalidRequest => e log "Invalid request" log_error e @@ -40,7 +50,7 @@ module Thin send_data chunk end - close_connection_after_writing + close_connection_after_writing unless @swiftiply rescue Object => e log "Unexpected error while processing request: #{e.message}" @@ -53,6 +63,7 @@ module Thin def unbind @server.connection_finished(self) + ::EventMachine.add_timer(rand(2)) { reconnect(server.host, server.port) } if @swiftiply and !@server.stopping end protected @@ -66,5 +77,14 @@ module Thin Socket.unpack_sockaddr_in(get_peername)[1] end end + + def swiftiply_handshake(key) + 'swiftclient' << host_ip.collect {|x| sprintf('%02x', x.to_i)}.join << sprintf('%04x', @server.port.to_i) << sprintf('%02x', key.length) << key + end + + # For some reason Swiftiply request the current host + def host_ip + Socket.gethostbyname(@server.host)[3].unpack('CCCC') rescue [0,0,0,0] + end end end \ No newline at end of file diff --git a/lib/thin/server.rb b/lib/thin/server.rb index a8bf756..ea03d8c 100644 --- a/lib/thin/server.rb +++ b/lib/thin/server.rb @@ -21,6 +21,12 @@ module Thin # Maximum time for incoming data to arrive attr_accessor :timeout + # nil, true, or Swiftiply auth key + attr_accessor :swiftiply + + # True if server is stopping + attr_accessor :stopping + # Creates a new server bound to host:port # or to +socket+ that will pass request to +app+. # If +host_or_socket+ contains a / it is assumed @@ -83,7 +89,11 @@ module Thin @stopping = true # Do not accept anymore connection - EventMachine.stop_server(@signature) + if @swiftiply + EventMachine.stop + else + EventMachine.stop_server(@signature) + end unless wait_for_connections_and_stop # Still some connections running, schedule a check later @@ -108,7 +118,9 @@ module Thin protected def start_server - if @socket + if @swiftiply + start_client_for_swiftiply + elsif @socket start_server_on_socket else start_server_on_host @@ -127,12 +139,18 @@ module Thin EventMachine.start_unix_domain_server(@socket, Connection, &method(:initialize_connection)) end + def start_client_for_swiftiply + log ">> Connecting to Swiftiply on #{@host}:#{@port}, CTRL+C to stop" + EventMachine.connect(@host, @port, Connection, &method(:initialize_connection)) + end + def initialize_connection(connection) connection.server = self connection.comm_inactivity_timeout = @timeout connection.app = @app connection.silent = @silent connection.unix_socket = !@socket.nil? + connection.swiftiply = @swiftiply @connections << connection end