From 8daf254356241c135ad2c843de567910528a10a7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 7 Oct 2010 06:55:22 +0000 Subject: start using more compact parser API This should be easier for Rainbows! to use --- lib/unicorn.rb | 2 +- lib/unicorn/http_request.rb | 37 ++++++---------- lib/unicorn/http_server.rb | 2 +- lib/unicorn/tee_input.rb | 104 ++++++++++++++++++++++---------------------- test/unit/test_tee_input.rb | 9 ++-- 5 files changed, 72 insertions(+), 82 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index f9d5954..622dc6c 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -73,11 +73,11 @@ class Unicorn::ClientShutdown < EOFError; end require 'unicorn/const' require 'unicorn/socket_helper' +require 'unicorn/tee_input' require 'unicorn/http_request' require 'unicorn/configurator' require 'unicorn/tmpio' require 'unicorn/util' -require 'unicorn/tee_input' require 'unicorn/http_response' require 'unicorn/worker' require 'unicorn/http_server' diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index 13e9900..2dcd839 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -2,7 +2,9 @@ require 'unicorn_http' -class Unicorn::HttpRequest +# TODO: remove redundant names +Unicorn.const_set(:HttpRequest, Unicorn::HttpParser) +class Unicorn::HttpParser # default parameters we merge into the request env for Rack handlers DEFAULTS = { @@ -23,20 +25,9 @@ class Unicorn::HttpRequest # A frozen format for this is about 15% faster REMOTE_ADDR = 'REMOTE_ADDR'.freeze RACK_INPUT = 'rack.input'.freeze + TeeInput = Unicorn::TeeInput # :startdoc: - attr_reader :env, :parser, :buf - - def initialize - @parser = Unicorn::HttpParser.new - @buf = "" - @env = {} - end - - def response_headers? - @parser.headers? - end - # Does the majority of the IO processing. It has been written in # Ruby using about 8 different IO processing strategies. # @@ -51,8 +42,8 @@ class Unicorn::HttpRequest # This does minimal exception trapping and it is up to the caller # to handle any socket errors (e.g. user aborted upload). def read(socket) - @env.clear - @parser.reset + reset + e = env # From http://www.ietf.org/rfc/rfc3875: # "Script authors should be aware that the REMOTE_ADDR and @@ -61,18 +52,18 @@ class Unicorn::HttpRequest # identify the client for the immediate request to the server; # that client may be a proxy, gateway, or other intermediary # acting on behalf of the actual source client." - @env[REMOTE_ADDR] = socket.kgio_addr + e[REMOTE_ADDR] = socket.kgio_addr # short circuit the common case with small GET requests first - if @parser.headers(@env, socket.kgio_read!(16384, @buf)).nil? + socket.kgio_read!(16384, buf) + if parse.nil? # Parser is not done, queue up more data to read and continue parsing - # an Exception thrown from the PARSER will throw us out of the loop + # an Exception thrown from the parser will throw us out of the loop begin - @buf << socket.kgio_read!(16384) - end while @parser.headers(@env, @buf).nil? + buf << socket.kgio_read!(16384) + end while parse.nil? end - @env[RACK_INPUT] = 0 == @parser.content_length ? - NULL_IO : Unicorn::TeeInput.new(socket, self) - @env.merge!(DEFAULTS) + e[RACK_INPUT] = 0 == content_length ? NULL_IO : TeeInput.new(socket, self) + e.merge!(DEFAULTS) end end diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index 5d6c023..513269f 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -521,7 +521,7 @@ class Unicorn::HttpServer r = @app.call(env) end # r may be frozen or const, so don't modify it - @request.response_headers? or r = [ r[0], nil, r[2] ] + @request.headers? or r = [ r[0], nil, r[2] ] http_response_write(client, r) rescue => e handle_error(client, e) diff --git a/lib/unicorn/tee_input.rb b/lib/unicorn/tee_input.rb index 32ee4f2..a3e01d2 100644 --- a/lib/unicorn/tee_input.rb +++ b/lib/unicorn/tee_input.rb @@ -11,8 +11,8 @@ # # When processing uploads, Unicorn exposes a TeeInput object under # "rack.input" of the Rack environment. -class Unicorn::TeeInput < Struct.new(:socket, :req, :parser, - :buf, :len, :tmp, :buf2) +class Unicorn::TeeInput + attr_accessor :tmp, :socket, :parser, :env, :buf, :len, :buf2 # The maximum size (in +bytes+) to buffer in memory before # resorting to a temporary file. Default is 112 kilobytes. @@ -26,18 +26,18 @@ class Unicorn::TeeInput < Struct.new(:socket, :req, :parser, # Initializes a new TeeInput object. You normally do not have to call # this unless you are writing an HTTP server. def initialize(socket, request) - self.socket = socket - self.req = request.env - self.parser = request.parser - self.buf = request.buf - self.len = parser.content_length - self.tmp = len && len < @@client_body_buffer_size ? - StringIO.new("") : Unicorn::TmpIO.new - self.buf2 = "" - if buf.size > 0 - parser.filter_body(buf2, buf) and finalize_input - tmp.write(buf2) - tmp.rewind + @socket = socket + @parser = request + @buf = request.buf + @env = request.env + @len = request.content_length + @tmp = @len && @len < @@client_body_buffer_size ? + StringIO.new("") : Unicorn::TmpIO.new + @buf2 = "" + if @buf.size > 0 + @parser.filter_body(@buf2, @buf) and finalize_input + @tmp.write(@buf2) + @tmp.rewind end end @@ -58,16 +58,16 @@ class Unicorn::TeeInput < Struct.new(:socket, :req, :parser, # earlier. Most applications should only need to call +read+ with a # specified +length+ in a loop until it returns +nil+. def size - len and return len + @len and return @len if socket - pos = tmp.pos - while tee(@@io_chunk_size, buf2) + pos = @tmp.pos + while tee(@@io_chunk_size, @buf2) end - tmp.seek(pos) + @tmp.seek(pos) end - self.len = tmp.size + @len = @tmp.size end # :call-seq: @@ -90,22 +90,22 @@ class Unicorn::TeeInput < Struct.new(:socket, :req, :parser, # any data and only block when nothing is available (providing # IO#readpartial semantics). def read(*args) - socket or return tmp.read(*args) + @socket or return @tmp.read(*args) length = args.shift if nil == length - rv = tmp.read || "" - while tee(@@io_chunk_size, buf2) - rv << buf2 + rv = @tmp.read || "" + while tee(@@io_chunk_size, @buf2) + rv << @buf2 end rv else rv = args.shift || "" - diff = tmp.size - tmp.pos + diff = @tmp.size - @tmp.pos if 0 == diff ensure_length(tee(length, rv), length) else - ensure_length(tmp.read(diff > length ? length : diff, rv), length) + ensure_length(@tmp.read(diff > length ? length : diff, rv), length) end end end @@ -120,27 +120,27 @@ class Unicorn::TeeInput < Struct.new(:socket, :req, :parser, # This takes zero arguments for strict Rack::Lint compatibility, # unlike IO#gets. def gets - socket or return tmp.gets + @socket or return @tmp.gets sep = $/ or return read - orig_size = tmp.size - if tmp.pos == orig_size - tee(@@io_chunk_size, buf2) or return nil - tmp.seek(orig_size) + orig_size = @tmp.size + if @tmp.pos == orig_size + tee(@@io_chunk_size, @buf2) or return nil + @tmp.seek(orig_size) end sep_size = Rack::Utils.bytesize(sep) - line = tmp.gets # cannot be nil here since size > pos + line = @tmp.gets # cannot be nil here since size > pos sep == line[-sep_size, sep_size] and return line - # unlikely, if we got here, then tmp is at EOF + # unlikely, if we got here, then @tmp is at EOF begin - orig_size = tmp.pos - tee(@@io_chunk_size, buf2) or break - tmp.seek(orig_size) - line << tmp.gets + orig_size = @tmp.pos + tee(@@io_chunk_size, @buf2) or break + @tmp.seek(orig_size) + line << @tmp.gets sep == line[-sep_size, sep_size] and return line - # tmp is at EOF again here, retry the loop + # @tmp is at EOF again here, retry the loop end while true line @@ -166,7 +166,7 @@ class Unicorn::TeeInput < Struct.new(:socket, :req, :parser, # the offset (zero) of the +ios+ pointer. Subsequent reads will # start from the beginning of the previously-buffered input. def rewind - tmp.rewind # Rack does not specify what the return value is here + @tmp.rewind # Rack does not specify what the return value is here end private @@ -175,11 +175,11 @@ private # backing store as well as returning it. +dst+ must be specified. # returns nil if reading from the input returns nil def tee(length, dst) - unless parser.body_eof? - r = socket.kgio_read(length, buf) or eof! - unless parser.filter_body(dst, r) - tmp.write(dst) - tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug + unless @parser.body_eof? + r = @socket.kgio_read(length, @buf) or eof! + unless @parser.filter_body(dst, @buf) + @tmp.write(dst) + @tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug return dst end end @@ -187,11 +187,11 @@ private end def finalize_input - while parser.trailers(req, buf).nil? - r = socket.kgio_read(@@io_chunk_size) or eof! - buf << r + while @parser.trailers(@env, @buf).nil? + r = @socket.kgio_read(@@io_chunk_size) or eof! + @buf << r end - self.socket = nil + @socket = nil end # tee()s into +dst+ until it is of +length+ bytes (or until @@ -204,10 +204,10 @@ private # len is nil for chunked bodies, so we can't ensure length for those # since they could be streaming bidirectionally and we don't want to # block the caller in that case. - return dst if dst.nil? || len.nil? + return dst if dst.nil? || @len.nil? - while dst.size < length && tee(length - dst.size, buf2) - dst << buf2 + while dst.size < length && tee(length - dst.size, @buf2) + dst << @buf2 end dst @@ -218,7 +218,7 @@ private # we do support clients that shutdown(SHUT_WR) after the # _entire_ request has been sent, and those will not have # raised EOFError on us. - socket.close if socket - raise Unicorn::ClientShutdown, "bytes_read=#{tmp.size}", [] + @socket.close if @socket + raise Unicorn::ClientShutdown, "bytes_read=#{@tmp.size}", [] end end diff --git a/test/unit/test_tee_input.rb b/test/unit/test_tee_input.rb index 263aa8a..a10ca34 100644 --- a/test/unit/test_tee_input.rb +++ b/test/unit/test_tee_input.rb @@ -5,7 +5,6 @@ require 'digest/sha1' require 'unicorn' class TestTeeInput < Test::Unit::TestCase - MockRequest = Struct.new(:env, :parser, :buf) def setup @rs = $/ @@ -164,7 +163,7 @@ class TestTeeInput < Test::Unit::TestCase @wr.write("0\r\n\r\n") } @wr.close - ti = Unicorn::TeeInput.new(@rd, MockRequest.new(@env, @parser, @buf)) + ti = Unicorn::TeeInput.new(@rd, @parser) assert_nil @parser.content_length assert_nil ti.len assert ! @parser.body_eof? @@ -202,7 +201,7 @@ class TestTeeInput < Test::Unit::TestCase end @wr.write("0\r\n\r\n") } - ti = Unicorn::TeeInput.new(@rd, MockRequest.new(@env, @parser, @buf)) + ti = Unicorn::TeeInput.new(@rd, @parser) assert_nil @parser.content_length assert_nil ti.len assert ! @parser.body_eof? @@ -231,7 +230,7 @@ class TestTeeInput < Test::Unit::TestCase @wr.write("Hello: World\r\n\r\n") } @wr.close - ti = Unicorn::TeeInput.new(@rd, MockRequest.new(@env, @parser, @buf)) + ti = Unicorn::TeeInput.new(@rd, @parser) assert_nil @parser.content_length assert_nil ti.len assert ! @parser.body_eof? @@ -253,7 +252,7 @@ private "\r\n#{body}" assert_equal @env, @parser.headers(@env, @buf) assert_equal body, @buf - MockRequest.new(@env, @parser, @buf) + @parser end end -- cgit v1.2.3-24-ge0c7