Class: FTW::Server
- Inherits:
-
Object
- Object
- FTW::Server
- Defined in:
- lib/ftw/server.rb
Overview
A web server.
Defined Under Namespace
Classes: InvalidAddress, ServerSetupFailure
Instance Method Summary collapse
-
#each_connection(&block) ⇒ Object
Yield FTW::Connection instances to the block as clients connect.
-
#initialize(addresses) ⇒ Server
constructor
Create a new server listening on the given addresses.
-
#stop ⇒ Object
Stop serving.
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
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 |
#stop ⇒ Object
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 |