about summary refs log tree commit homepage
path: root/lib/unicorn
diff options
context:
space:
mode:
Diffstat (limited to 'lib/unicorn')
-rw-r--r--lib/unicorn/app/exec_cgi.rb156
-rw-r--r--lib/unicorn/app/old_rails.rb29
-rw-r--r--lib/unicorn/app/old_rails/static.rb60
-rw-r--r--lib/unicorn/cgi_wrapper.rb149
-rw-r--r--lib/unicorn/configurator.rb159
-rw-r--r--lib/unicorn/const.rb96
-rw-r--r--lib/unicorn/http_request.rb220
-rw-r--r--lib/unicorn/http_response.rb68
-rw-r--r--lib/unicorn/launcher.rb33
-rw-r--r--lib/unicorn/socket.rb142
-rw-r--r--lib/unicorn/socket_helper.rb90
-rw-r--r--lib/unicorn/util.rb15
12 files changed, 780 insertions, 437 deletions
diff --git a/lib/unicorn/app/exec_cgi.rb b/lib/unicorn/app/exec_cgi.rb
new file mode 100644
index 0000000..8f81d78
--- /dev/null
+++ b/lib/unicorn/app/exec_cgi.rb
@@ -0,0 +1,156 @@
+require 'unicorn'
+require 'rack'
+
+module Unicorn::App
+
+  # This class is highly experimental (even more so than the rest of Unicorn)
+  # and has never run anything other than cgit.
+  class ExecCgi
+
+    CHUNK_SIZE = 16384
+    PASS_VARS = %w(
+      CONTENT_LENGTH
+      CONTENT_TYPE
+      GATEWAY_INTERFACE
+      AUTH_TYPE
+      PATH_INFO
+      PATH_TRANSLATED
+      QUERY_STRING
+      REMOTE_ADDR
+      REMOTE_HOST
+      REMOTE_IDENT
+      REMOTE_USER
+      REQUEST_METHOD
+      SERVER_NAME
+      SERVER_PORT
+      SERVER_PROTOCOL
+      SERVER_SOFTWARE
+    ).map { |x| x.freeze }.freeze # frozen strings are faster for Hash lookups
+
+    # Intializes the app, example of usage in a config.ru
+    #   map "/cgit" do
+    #     run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
+    #   end
+    def initialize(*args)
+      @args = args.dup
+      first = @args[0] or
+        raise ArgumentError, "need path to executable"
+      first[0..0] == "/" or @args[0] = ::File.expand_path(first)
+      File.executable?(@args[0]) or
+        raise ArgumentError, "#{@args[0]} is not executable"
+    end
+
+    # Calls the app
+    def call(env)
+      out, err = Tempfile.new(''), Tempfile.new('')
+      out.unlink
+      err.unlink
+      inp = force_file_input(env)
+      inp.sync = out.sync = err.sync = true
+      pid = fork { run_child(inp, out, err, env) }
+      inp.close
+      pid, status = Process.waitpid2(pid)
+      write_errors(env, err, status) if err.stat.size > 0
+      err.close
+
+      return parse_output!(out) if status.success?
+      out.close
+      [ 500, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
+    end
+
+    private
+
+    def run_child(inp, out, err, env)
+      PASS_VARS.each do |key|
+        val = env[key] or next
+        ENV[key] = val
+      end
+      ENV['SCRIPT_NAME'] = @args[0]
+      ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
+      env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
+
+      a = IO.new(0).reopen(inp)
+      b = IO.new(1).reopen(out)
+      c = IO.new(2).reopen(err)
+      exec(*@args)
+    end
+
+    # Extracts headers from CGI out, will change the offset of out.
+    # This returns a standard Rack-compatible return value:
+    #   [ 200, HeadersHash, body ]
+    def parse_output!(out)
+      size = out.stat.size
+      out.sysseek(0)
+      head = out.sysread(CHUNK_SIZE)
+      offset = 2
+      head, body = head.split(/\n\n/, 2)
+      if body.nil?
+        head, body = head.split(/\r\n\r\n/, 2)
+        offset = 4
+      end
+      offset += head.length
+      out.instance_variable_set('@unicorn_app_exec_cgi_offset', offset)
+      size -= offset
+
+      # Allows +out+ to be used as a Rack body.
+      def out.each
+        sysseek(@unicorn_app_exec_cgi_offset)
+
+        # don't use a preallocated buffer for sysread since we can't
+        # guarantee an actual socket is consuming the yielded string
+        # (or if somebody is pushing to an array for eventual concatenation
+        begin
+          yield(sysread(CHUNK_SIZE))
+        rescue EOFError
+          return
+        end while true
+      end
+
+      prev = nil
+      headers = Rack::Utils::HeaderHash.new
+      head.split(/\r?\n/).each do |line|
+        case line
+        when /^([A-Za-z0-9-]+):\s*(.*)$/ then headers[prev = $1] = $2
+        when /^[ \t]/ then headers[prev] << "\n#{line}" if prev
+        end
+      end
+      headers['Content-Length'] = size.to_s
+      [ 200, headers, out ]
+    end
+
+    # ensures rack.input is a file handle that we can redirect stdin to
+    def force_file_input(env)
+      inp = env['rack.input']
+      if inp.respond_to?(:fileno) && Integer === inp.fileno
+        inp
+      elsif inp.size == 0 # inp could be a StringIO or StringIO-like object
+        ::File.open('/dev/null')
+      else
+        tmp = Tempfile.new('')
+        tmp.unlink
+        tmp.binmode
+
+        # Rack::Lint::InputWrapper doesn't allow sysread :(
+        buf = ''
+        while inp.read(CHUNK_SIZE, buf)
+          tmp.syswrite(buf)
+        end
+        tmp.sysseek(0)
+        tmp
+      end
+    end
+
+    # rack.errors this may not be an IO object, so we couldn't
+    # just redirect the CGI executable to that earlier.
+    def write_errors(env, err, status)
+      err.seek(0)
+      dst = env['rack.errors']
+      pid = status.pid
+      dst.write("#{pid}: #{@args.inspect} status=#{status} stderr:\n")
+      err.each_line { |line| dst.write("#{pid}: #{line}") }
+      dst.flush
+    end
+
+  end
+
+end
diff --git a/lib/unicorn/app/old_rails.rb b/lib/unicorn/app/old_rails.rb
new file mode 100644
index 0000000..9b3a3b1
--- /dev/null
+++ b/lib/unicorn/app/old_rails.rb
@@ -0,0 +1,29 @@
+# This code is based on the original Rails handler in Mongrel
+# Copyright (c) 2005 Zed A. Shaw
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+# Additional work donated by contributors.  See CONTRIBUTORS for more info.
+require 'unicorn/cgi_wrapper'
+require 'dispatcher'
+
+module Unicorn; module App; end; end
+
+# Implements a handler that can run Rails.
+class Unicorn::App::OldRails
+
+  def call(env)
+    cgi = Unicorn::CGIWrapper.new(env)
+    begin
+      Dispatcher.dispatch(cgi,
+          ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
+          cgi.body)
+    rescue Object => e
+      err = env['rack.errors']
+      err.write("#{e} #{e.message}\n")
+      e.backtrace.each { |line| err.write("#{line}\n") }
+    end
+    cgi.out  # finalize the response
+    cgi.rack_response
+  end
+
+end
diff --git a/lib/unicorn/app/old_rails/static.rb b/lib/unicorn/app/old_rails/static.rb
new file mode 100644
index 0000000..17c007c
--- /dev/null
+++ b/lib/unicorn/app/old_rails/static.rb
@@ -0,0 +1,60 @@
+# This code is based on the original Rails handler in Mongrel
+# Copyright (c) 2005 Zed A. Shaw
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+
+require 'rack/file'
+
+# Static file handler for Rails < 2.3.  This handler is only provided
+# as a convenience for developers.  Performance-minded deployments should
+# use nginx (or similar) for serving static files.
+#
+# This supports page caching directly and will try to resolve a
+# request in the following order:
+#
+# * If the requested exact PATH_INFO exists as a file then serve it.
+# * If it exists at PATH_INFO+rest_operator+".html" exists
+#   then serve that.
+#
+# This means that if you are using page caching it will actually work
+# with Unicorn and you should see a decent speed boost (but not as
+# fast as if you use a static server like nginx).
+class Unicorn::App::OldRails::Static
+  FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze
+  REQUEST_METHOD = 'REQUEST_METHOD'.freeze
+  REQUEST_URI = 'REQUEST_URI'.freeze
+  PATH_INFO = 'PATH_INFO'.freeze
+
+  def initialize(app)
+    @app = app
+    @root = "#{::RAILS_ROOT}/public"
+    @file_server = ::Rack::File.new(@root)
+  end
+
+  def call(env)
+    # short circuit this ASAP if serving non-file methods
+    FILE_METHODS.include?(env[REQUEST_METHOD]) or return @app.call(env)
+
+    # first try the path as-is
+    path_info = env[PATH_INFO].chomp("/")
+    if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
+      # File exists as-is so serve it up
+      env[PATH_INFO] = path_info
+      return @file_server.call(env)
+    end
+
+    # then try the cached version:
+
+    # grab the semi-colon REST operator used by old versions of Rails
+    # this is the reason we didn't just copy the new Rails::Rack::Static
+    env[REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
+    path_info << "#$1#{ActionController::Base.page_cache_extension}"
+
+    if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
+      env[PATH_INFO] = path_info
+      return @file_server.call(env)
+    end
+
+    @app.call(env) # call OldRails
+  end
+end if defined?(Unicorn::App::OldRails)
diff --git a/lib/unicorn/cgi_wrapper.rb b/lib/unicorn/cgi_wrapper.rb
new file mode 100644
index 0000000..bc622ea
--- /dev/null
+++ b/lib/unicorn/cgi_wrapper.rb
@@ -0,0 +1,149 @@
+# This code is based on the original CGIWrapper from Mongrel
+# Copyright (c) 2005 Zed A. Shaw
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See CONTRIBUTORS for more info.
+
+require 'cgi'
+
+module Unicorn; end
+
+# The beginning of a complete wrapper around Unicorn's internal HTTP
+# processing system but maintaining the original Ruby CGI module.  Use
+# this only as a crutch to get existing CGI based systems working.  It
+# should handle everything, but please notify us if you see special
+# warnings.  This work is still very alpha so we need testers to help
+# work out the various corner cases.
+class Unicorn::CGIWrapper < ::CGI
+  undef_method :env_table
+  attr_reader :env_table
+  attr_reader :body
+
+  # these are stripped out of any keys passed to CGIWrapper.header function
+  NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless
+  CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this?
+  CHARSET = 'charset'.freeze # this gets appended to Content-Type
+  COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers
+  STATUS = 'status'.freeze # stored as @status
+  Status = 'Status'.freeze # code + human-readable text, Rails sets this
+
+  # some of these are common strings, but this is the only module
+  # using them and the reason they're not in Unicorn::Const
+  SET_COOKIE = 'Set-Cookie'.freeze
+  CONTENT_TYPE = 'Content-Type'.freeze
+  CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH
+  RACK_INPUT = 'rack.input'.freeze
+  RACK_ERRORS = 'rack.errors'.freeze
+
+  # this maps CGI header names to HTTP header names
+  HEADER_MAP = {
+    'status' => Status,
+    'type' => CONTENT_TYPE,
+    'server' => 'Server'.freeze,
+    'language' => 'Content-Language'.freeze,
+    'expires' => 'Expires'.freeze,
+    'length' => CONTENT_LENGTH,
+  }.freeze
+
+  # Takes an a Rackable environment, plus any additional CGI.new
+  # arguments These are used internally to create a wrapper around the
+  # real CGI while maintaining Rack/Unicorn's view of the world.  This
+  # this will NOT deal well with large responses that take up a lot of
+  # memory, but neither does the CGI nor the original CGIWrapper from
+  # Mongrel...
+  def initialize(rack_env, *args)
+    @env_table = rack_env
+    @status = nil
+    @head = {}
+    @headv = Hash.new { |hash,key| hash[key] = [] }
+    @body = StringIO.new
+    super(*args)
+  end
+
+  # finalizes the response in a way Rack applications would expect
+  def rack_response
+    # @head[CONTENT_LENGTH] ||= @body.size
+    @headv[SET_COOKIE] += @output_cookies if @output_cookies
+    @headv.each_pair do |key,value|
+      @head[key] ||= value.join("\n") unless value.empty?
+    end
+
+    # Capitalized "Status:", with human-readable status code (e.g. "200 OK")
+    parseable_status = @head.delete(Status)
+    @status ||= parseable_status.split(/ /)[0].to_i rescue 500
+
+    [ @status || 500, @head, [ @body.string ] ]
+  end
+
+  # The header is typically called to send back the header.  In our case we
+  # collect it into a hash for later usage.  This can be called multiple
+  # times to set different cookies.
+  def header(options = "text/html")
+    # if they pass in a string then just write the Content-Type
+    if String === options
+      @head[CONTENT_TYPE] ||= options
+    else
+      HEADER_MAP.each_pair do |from, to|
+        from = options.delete(from) or next
+        @head[to] = from.to_s
+      end
+
+      @head[CONTENT_TYPE] ||= "text/html"
+      if charset = options.delete(CHARSET)
+        @head[CONTENT_TYPE] << "; charset=#{charset}"
+      end
+
+      # lots of ways to set cookies
+      if cookie = options.delete(COOKIE)
+        set_cookies = @headv[SET_COOKIE]
+        case cookie
+        when Array
+          cookie.each { |c| set_cookies << c.to_s }
+        when Hash
+          cookie.each_value { |c| set_cookies << c.to_s }
+        else
+          set_cookies << cookie.to_s
+        end
+      end
+      @status ||= options.delete(STATUS) # all lower-case
+
+      # drop the keys we don't want anymore
+      options.delete(NPH)
+      options.delete(CONNECTION)
+
+      # finally, set the rest of the headers as-is, allowing duplicates
+      options.each_pair { |k,v| @headv[k] << v }
+    end
+
+    # doing this fakes out the cgi library to think the headers are empty
+    # we then do the real headers in the out function call later
+    ""
+  end
+
+  # The dumb thing is people can call header or this or both and in
+  # any order.  So, we just reuse header and then finalize the
+  # HttpResponse the right way.  This will have no effect if called
+  # the second time if the first "outputted" anything.
+  def out(options = "text/html")
+    header(options)
+    @body.size == 0 or return
+    @body << yield if block_given?
+  end
+
+  # Used to wrap the normal stdinput variable used inside CGI.
+  def stdinput
+    @env_table[RACK_INPUT]
+  end
+
+  # The stdoutput should be completely bypassed but we'll drop a
+  # warning just in case
+  def stdoutput
+    err = @env_table[RACK_ERRORS]
+    err.puts "WARNING: Your program is doing something not expected."
+    err.puts "Please tell Eric that stdoutput was used and what software " \
+             "you are running.  Thanks."
+    @body
+  end
+
+end
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb
index dd9ae3b..a432f64 100644
--- a/lib/unicorn/configurator.rb
+++ b/lib/unicorn/configurator.rb
@@ -1,5 +1,4 @@
-require 'unicorn/socket'
-require 'unicorn/const'
+require 'socket'
 require 'logger'
 
 module Unicorn
@@ -8,42 +7,40 @@ module Unicorn
   #
   # Example (when used with the unicorn config file):
   #   worker_processes 4
-  #   listeners %w(0.0.0.0:9292 /tmp/my_app.sock)
+  #   listen '/tmp/my_app.sock', :backlog => 1
+  #   listen '0.0.0.0:9292'
   #   timeout 10
   #   pid "/tmp/my_app.pid"
-  #   after_fork do |server,worker_nr|
-  #     server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
+  #   after_fork do |server,worker|
+  #     server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
   #   end
   class Configurator
-    include ::Unicorn::SocketHelper
-
     # The default logger writes its output to $stderr
     DEFAULT_LOGGER = Logger.new($stderr) unless defined?(DEFAULT_LOGGER)
 
     # Default settings for Unicorn
     DEFAULTS = {
       :timeout => 60,
-      :listeners => [ Const::DEFAULT_LISTEN ],
+      :listeners => [],
       :logger => DEFAULT_LOGGER,
       :worker_processes => 1,
-      :after_fork => lambda { |server, worker_nr|
-          server.logger.info("worker=#{worker_nr} spawned pid=#{$$}")
+      :after_fork => lambda { |server, worker|
+          server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
 
           # per-process listener ports for debugging/admin:
           # "rescue nil" statement is needed because USR2 will
           # cause the master process to reexecute itself and the
           # per-worker ports can be taken, necessitating another
           # HUP after QUIT-ing the original master:
-          # server.listen("127.0.0.1:#{8081 + worker_nr}") rescue nil
+          # server.listen("127.0.0.1:#{8081 + worker.nr}") rescue nil
         },
-      :before_fork => lambda { |server, worker_nr|
-          server.logger.info("worker=#{worker_nr} spawning...")
+      :before_fork => lambda { |server, worker|
+          server.logger.info("worker=#{worker.nr} spawning...")
         },
       :before_exec => lambda { |server|
           server.logger.info("forked child re-executing...")
         },
       :pid => nil,
-      :backlog => 1024,
       :preload_app => false,
       :stderr_path => nil,
       :stdout_path => nil,
@@ -83,23 +80,6 @@ module Unicorn
       @set[key]
     end
 
-    # Changes the listen() syscall backlog to +nr+ for yet-to-be-created
-    # sockets.  Due to limitations of the OS, this cannot affect
-    # existing listener sockets in any way, sockets must be completely
-    # closed and rebound (inherited sockets preserve their existing
-    # backlog setting).  Some operating systems allow negative values
-    # here to specify the maximum allowable value.  See the listen(2)
-    # syscall documentation of your OS for the exact semantics of this.
-    #
-    # If you are running unicorn on multiple machines, lowering this number
-    # can help your load balancer detect when a machine is overloaded
-    # and give requests to a different machine.
-    def backlog(nr)
-      Integer === nr or raise ArgumentError,
-         "not an integer: backlog=#{nr.inspect}"
-      @set[:backlog] = nr
-    end
-
     # sets object to the +new+ Logger-like object.  The new logger-like
     # object must respond to the following methods:
     #  +debug+, +info+, +warn+, +error+, +fatal+, +close+
@@ -116,23 +96,37 @@ module Unicorn
     # the worker after forking.  The following is an example hook which adds
     # a per-process listener to every worker:
     #
-    #  after_fork do |server,worker_nr|
+    #  after_fork do |server,worker|
     #    # per-process listener ports for debugging/admin:
     #    # "rescue nil" statement is needed because USR2 will
     #    # cause the master process to reexecute itself and the
     #    # per-worker ports can be taken, necessitating another
     #    # HUP after QUIT-ing the original master:
-    #    server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
+    #    server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
+    #
+    #    # drop permissions to "www-data" in the worker
+    #    # generally there's no reason to start Unicorn as a priviledged user
+    #    # as it is not recommended to expose Unicorn to public clients.
+    #    uid, gid = Process.euid, Process.egid
+    #    user, group = 'www-data', 'www-data'
+    #    target_uid = Etc.getpwnam(user).uid
+    #    target_gid = Etc.getgrnam(group).gid
+    #    worker.tempfile.chown(target_uid, target_gid)
+    #    if uid != target_uid || gid != target_gid
+    #      Process.initgroups(user, target_gid)
+    #      Process::GID.change_privilege(target_gid)
+    #      Process::UID.change_privilege(target_uid)
+    #    end
     #  end
-    def after_fork(&block)
-      set_hook(:after_fork, block)
+    def after_fork(*args, &block)
+      set_hook(:after_fork, block_given? ? block : args[0])
     end
 
     # sets before_fork got be a given Proc object.  This Proc
     # object will be called by the master process before forking
     # each worker.
-    def before_fork(&block)
-      set_hook(:before_fork, block)
+    def before_fork(*args, &block)
+      set_hook(:before_fork, block_given? ? block : args[0])
     end
 
     # sets the before_exec hook to a given Proc object.  This
@@ -141,20 +135,22 @@ module Unicorn
     # for freeing certain OS resources that you do NOT wish to
     # share with the reexeced child process.
     # There is no corresponding after_exec hook (for obvious reasons).
-    def before_exec(&block)
-      set_hook(:before_exec, block, 1)
+    def before_exec(*args, &block)
+      set_hook(:before_exec, block_given? ? block : args[0], 1)
     end
 
     # sets the timeout of worker processes to +seconds+.  Workers
     # handling the request/app.call/response cycle taking longer than
     # this time period will be forcibly killed (via SIGKILL).  This
     # timeout is enforced by the master process itself and not subject
-    # to the scheduling limitations by the worker process.
+    # to the scheduling limitations by the worker process.  Due the
+    # low-complexity, low-overhead implementation, timeouts of less
+    # than 3.0 seconds can be considered inaccurate and unsafe.
     def timeout(seconds)
       Numeric === seconds or raise ArgumentError,
                                   "not numeric: timeout=#{seconds.inspect}"
-      seconds > 0 or raise ArgumentError,
-                                  "not positive: timeout=#{seconds.inspect}"
+      seconds >= 3 or raise ArgumentError,
+                                  "too low: timeout=#{seconds.inspect}"
       @set[:timeout] = seconds
     end
 
@@ -171,13 +167,59 @@ module Unicorn
     # sets listeners to the given +addresses+, replacing or augmenting the
     # current set.  This is for the global listener pool shared by all
     # worker processes.  For per-worker listeners, see the after_fork example
-    def listeners(addresses)
+    # This is for internal API use only, do not use it in your Unicorn
+    # config file.  Use listen instead.
+    def listeners(addresses) # :nodoc:
       Array === addresses or addresses = Array(addresses)
+      addresses.map! { |addr| expand_addr(addr) }
       @set[:listeners] = addresses
     end
 
-    # adds an +address+ to the existing listener set
-    def listen(address)
+    # adds an +address+ to the existing listener set.
+    #
+    # The following options may be specified (but are generally not needed):
+    #
+    # +backlog+: this is the backlog of the listen() syscall.
+    #
+    # Some operating systems allow negative values here to specify the
+    # maximum allowable value.  In most cases, this number is only
+    # recommendation and there are other OS-specific tunables and
+    # variables that can affect this number.  See the listen(2)
+    # syscall documentation of your OS for the exact semantics of
+    # this.
+    #
+    # If you are running unicorn on multiple machines, lowering this number
+    # can help your load balancer detect when a machine is overloaded
+    # and give requests to a different machine.
+    #
+    # Default: 1024
+    #
+    # +rcvbuf+, +sndbuf+: maximum send and receive buffer sizes of sockets
+    #
+    # These correspond to the SO_RCVBUF and SO_SNDBUF settings which
+    # can be set via the setsockopt(2) syscall.  Some kernels
+    # (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and
+    # there is no need (and it is sometimes detrimental) to specify them.
+    #
+    # See the socket API documentation of your operating system
+    # to determine the exact semantics of these settings and
+    # other operating system-specific knobs where they can be
+    # specified.
+    #
+    # Defaults: operating system defaults
+    def listen(address, opt = { :backlog => 1024 })
+      address = expand_addr(address)
+      if String === address
+        Hash === @set[:listener_opts] or
+          @set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
+        [ :backlog, :sndbuf, :rcvbuf ].each do |key|
+          value = opt[key] or next
+          Integer === value or
+            raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
+        end
+        @set[:listener_opts][address].merge!(opt)
+      end
+
       @set[:listeners] = [] unless Array === @set[:listeners]
       @set[:listeners] << address
     end
@@ -194,6 +236,10 @@ module Unicorn
     # properly close/reopen sockets.  Files opened for logging do not
     # have to be reopened as (unbuffered-in-userspace) files opened with
     # the File::APPEND flag are written to atomically on UNIX.
+    #
+    # In addition to reloading the unicorn-specific config settings,
+    # SIGHUP will reload application code in the working
+    # directory/symlink when workers are gracefully restarted.
     def preload_app(bool)
       case bool
       when TrueClass, FalseClass
@@ -249,5 +295,28 @@ module Unicorn
       @set[var] = my_proc
     end
 
+    # expands "unix:path/to/foo" to a socket relative to the current path
+    # expands pathnames of sockets if relative to "~" or "~username"
+    # expands "*:port and ":port" to "0.0.0.0:port"
+    def expand_addr(address) #:nodoc
+      return "0.0.0.0:#{address}" if Integer === address
+      return address unless String === address
+
+      case address
+      when %r{\Aunix:(.*)\z}
+        File.expand_path($1)
+      when %r{\A~}
+        File.expand_path(address)
+      when %r{\A(?:\*:)?(\d+)\z}
+        "0.0.0.0:#$1"
+      when %r{\A(.*):(\d+)\z}
+        # canonicalize the name
+        packed = Socket.pack_sockaddr_in($2.to_i, $1)
+        Socket.unpack_sockaddr_in(packed).reverse!.join(':')
+      else
+        address
+      end
+    end
+
   end
 end
diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb
index 46398e5..241c52e 100644
--- a/lib/unicorn/const.rb
+++ b/lib/unicorn/const.rb
@@ -1,89 +1,19 @@
 
-module Unicorn
+require 'rack/utils'
 
-  # Every standard HTTP code mapped to the appropriate message.  These are
-  # used so frequently that they are placed directly in Unicorn for easy
-  # access rather than Unicorn::Const itself.
-  HTTP_STATUS_CODES = {  
-    100  => 'Continue',
-    101  => 'Switching Protocols',
-    200  => 'OK',
-    201  => 'Created',
-    202  => 'Accepted',
-    203  => 'Non-Authoritative Information',
-    204  => 'No Content',
-    205  => 'Reset Content',
-    206  => 'Partial Content',
-    300  => 'Multiple Choices',
-    301  => 'Moved Permanently',
-    302  => 'Moved Temporarily',
-    303  => 'See Other',
-    304  => 'Not Modified',
-    305  => 'Use Proxy',
-    400  => 'Bad Request',
-    401  => 'Unauthorized',
-    402  => 'Payment Required',
-    403  => 'Forbidden',
-    404  => 'Not Found',
-    405  => 'Method Not Allowed',
-    406  => 'Not Acceptable',
-    407  => 'Proxy Authentication Required',
-    408  => 'Request Time-out',
-    409  => 'Conflict',
-    410  => 'Gone',
-    411  => 'Length Required',
-    412  => 'Precondition Failed',
-    413  => 'Request Entity Too Large',
-    414  => 'Request-URI Too Large',
-    415  => 'Unsupported Media Type',
-    500  => 'Internal Server Error',
-    501  => 'Not Implemented',
-    502  => 'Bad Gateway',
-    503  => 'Service Unavailable',
-    504  => 'Gateway Time-out',
-    505  => 'HTTP Version not supported'
-  }
+module Unicorn
 
   # Frequently used constants when constructing requests or responses.  Many times
   # the constant just refers to a string with the same contents.  Using these constants
   # gave about a 3% to 10% performance improvement over using the strings directly.
   # Symbols did not really improve things much compared to constants.
-  #
-  # While Unicorn does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
-  # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
-  # too taxing on performance.
   module Const
-    DATE="Date".freeze
-
-    # This is the part of the path after the SCRIPT_NAME.
-    PATH_INFO="PATH_INFO".freeze
-    
-    # Request body
-    HTTP_BODY="HTTP_BODY".freeze
-
-    # This is the initial part that your handler is identified as by URIClassifier.
-    SCRIPT_NAME="SCRIPT_NAME".freeze
-
-    # The original URI requested by the client.  Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
-    REQUEST_URI='REQUEST_URI'.freeze
-    REQUEST_PATH='REQUEST_PATH'.freeze
-    
-    UNICORN_VERSION="0.1.0".freeze
-
-    UNICORN_TMP_BASE="unicorn".freeze
+    UNICORN_VERSION="0.7.0".freeze
 
     DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
     DEFAULT_PORT = "8080".freeze    # default TCP listen port
     DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}".freeze
 
-    # The standard empty 404 response for bad requests.  Use Error4040Handler for custom stuff.
-    ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Unicorn #{UNICORN_VERSION}\r\n\r\nNOT FOUND".freeze
-
-    CONTENT_LENGTH="CONTENT_LENGTH".freeze
-
-    # A common header for indicating the server is too busy.  Not used yet.
-    ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
-
     # The basic max request size we'll try to read.
     CHUNK_SIZE=(16 * 1024)
 
@@ -94,23 +24,15 @@ module Unicorn
     # Maximum request body size before it is moved out of memory and into a tempfile for reading.
     MAX_BODY=MAX_HEADER
 
+    # common errors we'll send back
+    ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
+    ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
+
     # A frozen format for this is about 15% faster
-    CONTENT_TYPE = "Content-Type".freeze
-    LAST_MODIFIED = "Last-Modified".freeze
-    ETAG = "ETag".freeze
-    REQUEST_METHOD="REQUEST_METHOD".freeze
-    GET="GET".freeze
-    HEAD="HEAD".freeze
-    # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
-    ETAG_FORMAT="\"%x-%x-%x\"".freeze
-    LINE_END="\r\n".freeze
+    CONTENT_LENGTH="CONTENT_LENGTH".freeze
     REMOTE_ADDR="REMOTE_ADDR".freeze
     HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
-    HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze
-    HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze
-    REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
-    HOST = "HOST".freeze
-    CONNECTION = "Connection".freeze
+    RACK_INPUT="rack.input".freeze
   end
 
 end
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index ce0e408..368305f 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -1,5 +1,4 @@
 require 'tempfile'
-require 'uri'
 require 'stringio'
 
 # compiled extension
@@ -13,165 +12,140 @@ module Unicorn
   #
   class HttpRequest
 
+    # default parameters we merge into the request env for Rack handlers
+    DEFAULTS = {
+      "rack.errors" => $stderr,
+      "rack.multiprocess" => true,
+      "rack.multithread" => false,
+      "rack.run_once" => false,
+      "rack.version" => [1, 0].freeze,
+      "SCRIPT_NAME" => "".freeze,
+
+      # this is not in the Rack spec, but some apps may rely on it
+      "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze
+    }
+
+    # Optimize for the common case where there's no request body
+    # (GET/HEAD) requests.
+    NULL_IO = StringIO.new
+    LOCALHOST = '127.0.0.1'.freeze
+
+    # Being explicitly single-threaded, we have certain advantages in
+    # not having to worry about variables being clobbered :)
+    BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
+    PARSER = HttpParser.new
+    PARAMS = Hash.new
+
     def initialize(logger)
       @logger = logger
-      @body = nil
-      @buffer = ' ' * Const::CHUNK_SIZE # initial size, may grow
-      @parser = HttpParser.new
-      @params = Hash.new
-    end
-
-    def reset
-      @parser.reset
-      @params.clear
-      @body.close rescue nil
-      @body = nil
     end
 
-    #
     # Does the majority of the IO processing.  It has been written in
-    # Ruby using about 7 different IO processing strategies and no
-    # matter how it's done the performance just does not improve.  It is
-    # currently carefully constructed to make sure that it gets the best
-    # possible performance, but anyone who thinks they can make it
-    # faster is more than welcome to take a crack at it.
+    # Ruby using about 8 different IO processing strategies.
+    #
+    # It is currently carefully constructed to make sure that it gets
+    # the best possible performance for the common case: GET requests
+    # that are fully complete after a single read(2)
+    #
+    # Anyone who thinks they can make it faster is more than welcome to
+    # take a crack at it.
     #
     # returns an environment hash suitable for Rack if successful
     # This does minimal exception trapping and it is up to the caller
     # to handle any socket errors (e.g. user aborted upload).
     def read(socket)
-      data = String.new(read_socket(socket))
-      nparsed = 0
-
-      # Assumption: nparsed will always be less since data will get
-      # filled with more after each parsing.  If it doesn't get more
-      # then there was a problem with the read operation on the client
-      # socket.  Effect is to stop processing when the socket can't
-      # fill the buffer for further parsing.
-      while nparsed < data.length
-        nparsed = @parser.execute(@params, data, nparsed)
-
-        if @parser.finished?
-          # From http://www.ietf.org/rfc/rfc3875:
-          # "Script authors should be aware that the REMOTE_ADDR and
-          #  REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
-          #  may not identify the ultimate source of the request.  They
-          #  identify the client for the immediate request to the server;
-          #  that client may be a proxy, gateway, or other intermediary
-          #  acting on behalf of the actual source client."
-          @params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
-
-          handle_body(socket) and return rack_env # success!
-          return nil # fail
-        else
-          # Parser is not done, queue up more data to read and continue
-          # parsing
-          data << read_socket(socket)
-          if data.length >= Const::MAX_HEADER
-            raise HttpParserError.new("HEADER is longer than allowed, " \
-                                      "aborting client early.")
-          end
-        end
+      # reset the parser
+      unless NULL_IO == (input = PARAMS[Const::RACK_INPUT]) # unlikely
+        input.close rescue nil
+        input.close! rescue nil
       end
-      nil # XXX bug?
+      PARAMS.clear
+      PARSER.reset
+
+      # From http://www.ietf.org/rfc/rfc3875:
+      # "Script authors should be aware that the REMOTE_ADDR and
+      #  REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
+      #  may not identify the ultimate source of the request.  They
+      #  identify the client for the immediate request to the server;
+      #  that client may be a proxy, gateway, or other intermediary
+      #  acting on behalf of the actual source client."
+      PARAMS[Const::REMOTE_ADDR] =
+                    TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
+
+      # short circuit the common case with small GET requests first
+      PARSER.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and
+          return handle_body(socket)
+
+      data = BUFFER.dup # socket.readpartial will clobber BUFFER
+
+      # Parser is not done, queue up more data to read and continue parsing
+      # an Exception thrown from the PARSER will throw us out of the loop
+      begin
+        data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
+        PARSER.execute(PARAMS, data) and return handle_body(socket)
+      end while true
       rescue HttpParserError => e
         @logger.error "HTTP parse error, malformed request " \
-                      "(#{@params[Const::HTTP_X_FORWARDED_FOR] ||
-                          socket.unicorn_peeraddr}): #{e.inspect}"
+                      "(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] ||
+                          PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}"
         @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
-                      "PARAMS: #{@params.inspect}\n---\n"
-        socket.closed? or socket.close rescue nil
-        nil
+                      "PARAMS: #{PARAMS.inspect}\n---\n"
+        raise e
     end
 
     private
 
     # Handles dealing with the rest of the request
-    # returns true if successful, false if not
+    # returns a Rack environment if successful, raises an exception if not
     def handle_body(socket)
-      http_body = @params[Const::HTTP_BODY]
-      content_length = @params[Const::CONTENT_LENGTH].to_i
-      remain = content_length - http_body.length
+      http_body = PARAMS.delete(:http_body)
+      content_length = PARAMS[Const::CONTENT_LENGTH].to_i
 
-      # must read more data to complete body
-      if remain < Const::MAX_BODY
-        # small body, just use that
-        @body = StringIO.new(http_body)
-      else # huge body, put it in a tempfile
-        @body = Tempfile.new(Const::UNICORN_TMP_BASE)
-        @body.binmode
-        @body.sync = true
-        @body.syswrite(http_body)
+      if content_length == 0 # short circuit the common case
+        PARAMS[Const::RACK_INPUT] = NULL_IO.closed? ? NULL_IO.reopen : NULL_IO
+        return PARAMS.update(DEFAULTS)
       end
 
+      # must read more data to complete body
+      remain = content_length - http_body.length
+
+      body = PARAMS[Const::RACK_INPUT] = (remain < Const::MAX_BODY) ?
+          StringIO.new : Tempfile.new('unicorn')
+
+      body.binmode
+      body.write(http_body)
+
       # Some clients (like FF1.0) report 0 for body and then send a body.
       # This will probably truncate them but at least the request goes through
       # usually.
-      if remain > 0
-        read_body(socket, remain) or return false # fail!
-      end
-      @body.rewind
-      @body.sysseek(0) if @body.respond_to?(:sysseek)
+      read_body(socket, remain, body) if remain > 0
+      body.rewind
 
       # in case read_body overread because the client tried to pipeline
       # another request, we'll truncate it.  Again, we don't do pipelining
       # or keepalive
-      @body.truncate(content_length)
-      true
+      body.truncate(content_length)
+      PARAMS.update(DEFAULTS)
     end
 
-    # Returns an environment which is rackable:
-    # http://rack.rubyforge.org/doc/files/SPEC.html
-    # Based on Rack's old Mongrel handler.
-    def rack_env
-      # It might be a dumbass full host request header
-      @params[Const::REQUEST_PATH] ||=
-                           URI.parse(@params[Const::REQUEST_URI]).path
-      raise "No REQUEST PATH" unless @params[Const::REQUEST_PATH]
-
-      @params["QUERY_STRING"] ||= ''
-      @params.delete "HTTP_CONTENT_TYPE"
-      @params.delete "HTTP_CONTENT_LENGTH"
-      @params.update({ "rack.version" => [0,1],
-                      "rack.input" => @body,
-                      "rack.errors" => $stderr,
-                      "rack.multithread" => false,
-                      "rack.multiprocess" => true,
-                      "rack.run_once" => false,
-                      "rack.url_scheme" => "http",
-                      Const::PATH_INFO => @params[Const::REQUEST_PATH],
-                      Const::SCRIPT_NAME => "",
-                    })
-    end
-
-    # Does the heavy lifting of properly reading the larger body requests in
-    # small chunks.  It expects @body to be an IO object, socket to be valid,
-    # It also expects any initial part of the body that has been read to be in
-    # the @body already.  It will return true if successful and false if not.
-    def read_body(socket, remain)
-      while remain > 0
-        # writes always write the requested amount on a POSIX filesystem
-        remain -= @body.syswrite(read_socket(socket))
-      end
-      true # success!
+    # Does the heavy lifting of properly reading the larger body
+    # requests in small chunks.  It expects PARAMS['rack.input'] to be
+    # an IO object, socket to be valid, It also expects any initial part
+    # of the body that has been read to be in the PARAMS['rack.input']
+    # already.  It will return true if successful and false if not.
+    def read_body(socket, remain, body)
+      begin
+        # write always writes the requested amount on a POSIX filesystem
+        remain -= body.write(socket.readpartial(Const::CHUNK_SIZE, BUFFER))
+      end while remain > 0
     rescue Object => e
-      logger.error "Error reading HTTP body: #{e.inspect}"
-      socket.closed? or socket.close rescue nil
+      @logger.error "Error reading HTTP body: #{e.inspect}"
 
       # Any errors means we should delete the file, including if the file
       # is dumped.  Truncate it ASAP to help avoid page flushes to disk.
-      @body.truncate(0) rescue nil
+      body.truncate(0) rescue nil
       reset
-      false
-    end
-
-    # read(2) on "slow" devices like sockets can be interrupted by signals
-    def read_socket(socket)
-      begin
-        socket.sysread(Const::CHUNK_SIZE, @buffer)
-      rescue Errno::EINTR
-        retry
-      end
+      raise e
     end
 
   end
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index 7bbb940..15df3f6 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -21,52 +21,50 @@ module Unicorn
 
   class HttpResponse
 
-    # headers we allow duplicates for
-    ALLOWED_DUPLICATES = {
-      'Set-Cookie' => true,
-      'Set-Cookie2' => true,
-      'Warning' => true,
-      'WWW-Authenticate' => true,
-    }.freeze
+    # Every standard HTTP code mapped to the appropriate message.
+    CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
+      hash[code] = "#{code} #{msg}"
+      hash
+    }
+
+    # Rack does not set/require a Date: header.  We always override the
+    # Connection: and Date: headers no matter what (if anything) our
+    # Rack application sent us.
+    SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
+    EMPTY = ''.freeze # :nodoc
+    OUT = [] # :nodoc
 
     # writes the rack_response to socket as an HTTP response
     def self.write(socket, rack_response)
       status, headers, body = rack_response
+      status = CODES[status.to_i]
+      OUT.clear
 
-      # Rack does not set/require Date, but don't worry about Content-Length
-      # since Rack applications that conform to Rack::Lint enforce that
-      out = [ "#{Const::DATE}: #{Time.now.httpdate}" ]
-      sent = { Const::CONNECTION => true, Const::DATE => true }
-
+      # Don't bother enforcing duplicate supression, it's a Hash most of
+      # the time anyways so just hope our app knows what it's doing
       headers.each do |key, value|
-        if ! sent[key] || ALLOWED_DUPLICATES[key]
-          sent[key] = true
-          out << "#{key}: #{value}"
+        next if SKIP.include?(key.downcase)
+        if value =~ /\n/
+          value.split(/\n/).each { |v| OUT << "#{key}: #{v}\r\n" }
+        else
+          OUT << "#{key}: #{value}\r\n"
         end
       end
 
-      socket_write(socket,
-                   "HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n" \
+      # Rack should enforce Content-Length or chunked transfer encoding,
+      # so don't worry or care about them.
+      # Date is required by HTTP/1.1 as long as our clock can be trusted.
+      # Some broken clients require a "Status" header so we accomodate them
+      socket.write("HTTP/1.1 #{status}\r\n" \
+                   "Date: #{Time.now.httpdate}\r\n" \
+                   "Status: #{status}\r\n" \
                    "Connection: close\r\n" \
-                   "#{out.join("\r\n")}\r\n\r\n")
-      body.each { |chunk| socket_write(socket, chunk) }
+                   "#{OUT.join(EMPTY)}\r\n")
+      body.each { |chunk| socket.write(chunk) }
+      socket.close # flushes and uncorks the socket immediately
+      ensure
+        body.respond_to?(:close) and body.close rescue nil
     end
 
-    private
-
-      # write(2) can return short on slow devices like sockets as well
-      # as fail with EINTR if a signal was caught.
-      def self.socket_write(socket, buffer)
-        loop do
-          begin
-            written = socket.syswrite(buffer)
-            return written if written == buffer.length
-            buffer = buffer[written..-1]
-          rescue Errno::EINTR
-            retry
-          end
-        end
-      end
-
   end
 end
diff --git a/lib/unicorn/launcher.rb b/lib/unicorn/launcher.rb
new file mode 100644
index 0000000..8c96059
--- /dev/null
+++ b/lib/unicorn/launcher.rb
@@ -0,0 +1,33 @@
+$stdin.sync = $stdout.sync = $stderr.sync = true
+require 'unicorn'
+
+class Unicorn::Launcher
+
+  # We don't do a lot of standard daemonization stuff:
+  #   * umask is whatever was set by the parent process at startup
+  #     and can be set in config.ru and config_file, so making it
+  #     0000 and potentially exposing sensitive log data can be bad
+  #     policy.
+  #   * don't bother to chdir("/") here since unicorn is designed to
+  #     run inside APP_ROOT.  Unicorn will also re-chdir() to
+  #     the directory it was started in when being re-executed
+  #     to pickup code changes if the original deployment directory
+  #     is a symlink or otherwise got replaced.
+  def self.daemonize!
+    $stdin.reopen("/dev/null")
+
+    # We only start a new process group if we're not being reexecuted
+    # and inheriting file descriptors from our parent
+    unless ENV['UNICORN_FD']
+      exit if fork
+      Process.setsid
+      exit if fork
+
+      # $stderr/$stderr can/will be redirected separately in the Unicorn config
+      $stdout.reopen("/dev/null", "a")
+      $stderr.reopen("/dev/null", "a")
+    end
+    $stdin.sync = $stdout.sync = $stderr.sync = true
+  end
+
+end
diff --git a/lib/unicorn/socket.rb b/lib/unicorn/socket.rb
deleted file mode 100644
index 4913261..0000000
--- a/lib/unicorn/socket.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-require 'fcntl'
-require 'socket'
-require 'io/nonblock'
-
-# non-portable Socket code goes here:
-class Socket
-  module Constants
-    # configure platform-specific options (only tested on Linux 2.6 so far)
-    case RUBY_PLATFORM
-    when /linux/
-      # from /usr/include/linux/tcp.h
-      TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
-      TCP_CORK = 3 unless defined?(TCP_CORK)
-    when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
-    when /freebsd/
-      # Use the HTTP accept filter if available.
-      # The struct made by pack() is defined in /usr/include/sys/socket.h
-      # as accept_filter_arg
-      unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
-        unless defined?(SO_ACCEPTFILTER_HTTPREADY)
-          SO_ACCEPTFILTER_HTTPREADY = ['httpready',nil].pack('a16a240').freeze
-        end
-
-      end
-    end
-  end
-end
-
-class UNIXSocket
-  UNICORN_PEERADDR = '127.0.0.1'.freeze
-  def unicorn_peeraddr
-    UNICORN_PEERADDR
-  end
-end
-
-class TCPSocket
-  def unicorn_peeraddr
-    peeraddr.last
-  end
-end
-
-module Unicorn
-  module SocketHelper
-    include Socket::Constants
-
-    def set_client_sockopt(sock)
-      sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
-      sock.setsockopt(SOL_TCP, TCP_CORK, 1) if defined?(TCP_CORK)
-    end
-
-    def set_cloexec(io)
-      io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined?(Fcntl::FD_CLOEXEC)
-    end
-
-    def set_server_sockopt(sock)
-      if defined?(TCP_DEFER_ACCEPT)
-        sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
-      end
-      if defined?(SO_ACCEPTFILTER_HTTPREADY)
-        sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER,
-                        SO_ACCEPTFILTER_HTTPREADY) rescue nil
-      end
-    end
-
-    def destroy_safely(io)
-      if io.respond_to?(:path) && File.stat(io.path).ino == io.stat.ino
-        File.unlink(io.path) rescue nil
-      end
-      io.close rescue nil
-    end
-
-    # creates a new server, socket. address may be a HOST:PORT or
-    # an absolute path to a UNIX socket.  address can even be a Socket
-    # object in which case it is immediately returned
-    def bind_listen(address = '0.0.0.0:8080', backlog = 1024)
-      return address unless String === address
-
-      domain, bind_addr = if address[0..0] == "/"
-        if File.exist?(address)
-          if File.socket?(address)
-            if self.respond_to?(:logger)
-              logger.info "unlinking existing socket=#{address}"
-            end
-            File.unlink(address)
-          else
-            raise ArgumentError,
-                  "socket=#{address} specified but it is not a socket!"
-          end
-        end
-        [ AF_UNIX, Socket.pack_sockaddr_un(address) ]
-      elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
-        [ AF_INET, Socket.pack_sockaddr_in($2.to_i, $1) ]
-      else
-        raise ArgumentError, "Don't know how to bind: #{address}"
-      end
-
-      sock = Socket.new(domain, SOCK_STREAM, 0)
-      sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) if defined?(SO_REUSEADDR)
-      begin
-        sock.bind(bind_addr)
-      rescue Errno::EADDRINUSE
-        sock.close rescue nil
-        return nil
-      end
-      sock.listen(backlog)
-      set_server_sockopt(sock) if domain == AF_INET
-      sock
-    end
-
-    # Returns the configuration name of a socket as a string.  sock may
-    # be a string value, in which case it is returned as-is
-    # Warning: TCP sockets may not always return the name given to it.
-    def sock_name(sock)
-      case sock
-      when String then sock
-      when UNIXServer
-        Socket.unpack_sockaddr_un(sock.getsockname)
-      when TCPServer
-        Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
-      when Socket
-        begin
-          Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
-        rescue ArgumentError
-          Socket.unpack_sockaddr_un(sock.getsockname)
-        end
-      else
-        raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
-      end
-    end
-
-    # casts a given Socket to be a TCPServer or UNIXServer
-    def server_cast(sock)
-      begin
-        Socket.unpack_sockaddr_in(sock.getsockname)
-        TCPServer.for_fd(sock.fileno)
-      rescue ArgumentError
-        UNIXServer.for_fd(sock.fileno)
-      end
-    end
-
-  end # module SocketHelper
-end # module Unicorn
diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb
new file mode 100644
index 0000000..850ad85
--- /dev/null
+++ b/lib/unicorn/socket_helper.rb
@@ -0,0 +1,90 @@
+require 'socket'
+
+module Unicorn
+  module SocketHelper
+    include Socket::Constants
+
+    def set_server_sockopt(sock, opt)
+      opt ||= {}
+      if opt[:rcvbuf] || opt[:sndbuf]
+        log_buffer_sizes(sock, "before: ")
+        sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
+        sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
+        log_buffer_sizes(sock, " after: ")
+      end
+      sock.listen(opt[:backlog] || 1024)
+    end
+
+    def log_buffer_sizes(sock, pfx = '')
+      respond_to?(:logger) or return
+      rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
+      sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
+      logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
+    end
+
+    # creates a new server, socket. address may be a HOST:PORT or
+    # an absolute path to a UNIX socket.  address can even be a Socket
+    # object in which case it is immediately returned
+    def bind_listen(address = '0.0.0.0:8080', opt = { :backlog => 1024 })
+      return address unless String === address
+
+      sock = if address[0..0] == "/"
+        if File.exist?(address)
+          if File.socket?(address)
+            if self.respond_to?(:logger)
+              logger.info "unlinking existing socket=#{address}"
+            end
+            File.unlink(address)
+          else
+            raise ArgumentError,
+                  "socket=#{address} specified but it is not a socket!"
+          end
+        end
+        old_umask = File.umask(0)
+        begin
+          UNIXServer.new(address)
+        ensure
+          File.umask(old_umask)
+        end
+      elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
+        TCPServer.new($1, $2.to_i)
+      else
+        raise ArgumentError, "Don't know how to bind: #{address}"
+      end
+      set_server_sockopt(sock, opt)
+      sock
+    end
+
+    # Returns the configuration name of a socket as a string.  sock may
+    # be a string value, in which case it is returned as-is
+    # Warning: TCP sockets may not always return the name given to it.
+    def sock_name(sock)
+      case sock
+      when String then sock
+      when UNIXServer
+        Socket.unpack_sockaddr_un(sock.getsockname)
+      when TCPServer
+        Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
+      when Socket
+        begin
+          Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
+        rescue ArgumentError
+          Socket.unpack_sockaddr_un(sock.getsockname)
+        end
+      else
+        raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
+      end
+    end
+
+    # casts a given Socket to be a TCPServer or UNIXServer
+    def server_cast(sock)
+      begin
+        Socket.unpack_sockaddr_in(sock.getsockname)
+        TCPServer.for_fd(sock.fileno)
+      rescue ArgumentError
+        UNIXServer.for_fd(sock.fileno)
+      end
+    end
+
+  end # module SocketHelper
+end # module Unicorn
diff --git a/lib/unicorn/util.rb b/lib/unicorn/util.rb
index 0400fd0..2d3f827 100644
--- a/lib/unicorn/util.rb
+++ b/lib/unicorn/util.rb
@@ -4,6 +4,8 @@ module Unicorn
   class Util
     class << self
 
+      APPEND_FLAGS = File::WRONLY | File::APPEND
+
       # this reopens logs that have been rotated (using logrotate(8) or
       # similar).  It is recommended that you install
       # A +File+ object is considered for reopening if it is:
@@ -17,17 +19,20 @@ module Unicorn
         ObjectSpace.each_object(File) do |fp|
           next if fp.closed?
           next unless (fp.sync && fp.path[0..0] == "/")
-
-          flags = fp.fcntl(Fcntl::F_GETFL)
-          open_flags = File::WRONLY | File::APPEND
-          next unless (flags & open_flags) == open_flags
+          next unless (fp.fcntl(Fcntl::F_GETFL) & APPEND_FLAGS) == APPEND_FLAGS
 
           begin
             a, b = fp.stat, File.stat(fp.path)
             next if a.ino == b.ino && a.dev == b.dev
           rescue Errno::ENOENT
           end
-          fp.reopen(fp.path, "a")
+
+          open_arg = 'a'
+          if fp.respond_to?(:external_encoding) && enc = fp.external_encoding
+            open_arg << ":#{enc.to_s}"
+            enc = fp.internal_encoding and open_arg << ":#{enc.to_s}"
+          end
+          fp.reopen(fp.path, open_arg)
           fp.sync = true
           nr += 1
         end # each_object