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/http_request.rb14
-rw-r--r--lib/unicorn/http_response.rb5
-rw-r--r--lib/unicorn/socket.rb158
3 files changed, 111 insertions, 66 deletions
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index 0a8c5b1..47600d6 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -1,3 +1,9 @@
+require 'tempfile'
+require 'uri'
+require 'stringio'
+
+# compiled extension
+require 'http11'
 
 module Unicorn
   #
@@ -54,7 +60,7 @@ module Unicorn
           #  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.last
+          @params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
 
           handle_body(socket) and return rack_env # success!
           return nil # fail
@@ -72,10 +78,10 @@ module Unicorn
       rescue HttpParserError => e
         @logger.error "HTTP parse error, malformed request " \
                       "(#{@params[Const::HTTP_X_FORWARDED_FOR] ||
-                          socket.unicorn_peeraddr.last}): #{e.inspect}"
+                          socket.unicorn_peeraddr}): #{e.inspect}"
         @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
                       "PARAMS: #{@params.inspect}\n---\n"
-        socket.close rescue nil
+        socket.closed? or socket.close rescue nil
         nil
     end
 
@@ -152,7 +158,7 @@ module Unicorn
       true # success!
     rescue Object => e
       logger.error "Error reading HTTP body: #{e.inspect}"
-      socket.close rescue nil
+      socket.closed? or socket.close rescue nil
 
       # Any errors means we should delete the file, including if the file
       # is dumped.  Truncate it ASAP to help avoid page flushes to disk.
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index eab3a82..1192d48 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -1,3 +1,5 @@
+require 'time'
+
 module Unicorn
   # Writes a Rack response to your client using the HTTP/1.1 specification.
   # You use it by simply doing:
@@ -33,11 +35,12 @@ module Unicorn
       'WWW-Authenticate' => true,
     }.freeze
 
+    # writes the rack_response to socket as an HTTP response
     def self.write(socket, rack_response)
       status, headers, body = rack_response
 
       # Rack does not set/require Date, but don't worry about Content-Length
-      # since Rack enforces that in Rack::Lint.
+      # since Rack applications that conform to Rack::Lint enforce that
       out = [ "#{Const::DATE}: #{Time.now.httpdate}\r\n" ]
       sent = { Const::CONNECTION => true, Const::DATE => true }
 
diff --git a/lib/unicorn/socket.rb b/lib/unicorn/socket.rb
index 3f567c6..bc09688 100644
--- a/lib/unicorn/socket.rb
+++ b/lib/unicorn/socket.rb
@@ -1,84 +1,120 @@
+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
 
-  # 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)
-
-    def unicorn_server_init
-      self.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
-    end
-  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
-
-      def unicorn_server_init
-        self.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, SO_ACCEPTFILTER_HTTPREADY)
       end
     end
   end
+end
 
-  def unicorn_client_init
-    self.sync = true
-    self.nonblock = false
-    self.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
-    self.setsockopt(SOL_TCP, TCP_CORK, 1) if defined?(TCP_CORK)
+class UNIXSocket
+  UNICORN_PEERADDR = '127.0.0.1'.freeze
+  def unicorn_peeraddr
+    UNICORN_PEERADDR
   end
+end
 
+class TCPSocket
   def unicorn_peeraddr
-    Socket.unpack_sockaddr_in(getpeername)
+    peeraddr.last
   end
+end
 
-  # returns the config-friendly name of the current listener socket, this is
-  # useful for config reloads and even works across execs where the Unicorn
-  # binary is replaced
-  def unicorn_addr
-    @unicorn_addr ||= if respond_to?(:getsockname)
-      port, host = Socket.unpack_sockaddr_in(getsockname)
-      "#{host}:#{port}"
-    elsif respond_to?(:getsockname)
-      addr = Socket.unpack_sockaddr_un(getsockname)
-      # strip the pid from the temp socket path
-      addr.gsub!(/\.\d+\.tmp$/, '') or
-        raise ArgumentError, "PID not found in path: #{addr}"
-    else
-      raise ArgumentError, "could not determine unicorn_addr for #{self}"
+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
-  end
 
-  class << self
+    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
+
+    # 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 if address.kind_of?(Socket)
 
-    # creates a new server, address may be a HOST:PORT or
-    # an absolute path to a UNIX socket.  When creating a UNIX
-    # socket to listen on, we always add a PID suffix to it
-    # when binding and then rename it into its intended name to
-    # atomically replace and start listening for new connections.
-    def unicorn_server_new(address = '0.0.0.0:8080', backlog = 1024)
       domain, bind_addr = if address[0..0] == "/"
-        [ AF_UNIX, pack_sockaddr_un("#{address}.#{$$}.tmp") ]
+        [ AF_UNIX, Socket.pack_sockaddr_un(address) ]
       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
-        [ AF_INET, pack_sockaddr_in($2.to_i, $1) ]
+        [ AF_INET, Socket.pack_sockaddr_in($2.to_i, $1) ]
+      else
+        raise ArgumentError, "Don't know how to bind: #{address}"
       end
-      s = new(domain, SOCK_STREAM, 0)
-      s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) if defined?(SO_REUSEADDR)
-      s.bind(bind_addr)
-      s.listen(backlog)
-      s.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined?(Fcntl::FD_CLOEXEC)
 
-      # atomically replace existing domain socket
-      File.rename("#{address}.#{$$}.tmp", address) if domain == AF_UNIX
-      s
+      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
 
-  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
 
-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