Class: FTW::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/ftw/server.rb

Overview

A web server.

Defined Under Namespace

Classes: InvalidAddress, ServerSetupFailure

Instance Method Summary collapse

Constructor Details

#initialize(addresses) ⇒ Server

Create a new server listening on the given addresses

This method will create, bind, and listen, so any errors during that process be raised as ServerSetupFailure

The parameter ‘addresses’ can be a single string or an array of strings. These strings MUST have the form “address:port”. If the ‘address’ part is missing, it is assumed to be 0.0.0.0

Raises:



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ftw/server.rb', line 27

def initialize(addresses)
  addresses = [addresses] if !addresses.is_a?(Array)
  dns = FTW::DNS.singleton

  @control_lock = Mutex.new
  @sockets = {}

  failures = []
  # address format is assumed to be 'host:port'
  # TODO(sissel): The split on ":" breaks ipv6 addresses, yo.
  addresses.each do |address|
    m = ADDRESS_RE.match(address)
    if !m
      raise InvalidAddress.new("Invalid address #{address.inspect}, spected string with format 'host:port'")
    end
    host, port = m[1..2] # first capture is host, second capture is port

    # Permit address being simply ':PORT'
    host = "0.0.0.0" if host.nil?

    # resolve each hostname, use the first one that successfully binds.
    local_failures = []
    dns.resolve(host).each do |ip|
      #family = ip.include?(":") ? Socket::AF_INET6 : Socket::AF_INET
      #socket = Socket.new(family, Socket::SOCK_STREAM, 0)
      #sockaddr = Socket.pack_sockaddr_in(port, ip)
      socket = TCPServer.new(ip, port)
      #begin
        #socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
        #socket.bind(sockaddr)
        # If we get here, bind was successful
      #rescue Errno::EADDRNOTAVAIL => e
        # TODO(sissel): Record this failure.
        #local_failures << "Could not bind to #{ip}:#{port}, address not available on this system."
        #next
      #rescue Errno::EACCES
        # TODO(sissel): Record this failure.
        #local_failures << "No permission to bind to #{ip}:#{port}: #{e.inspect}"
        #next
      #end

      begin
        socket.listen(100)
      rescue Errno::EADDRINUSE
        local_failures << "Address in use, #{ip}:#{port}, cannot listen."
        next
      end

      # Break when successfully listened
      #p :accept? => socket.respond_to?(:accept)
      @sockets["#{host}(#{ip}):#{port}"] = socket
      local_failures.clear
      break
    end
    failures += local_failures
  end

  # This allows us to interrupt the #each_connection's select() later
  # when anyone calls stop()
  @stopper = IO.pipe

  # Abort if there were failures
  raise ServerSetupFailure.new(failures) if failures.any?
end

Instance Method Details

#each_connection(&block) ⇒ Object

Yield FTW::Connection instances to the block as clients connect.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ftw/server.rb', line 105

def each_connection(&block)
  # TODO(sissel): Select on all sockets
  # TODO(sissel): Accept and yield to the block
  stopper = @stopper[0]
  while true
    @control_lock.synchronize do
      sockets = @sockets.values + [stopper]
      read, write, error = IO.select(sockets, nil, nil, nil)
      break if read.include?(stopper)
      read.each do |serversocket|
        socket, addrinfo = serversocket.accept
        connection = FTW::Connection.from_io(socket)
        yield connection
      end
    end
  end
end

#stopObject

Stop serving.



93
94
95
96
97
98
99
100
101
102
# File 'lib/ftw/server.rb', line 93

def stop
  @stopper[1].syswrite(".")
  @stopper[1].close()
  @control_lock.synchronize do
    @sockets.each do |name, socket|
      socket.close
    end
    @sockets.clear
  end
end