Index: [Article Count Order] [Thread]

Date:  Thu, 21 Mar 2002 22:18:10 +0100 (MET)
From:  Lars Christensen <larsch@cs.auc.dk>
Subject:  [webricken:69] Re: Help with a 408 error
To:  webricken@notwork.org
Message-Id:  <Pine.GSO.4.44.0203212132150.24869-100000@peta.cs.auc.dk>
In-Reply-To:  <Pine.GSO.4.44.0203202348590.12174-100000@peta.cs.auc.dk>
X-Mail-Count: 00069

On Thu, 21 Mar 2002, Lars Christensen wrote:

> On 15 Feb 2002, Dave Thomas wrote:
>
> > GOTO Kentaro <gotoken@notwork.org> writes:
> >
> > > That doesn't be reproduced on my Window ME box with
> > > sample/httpd/htdocs/test.rhtml.  Could you show the rhtml or any example?
> >
> > More information... if I disable keep_alive? in HttpResponse, it all
> > works perfectly. I'm not sure where to look next.
>
> I have the same problem here under Linux with Mozilla. I get this:

<snip>

After a bit of digging I think this is caused by clients not disconnecting
immediatly when it is done with the connection. This is quite normal I
think, but it fails with webrick's model where you need a free threads to
handle new connections. Even with 5 threads you risk have all threads
lingering on open connection waiting for more requests.

Below is two patches for webrick-1.1.5. The first fixes a smaller problem
with HTTP/1.0 client that expect the server to disconnect if no
"Connection:" header is sent (e.g. wget).

The next two seem to fix the above problem. It changes the server model a
little. When a request has been processed, the thread returns the sockets
to the main loop.. The main loop will IO#select on the sockets and return
them to a thread when there is incoming data.

WEBrick will still be vulnerable to DOS attacks by opening connections for
all thread and sending the request, but not completing the header.


diff -ru /usr/lib/ruby/1.6/webrick/httpresponse.rb webrick/httpresponse.rb
--- /usr/lib/ruby/1.6/webrick/httpresponse.rb	Fri Feb 15 02:05:51 2002
+++ webrick/httpresponse.rb	Thu Mar 21 21:31:06 2002
@@ -55,7 +55,11 @@
       @header['server'] ||= @config[:ServerSoftware]
       @header['content-length'] = @body ? @body.size : 0.to_s
       @header['date'] ||= HTTPDate.time2s(Time.now)
-      @header['connection'] = "close" unless @keep_conn
+      if @keep_conn
+	@header['connection'] = "Keep-Alive"
+      else
+	@header['connection'] = "close"
+      end

       if /^(\d+)/ =~ @header['status']
         self.status = $1.to_i
diff -ru /usr/lib/ruby/1.6/webrick/httpserver.rb webrick/httpserver.rb
--- /usr/lib/ruby/1.6/webrick/httpserver.rb	Fri Feb 15 02:05:51 2002
+++ webrick/httpserver.rb	Thu Mar 21 21:36:35 2002
@@ -30,35 +30,36 @@

     def run(sock)
       peeraddr = sock.peeraddr[3]
-      while true
-        res = HTTPResponse.new(@config)
-        req = HTTPRequest.new(@config)
-        request_line = nil
-        begin
-          req.parse(sock)
-          service(req, res)
-        rescue HTTPStatus::EOFError
-          break
-        rescue HTTPStatus::RequestTimeout => ex
-          access_log(sock, req, res, ex)
-          break
-        rescue HTTPStatus::Error => ex
-          res.set_error(ex)
-        rescue HTTPStatus::Status => ex
-          res.status = ex.code
-        rescue StandardError, NameError => ex # for Ruby 1.6
-          @logger.error(ex)
-          res.set_error(ex, true)
-        end
-        if req.request_method == "HEAD"
-          res.send_header(sock)
-        else
-          res.send_response(sock)
-        end
-        access_log(sock, req, res)
-        break unless req.keep_alive?
-        break unless res.keep_alive?
+
+      res = HTTPResponse.new(@config)
+      req = HTTPRequest.new(@config)
+      request_line = nil
+      begin
+	req.parse(sock)
+	service(req, res)
+      rescue HTTPStatus::EOFError
+	break
+      rescue HTTPStatus::RequestTimeout => ex
+	access_log(sock, req, res, ex)
+	break
+      rescue HTTPStatus::Error => ex
+	res.set_error(ex)
+      rescue HTTPStatus::Status => ex
+	res.status = ex.code
+      rescue StandardError, NameError => ex # for Ruby 1.6
+	@logger.error(ex)
+	res.set_error(ex, true)
       end
+      if req.request_method == "HEAD"
+	res.send_header(sock)
+      else
+	res.send_response(sock)
+      end
+      access_log(sock, req, res)
+
+      return false unless req.keep_alive?
+      return false unless res.keep_alive?
+      return true
     end

     def service(req, res)
diff -ru /usr/lib/ruby/1.6/webrick/server.rb webrick/server.rb
--- /usr/lib/ruby/1.6/webrick/server.rb	Fri Feb 15 02:05:51 2002
+++ webrick/server.rb	Thu Mar 21 22:07:31 2002
@@ -77,6 +77,8 @@
       server_type.start{
         thgroup = Array.new
         queue   = SizedQueue.new(start_threads)
+	rqueue  = Queue.new
+	sockets = Array.new

         @logger.info "#{self.type}#start: pid=#{$$} port=#{@config[:Port]}"
         start_hook()
@@ -92,12 +94,16 @@
                   addr = sock.peeraddr
                   @logger.debug "accept: #{addr[3]}:#{addr[1]}"
                   accept_hook(sock)
-                  block_given? ? yield(sock) : run(sock)
+                  continue = (block_given? ? yield(sock) : run(sock))
                 rescue Exception => ex
                   @logger.error ex
                 ensure
                   @logger.debug "close: #{addr[3]}:#{addr[1]}"
-                  sock.close
+		  if continue
+		    rqueue.push sock
+		  else
+		    sock.close
+		  end
                 end
               else
                 @logger.info "thread(#{j}) exit."
@@ -111,13 +117,22 @@
         @status = :Running
         while @status == :Running
           begin
-            if socks = IO.select(@listeners, nil, nil, 2.0)
-              socks[0].each{|s|
-                ns = s.accept
-                ns.sync = true
-                queue.push(ns)
+            if socks = IO.select(@listeners+sockets, nil, nil, 0.1)
+              socks[0].each { |s|
+		if @listeners.include? s
+		  ns = s.accept
+		  ns.sync = true
+		  sockets.push ns
+		else
+		  sockets.delete s
+		  queue.push(s)
+		end
               }
             end
+	    while !rqueue.empty?
+	      p :pushing
+	      sockets.push rqueue.pop
+	    end
           rescue Errno::ECONNRESET, Errno::ECONNABORTED => ex
             msg = "#{ex.type}: #{ex.message}\n\t#{ex.backtrace[0]}"
             @logger.error msg


-- 
Lars Christensen, larsch@cs.auc.dk