Module: Zerg::Support::SocketFactory

Defined in:
lib/zerg_support/socket_factory.rb

Class Method Summary collapse

Class Method Details

.addr_infos(options) ⇒ Object

Retrieves possible IP addresses to connect to based on the given options.

The retrieval is done via setsockopt.



138
139
140
141
# File 'lib/zerg_support/socket_factory.rb', line 138

def self.addr_infos(options)
  Socket.getaddrinfo connect_host(options), connect_port(options),
                     Socket::AF_INET, socket_type(options)
end

.bind(socket, options) ⇒ Object

Binds a socket to an address based on the options.



121
122
123
124
# File 'lib/zerg_support/socket_factory.rb', line 121

def self.bind(socket, options)
  socket.bind bind_socket_address(options)    
  socket
end

.bind_host(options) ⇒ Object

The host from a host:port IP address, in a form suitable for bind().



41
42
43
# File 'lib/zerg_support/socket_factory.rb', line 41

def self.bind_host(options)
  host_from_address(options[:in_addr]) or options[:in_host] or '0.0.0.0'
end

.bind_port(options) ⇒ Object

The port from a host:port IP address, in a form suitable for bind().



46
47
48
# File 'lib/zerg_support/socket_factory.rb', line 46

def self.bind_port(options)
  port_from_address(options[:in_addr]) or options[:in_port] or 0
end

.bind_socket_address(options) ⇒ Object

An address suitable for bind() based on the options.



51
52
53
# File 'lib/zerg_support/socket_factory.rb', line 51

def self.bind_socket_address(options)
  Socket::pack_sockaddr_in bind_port(options), bind_host(options)
end

.connect_host(options) ⇒ Object

The host from a host:port IP address, in a form suitable for connect().



56
57
58
# File 'lib/zerg_support/socket_factory.rb', line 56

def self.connect_host(options)
  options[:out_host] or host_from_address(options[:out_addr]) or 'localhost'
end

.connect_port(options) ⇒ Object

The port from a host:port IP address, in a form suitable for connect().



61
62
63
# File 'lib/zerg_support/socket_factory.rb', line 61

def self.connect_port(options)
  port_from_address(options[:out_addr]) or options[:out_port]
end

.connect_with_addr_info(socket, addr_info) ⇒ Object

Connects a socket to an address obtained from getaddrinfo().

Returns the socket for success, or nil if the connection failed.



151
152
153
154
155
156
157
158
# File 'lib/zerg_support/socket_factory.rb', line 151

def self.connect_with_addr_info(socket, addr_info)
  begin      
    socket.connect Socket.pack_sockaddr_in(addr_info[1], addr_info[3])
    socket
  rescue
    nil
  end    
end

.host_from_address(address) ⇒ Object

The host from a host:port IP address.

Empty string if the IP address does not contain a host (e.g. :3000)



24
25
26
# File 'lib/zerg_support/socket_factory.rb', line 24

def self.host_from_address(address)
  address and split_address(address)[0]
end

.new_inbound_socket(options) ⇒ Object

New inbound socket based on the options.



127
128
129
130
131
132
133
# File 'lib/zerg_support/socket_factory.rb', line 127

def self.new_inbound_socket(options)
  socket = Socket.new Socket::AF_INET, socket_type(options), Socket::PF_UNSPEC
  set_options socket, options
  set_options_on_accept_sockets socket, options
  sugar_socket_listen socket
  bind socket, options    
end

.new_outbound_socket(options) ⇒ Object

New outbound socket based on the options.



161
162
163
164
165
166
167
168
169
170
# File 'lib/zerg_support/socket_factory.rb', line 161

def self.new_outbound_socket(options)
  addr_infos = self.addr_infos options
  addr_infos.each do |addr_info|
    socket = new_outbound_socket_with_addr_info addr_info
    set_options socket, options      
    return socket if connect_with_addr_info socket, addr_info
    socket.close rescue nil
  end
  nil
end

.new_outbound_socket_with_addr_info(addr_info) ⇒ Object

New outbound socket based on an address obtained from getaddrinfo().



144
145
146
# File 'lib/zerg_support/socket_factory.rb', line 144

def self.new_outbound_socket_with_addr_info(addr_info)
  Socket.new addr_info[4], addr_info[5], addr_info[6]
end

.outbound?(options) ⇒ Boolean

True for options requesting a connecting (as opposed to listening) socket.

Returns:

  • (Boolean)


36
37
38
# File 'lib/zerg_support/socket_factory.rb', line 36

def self.outbound?(options)
  [:out_port, :out_host, :out_addr].any? { |k| options[k] }
end

.port_from_address(address) ⇒ Object

The port from a host:port IP address.

nil if the IP address does not contain a port (e.g. localhost)



31
32
33
# File 'lib/zerg_support/socket_factory.rb', line 31

def self.port_from_address(address)
  address and (port_string = split_address(address)[1]) and port_string.to_i
end

.set_options(socket, options) ⇒ Object

Sets socket flags (via setsockopt) based on the options.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/zerg_support/socket_factory.rb', line 76

def self.set_options(socket, options)
  if options[:no_delay]
    if tcp?(options)
      socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
    end
    socket.sync = true
  end
  
  if options[:reuse_addr]
    socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
  end
  
  unless options[:reverse_lookup]
    if socket.respond_to? :do_not_reverse_lookup
      socket.do_not_reverse_lookup = true
    else
      # work around until the patch below actually gets committed:
      # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2346
      BasicSocket.do_not_reverse_lookup = true
    end
  end
end

.set_options_on_accept_sockets(socket, options) ⇒ Object

Hacks a socket’s accept method so that new sockets have the given flags set.

The flags are set in a similar manner to set_options.



102
103
104
105
106
107
108
109
110
# File 'lib/zerg_support/socket_factory.rb', line 102

def self.set_options_on_accept_sockets(socket, options)
  socket.instance_variable_set :@zerg_support_factory_options, options
  def socket.accept(*args)
    sock, addr = super
    Zerg::Support::SocketFactory.set_options sock,
                                             @zerg_support_factory_options
    return sock, addr
  end
end

.socket(options) ⇒ Object

Kitchen-sink socket creation method. The following options are supported:

tcp:: forces the use of TCP (overrides the udp option)
udp:: selects UDP (the default is TCP)
out_port:: the port to connect an outgoing socket to
out_host:: the host to connect an outgoing socket to
out_addr:: the host:port address to connect an outgoing socket to; the
           host and port override out_port and out_host, which can be used
           in conjunction with out_addr to provide default values
in_port:: the port to bind a listening socket to
in_host:: the host to bind a listening socket to
in_addr:: the host:port address to bind a listening socket to; the host
           and port override in_port and in_host, which can be used in
           conjunction with in_addr to provide default values
no_delay:: disables Nagles' algorithm
reuse_addr:: allows binding other sockets to this socket's address
reverse_lookup:: enables reverse lookups on connections; this is the Ruby
                 default, but ZergSupport changes it for performance


189
190
191
192
193
194
195
# File 'lib/zerg_support/socket_factory.rb', line 189

def self.socket(options)
  if outbound? options
    new_outbound_socket options
  else      
    new_inbound_socket options
  end
end

.socket_type(options) ⇒ Object

The socket() type based on the options.



71
72
73
# File 'lib/zerg_support/socket_factory.rb', line 71

def self.socket_type(options)
  tcp?(options) ? Socket::SOCK_STREAM : Socket::SOCK_DGRAM
end

.split_address(address) ⇒ Object

Splits a host:port IP address into its components. IPv6 addresses welcome.

The port will be nil if the IP address doesn’t contain it.



10
11
12
13
14
15
16
17
18
19
# File 'lib/zerg_support/socket_factory.rb', line 10

def self.split_address(address)
  port_match = /(^|[^:])\:([^:].*)$/.match(address)
  if port_match
    port = port_match[2]
    host = address[0, address.length - port.length - 1]
    [(host.empty? ? nil : host), port]
  else
    [address, nil]
  end
end

.sugar_socket_listen(socket) ⇒ Object

Sugar-coat the socket’s listen() call with a default value for its argument.



113
114
115
116
117
118
# File 'lib/zerg_support/socket_factory.rb', line 113

def self.sugar_socket_listen(socket)
  def socket.listen(*args)
    args = [1000] if args.empty?
    super(*args)
  end
end

.tcp?(options) ⇒ Boolean

True if the options indicate TCP should be used, false for UDP.

Returns:

  • (Boolean)


66
67
68
# File 'lib/zerg_support/socket_factory.rb', line 66

def self.tcp?(options)
  options[:tcp] or !options[:udp]
end