For a school assignment, I am trying to create a simple HTTP server using Ruby and the sockets library.
Right now, I can get it to respond to any connection with a simple hello:
require 'socket'
server = TCPServer.open 2000
puts "Listening on port 2000"
loop {
client = server.accept()
resp = "Hello?"
headers = ["HTTP/1.1 200 OK",
"Date: Tue, 14 Dec 2010 10:48:45 GMT",
"Server: Ruby",
"Content-Type: text/html; charset=iso-8859-1",
"Content-Length: #{resp.length}\r\n\r\n"].join("\r\n")
client.puts headers
client.puts resp
client.close
}
This works as expected. However, when I have the server tell me who just connected with
puts "Client: #{client.addr[2]}"
and use Chromium (browser) to connect to localhost:2000/ (just once), I get:
Client: 127.0.0.1
Client: 127.0.0.1
Client: 127.0.0.1
Client: 127.0.0.1
I assume this is Chromium requesting auxiliary files, like favicon.ico, and not my script doing something weird, so I wanted to investigate the incoming request. I replaced the resp = "Hello?" line with
resp = client.read()
And restarted the server. I resent the request in Chromium, and instead of it coming back right away, it just hung. Meanwhile, I got the output Client: 127.0.0.1 in my server output. I hit the “stop” button in Chromium, and then the server crashed with
server.rb:16:in `write': Broken pipe (Errno::EPIPE)
from server.rb:16:in `puts'
from server.rb:16:in `block in <main>'
from server.rb:6:in `loop'
from server.rb:6:in `<main>'
Obviously, I’m doing something wrong, as the expected behavior was sending the incoming request back as the response.
What am I missing?
I don’t really know about chrome and the four connections, but I’ll try to answer your questions on how to read the request properly.
First of all,
IO#readwon’t work in this case. According to the documentation,readwithout any parameters reads until it encounters EOF, but nothing like that happens. A socket is an endless stream, you won’t be able to use that method in order to read in the entire message, since there is no “entire” message for the socket. You could use read with an integer, likeread(100)or something, but that will block at some point anyway.Basically, reading a socket is very different from reading a file. A socket is updated asynchronously, completely independent of the time you try to read it. If you request 10 bytes, it’s possible that, at this point in the code, only 5 bytes are available. With blocking IO, the
read(10)call will then hang and wait until 5 more bytes are available, or until the connection is closed. This means that, if you try repeatedly reading packets of 10 bytes, at some point, it will still hang. Another way to read a socket is using non-blocking IO, but that’s not very important in your case, and it’s a long topic by itself.So here’s an example of how you might access the data by using blocking IO:
The
getsmethod tries to read from the socket until it encounters a newline. This will happen at some point for an HTTP request, so even if the entire message is transferred piece by piece,getsshould return a single line from the output. Theline.chompcall will cut off the final newlines if they’re present. If the line read is empty, that means the HTTP headers have been transferred and we can safely break the loop (you can put that in thewhilecondition, of course). The request will be dumped to the console that the server has been started on. If you really want to send it back to the browser, the idea’s the same, you just need to handle the lines differently:As for the broken pipe, that error occurs because the browser forcefully breaks the connection off while
readis trying to access data.