add global Unicorn.listener_names method
[unicorn.git] / lib / unicorn / socket_helper.rb
blob9a4266df1b5b170bef19716098ad8f1135a26321
1 # -*- encoding: binary -*-
3 require 'socket'
5 module Unicorn
6   module SocketHelper
7     include Socket::Constants
9     # configure platform-specific options (only tested on Linux 2.6 so far)
10     case RUBY_PLATFORM
11     when /linux/
12       # from /usr/include/linux/tcp.h
13       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
15       # do not send out partial frames (Linux)
16       TCP_CORK = 3 unless defined?(TCP_CORK)
17     when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
18       # Do nothing for httpready, just closing a bug when freebsd <= 5.4
19       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) # :nodoc:
20     when /freebsd/
21       # do not send out partial frames (FreeBSD)
22       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
24       # Use the HTTP accept filter if available.
25       # The struct made by pack() is defined in /usr/include/sys/socket.h
26       # as accept_filter_arg
27       unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
28         # set set the "httpready" accept filter in FreeBSD if available
29         # if other protocols are to be supported, this may be
30         # String#replace-d with "dataready" arguments instead
31         FILTER_ARG = ['httpready', nil].pack('a16a240')
32       end
33     end
35     def set_tcp_sockopt(sock, opt)
37       # highly portable, but off by default because we don't do keepalive
38       if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
39         sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
40       end
42       unless (val = opt[:tcp_nopush]).nil?
43         val = val ? 1 : 0
44         if defined?(TCP_CORK) # Linux
45           sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
46         elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
47           sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
48         end
49       end
51       # No good reason to ever have deferred accepts off
52       if defined?(TCP_DEFER_ACCEPT)
53         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
54       elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
55         sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG)
56       end
57     end
59     def set_server_sockopt(sock, opt)
60       opt ||= {}
62       TCPSocket === sock and set_tcp_sockopt(sock, opt)
64       if opt[:rcvbuf] || opt[:sndbuf]
65         log_buffer_sizes(sock, "before: ")
66         sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
67         sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
68         log_buffer_sizes(sock, " after: ")
69       end
70       sock.listen(opt[:backlog] || 1024)
71       rescue => e
72         if respond_to?(:logger)
73           logger.error "error setting socket options: #{e.inspect}"
74           logger.error e.backtrace.join("\n")
75         end
76     end
78     def log_buffer_sizes(sock, pfx = '')
79       respond_to?(:logger) or return
80       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
81       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
82       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
83     end
85     # creates a new server, socket. address may be a HOST:PORT or
86     # an absolute path to a UNIX socket.  address can even be a Socket
87     # object in which case it is immediately returned
88     def bind_listen(address = '0.0.0.0:8080', opt = {})
89       return address unless String === address
91       sock = if address[0] == ?/
92         if File.exist?(address)
93           if File.socket?(address)
94             if self.respond_to?(:logger)
95               logger.info "unlinking existing socket=#{address}"
96             end
97             File.unlink(address)
98           else
99             raise ArgumentError,
100                   "socket=#{address} specified but it is not a socket!"
101           end
102         end
103         old_umask = File.umask(opt[:umask] || 0)
104         begin
105           UNIXServer.new(address)
106         ensure
107           File.umask(old_umask)
108         end
109       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
110         TCPServer.new($1, $2.to_i)
111       else
112         raise ArgumentError, "Don't know how to bind: #{address}"
113       end
114       set_server_sockopt(sock, opt)
115       sock
116     end
118     # Returns the configuration name of a socket as a string.  sock may
119     # be a string value, in which case it is returned as-is
120     # Warning: TCP sockets may not always return the name given to it.
121     def sock_name(sock)
122       case sock
123       when String then sock
124       when UNIXServer
125         Socket.unpack_sockaddr_un(sock.getsockname)
126       when TCPServer
127         Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
128       when Socket
129         begin
130           Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
131         rescue ArgumentError
132           Socket.unpack_sockaddr_un(sock.getsockname)
133         end
134       else
135         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
136       end
137     end
139     module_function :sock_name
141     # casts a given Socket to be a TCPServer or UNIXServer
142     def server_cast(sock)
143       begin
144         Socket.unpack_sockaddr_in(sock.getsockname)
145         TCPServer.for_fd(sock.fileno)
146       rescue ArgumentError
147         UNIXServer.for_fd(sock.fileno)
148       end
149     end
151   end # module SocketHelper
152 end # module Unicorn