使用TCPServer的Ruby中的简单HTTP服务器

时间:2011-09-24 15:51:25

标签: ruby http sockets tcpserver

对于学校作业,我正在尝试使用Ruby和套接字库创建一个简单的HTTP服务器。

现在,我可以用简单的问候来回应任何连接:

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
}

这可以按预期工作。但是,当我让服务器告诉我谁与

连接时
puts "Client: #{client.addr[2]}"

并使用Chromium(浏览器)连接到localhost:2000/(只需一次),我得到:

Client: 127.0.0.1
Client: 127.0.0.1
Client: 127.0.0.1
Client: 127.0.0.1

我认为这是Chromium请求辅助文件,如favicon.ico,而不是我的脚本做一些奇怪的事情,所以我想调查传入的请求。我用

替换了resp = "Hello?"
resp = client.read()

然后重新启动服务器。我对Chromium的请求表示不满,而不是立即回来,它只是挂了。同时,我在服务器输出中得到了输出Client: 127.0.0.1。我点击了Chromium中的“停止”按钮,然后服务器崩溃了

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>'

显然,我做错了,因为预期的行为是将传入的请求作为响应发回。

我错过了什么?

1 个答案:

答案 0 :(得分:20)

我真的不知道chrome和四个连接,但我会尝试回答你关于如何正确阅读请求的问题。

首先,IO#read在这种情况下不起作用。根据{{​​3}},read没有任何参数读取,直到它遇到EOF,但没有发生这种情况。套接字是一个循环流,您将无法使用该方法读取整个消息,因为套接字没有“完整”消息。你可以使用带整数的读取,比如read(100)或者其他东西,但 会在某个时候阻塞

基本上,读取套接字与读取文件非常不同。套接字是异步更新的,完全独立于您尝试读取它的时间。如果您请求10个字节,那么在代码中此时,可能只有5个字节可用。使用阻止 IO,read(10)调用将挂起并等待5个字节可用,或直到连接关闭。这意味着,如果您尝试重复读取10个字节的数据包,在某些时候,它仍会挂起。另一种读取套接字的方法是使用非阻塞IO,但这在你的情况下并不是很重要,而且它本身就是一个很长的话题。

以下是使用阻塞IO访问数据的示例:

loop {
  client = server.accept

  while line = client.gets
    puts line.chomp
    break if line =~ /^\s*$/
  end

  # rest of loop ...
}

gets方法尝试从套接字读取,直到遇到换行符。这个在某个时刻发生HTTP请求,因此即使整个消息是逐个传输的,gets也应该从输出返回一行。 line.chomp电话会在最终换行符出现时切断。如果读取的行是空的,这意味着已经传输了HTTP标头,我们可以安全地中断循环(当然,您可以将其置于while条件下)。请求将被转储到已启动服务器的控制台。如果你真的想把它发回浏览器,想法是一样的,你只需要以不同的方式处理这些行:

loop {
  client = server.accept

  lines = []
  while line = client.gets and line !~ /^\s*$/
    lines << line.chomp
  end

  resp = lines.join("<br />")
  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          # send the time to the client
  client.puts resp
  client.close
}

对于损坏的管道,发生该错误是因为浏览器在read尝试访问数据时强行断开连接。