5

So, I'm trying to simulate some basic HTTP persistent connections using sockets and Ruby - for a college class.

The point is to build a server - able to handle multiple clients - that receives a file path and gives back the file content - just like an HTTP GET.

The current server implementation loops listening for clients, fires a new thread when there's an incoming connection and reads the file paths from this socket. It's very dumb, but it works fine when working with non-presistent connections - one request per connection.

But they should be persistent.

Which means the client shouldn't worry about closing the connection. In the non-persistent version the servers echoes the response and close the connection - goodbye client, farewell. But being persistent means the server thread should loop and wait for more incoming requests until... well until there's no more requests. How does the server knows that? It doesn't! Some sort of timeout is needed. I tried to do that with Ruby's Timeout, but it didn't work.

Googling for some solutions - besides being thoroughly advised to avoid using Timeout module - I've seen a lot of posts about the IO.select method, that should handle this socket waiting issue way better than using threads and stuff (which really sounds cool, considering how Ruby threads (don't) work). I'm trying to understand here how IO.select works, but still wasn't able to make it work in the current scenario.

So I aske basically two things:

  • how can I efficiently work this timeout issue on the server-side, either using some thread based solution, low-level socket options or some IO.select magic?

  • how can the client side know that the server has closed its side of the connection?

Here's the current code for the server:

require 'date'
module Sockettp
  class Server
    def initialize(dir, port = Sockettp::DEFAULT_PORT)
      @dir = dir
      @port = port
    end

    def start
      puts "Starting Sockettp server..."
      puts "Serving #{@dir.yellow} on port #{@port.to_s.green}"

      Socket.tcp_server_loop(@port) do |socket, client_addrinfo|
        handle socket, client_addrinfo
      end
    end

    private
    def handle(socket, addrinfo)
      Thread.new(socket) do |client|
        log "New client connected"
        begin
          loop do
            if client.eof?
              puts "#{'-' * 100} end connection"
              break
            end

            input = client.gets.chomp

            body = content_for(input)

            response = {}

            if body
              response.merge!({
                status: 200,
                body: body
              })
            else
              response.merge!({
                status: 404,
                body: Sockettp::STATUSES[404]
              })
            end

            log "#{addrinfo.ip_address} #{input} -- #{response[:status]} #{Sockettp::STATUSES[response[:status]]}".send(response[:status] == 200 ? :green : :red)

            client.puts(response.to_json)
          end
        ensure
          socket.close
        end
      end
    end

    def content_for(path)
      path = File.join(@dir, path)

      return File.read(path) if File.file?(path)
      return Dir["#{path}/*"] if File.directory?(path)
    end

    def log(msg)
      puts "#{Thread.current} -- #{DateTime.now.to_s} -- #{msg}"
    end
  end
end

Update

I was able to simulate the timeout behaviour using the IO.select method, but the implementation doesn't feel good when combining with a couple of threads for accepting new connections and another couple for handling requests. The concurrency makes the situation mad and unstable, and I'm probably not sticking with it unless I can figure out a better way of using this solution.

Update 2

Seems like Timeout is still the best way to handle this. I'm sticking with it till find a better option. I still don't know how to deal with zombie client connections.

Solution

I endend up using IO.select (got inspired when looking at the webrick code). You cha check the final version here (lib/http/server/client_handler.rb)

Fuad Saud
  • 3,086
  • 1
  • 15
  • 16
  • Can you not just close the connection at the client when it has received all its files and it has no more requests to make? – Martin James Mar 29 '13 at 08:49
  • that could help you http://stackoverflow.com/questions/6158228/how-do-i-create-persistant-tcpsockets – toch Mar 29 '13 at 09:05
  • @MartinJames That would make the process a lot easier, but the HTTP specification states that the client shouldn't worry about the connection; this is server's responsibility. – Fuad Saud Mar 29 '13 at 10:16
  • @toch Thanks, but I've been there already, but it doesn't solve. In fact, as of now, I'm starting to get fond of IO.select - I wrote a version of the server using it (based on this one http://joachimwuttke.de/techblog/multiclient-tcpserver-ruby.html) and the timeout seems to be working fine. The only problem with it is the how the code is written: it feels a bit weird treating the array it returns :\ – Fuad Saud Mar 29 '13 at 10:32
  • You might like to have a look a this answer http://stackoverflow.com/a/12111120/694576 – alk Mar 29 '13 at 10:34
  • the w3c rfc on http 1.1 indicates that the state of a connection being persistent or not should be signaled by `Connection: close`. see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10 – didierc Mar 29 '13 at 10:57
  • however, would the client end up in a state of limbo, the server has to implement a timeout mechanism. – didierc Mar 29 '13 at 10:59

1 Answers1

0

You should implement something like heartbeat packets.Client side should send special packets to after few secs/mins to ensure that server doesn't time out the connection on the client end.You just avoid doing anything in this call.