tags:

views:

256

answers:

3

I want to create an extremely simple web server for development purposes in Ruby (no, don’t want to use ready solutions).

Here is the code:

#!/usr/bin/ruby

require 'socket'

server = TCPServer.new('127.0.0.1', 8080)

while connection = server.accept
  headers = []
  length  = 0

  while line = connection.gets
    headers << line

    if line =~ /^Content-Length:\s+(\d+)/i
      length = $1.to_i
    end

    break if line == "\r\n"
  end

  body = connection.readpartial(length)

  IO.popen(ARGV[0], 'r+') do |script|
    script.print(headers.join + body)
    script.close_write
    connection.print script.read
  end

  connection.close
end

The idea is to run this script from the command line, providing another script, which will get the request on its standard input, and gives back the complete response on its standard output.

So far so good, but this turns out to be really fragile, as it breaks on the second request with the error:

/usr/bin/serve:24:in `write': Broken pipe (Errno::EPIPE)
    from /usr/bin/serve:24:in `print'
    from /usr/bin/serve:24
    from /usr/bin/serve:23:in `popen'
    from /usr/bin/serve:23

Any idea how to improve the above code to be sufficient for easy use?

Versions: Ubuntu 9.10 (2.6.31-20-generic), Ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux]

+1  A: 

With the Ruby Webrick Lib you have an easy Library to build a webserver.

http://www.ruby-doc.org/stdlib/libdoc/webrick/rdoc/

u2ix
Thanks, but as I have pointed out, I’d do it myself.
Joó Ádám
+3  A: 

The problem appears to be in the child script, since the parent script in your question runs on my box (Debian Squeeze, Ruby 1.8.7 patchlevel 249):

I created the dummy child script bar.rb:

#!/usr/bin/ruby1.8

s = $stdin.read
$stderr.puts s
print s

I then ran your script, passing it the path to the dummy script:

$ /tmp/foo.rb /tmp/bar.rb

The I hit it with wget:

$ wget localhost:8080/index

And saw the dummy script's output:

GET /index HTTP/1.0^M
User-Agent: Wget/1.12 (linux-gnu)^M
Accept: */*^M
Host: localhost:8080^M
Connection: Keep-Alive^M
^M

I also saw that wget received what it sent:

$ cat index
GET /index HTTP/1.0
User-Agent: Wget/1.12 (linux-gnu)
Accept: */*
Host: localhost:8080
Connection: Keep-Alive

It worked the same no matter how many times I hit it with wget.

Wayne Conrad
I tried your child script and it throwed an error for the first run. Maybe some OS-level difference plays here.
Joó Ádám
OK, I found where the problem lies. It is in the child script: I merely printed out a basic HTTP response, while you have read stdin first. Now the question is why it is neccessery to do so.
Joó Ádám
+1  A: 

The Ruby Web Servers Booklet describes most of web server implementation strategies.

clyfe