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