Refactor and get exec + FD inheritance working
[unicorn.git] / lib / unicorn / socket.rb
blobbc09688c2e4ec20b22e89bcc360a7ead77ca7a50
1 require 'fcntl'
2 require 'socket'
3 require 'io/nonblock'
5 # non-portable Socket code goes here:
6 class Socket
7   module Constants
8     # configure platform-specific options (only tested on Linux 2.6 so far)
9     case RUBY_PLATFORM
10     when /linux/
11       # from /usr/include/linux/tcp.h
12       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
13       TCP_CORK = 3 unless defined?(TCP_CORK)
14     when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
15     when /freebsd/
16       # Use the HTTP accept filter if available.
17       # The struct made by pack() is defined in /usr/include/sys/socket.h
18       # as accept_filter_arg
19       unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
20         unless defined?(SO_ACCEPTFILTER_HTTPREADY)
21           SO_ACCEPTFILTER_HTTPREADY = ['httpready',nil].pack('a16a240').freeze
22         end
24       end
25     end
26   end
27 end
29 class UNIXSocket
30   UNICORN_PEERADDR = '127.0.0.1'.freeze
31   def unicorn_peeraddr
32     UNICORN_PEERADDR
33   end
34 end
36 class TCPSocket
37   def unicorn_peeraddr
38     peeraddr.last
39   end
40 end
42 module Unicorn
43   module SocketHelper
44     include Socket::Constants
46     def set_client_sockopt(sock)
47       sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
48       sock.setsockopt(SOL_TCP, TCP_CORK, 1) if defined?(TCP_CORK)
49     end
51     def set_server_sockopt(sock)
52       if defined?(TCP_DEFER_ACCEPT)
53         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
54       end
55       if defined?(SO_ACCEPTFILTER_HTTPREADY)
56         sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER,
57                         SO_ACCEPTFILTER_HTTPREADY) rescue nil
58       end
59     end
61     # creates a new server, socket. address may be a HOST:PORT or
62     # an absolute path to a UNIX socket.  address can even be a Socket
63     # object in which case it is immediately returned
64     def bind_listen(address = '0.0.0.0:8080', backlog = 1024)
65       return address if address.kind_of?(Socket)
67       domain, bind_addr = if address[0..0] == "/"
68         [ AF_UNIX, Socket.pack_sockaddr_un(address) ]
69       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
70         [ AF_INET, Socket.pack_sockaddr_in($2.to_i, $1) ]
71       else
72         raise ArgumentError, "Don't know how to bind: #{address}"
73       end
75       sock = Socket.new(domain, SOCK_STREAM, 0)
76       sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) if defined?(SO_REUSEADDR)
77       begin
78         sock.bind(bind_addr)
79       rescue Errno::EADDRINUSE
80         sock.close rescue nil
81         return nil
82       end
83       sock.listen(backlog)
84       set_server_sockopt(sock) if domain == AF_INET
85       sock
86     end
88     # Returns the configuration name of a socket as a string.  sock may
89     # be a string value, in which case it is returned as-is
90     # Warning: TCP sockets may not always return the name given to it.
91     def sock_name(sock)
92       case sock
93       when String then sock
94       when UNIXServer
95         Socket.unpack_sockaddr_un(sock.getsockname)
96       when TCPServer
97         Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
98       when Socket
99         begin
100           Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
101         rescue ArgumentError
102           Socket.unpack_sockaddr_un(sock.getsockname)
103         end
104       else
105         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
106       end
107     end
109     # casts a given Socket to be a TCPServer or UNIXServer
110     def server_cast(sock)
111       begin
112         Socket.unpack_sockaddr_in(sock.getsockname)
113         TCPServer.for_fd(sock.fileno)
114       rescue ArgumentError
115         UNIXServer.for_fd(sock.fileno)
116       end
117     end
119   end # module SocketHelper
120 end # module Unicorn