diff options
Diffstat (limited to 'lib/unicorn')
-rw-r--r-- | lib/unicorn/http_request.rb | 14 | ||||
-rw-r--r-- | lib/unicorn/http_response.rb | 5 | ||||
-rw-r--r-- | lib/unicorn/socket.rb | 158 |
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 |