diff options
Diffstat (limited to 'lib/unicorn/http_request.rb')
-rw-r--r-- | lib/unicorn/http_request.rb | 92 |
1 files changed, 19 insertions, 73 deletions
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index d7078a3..b8df403 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -1,19 +1,11 @@ -require 'tempfile' require 'stringio' # compiled extension require 'unicorn/http11' module Unicorn - # - # The HttpRequest.initialize method will convert any request that is larger than - # Const::MAX_BODY into a Tempfile and use that as the body. Otherwise it uses - # a StringIO object. To be safe, you should assume it works like a file. - # class HttpRequest - attr_accessor :logger - # default parameters we merge into the request env for Rack handlers DEFAULTS = { "rack.errors" => $stderr, @@ -27,21 +19,19 @@ module Unicorn "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze } - # Optimize for the common case where there's no request body - # (GET/HEAD) requests. - NULL_IO = StringIO.new + NULL_IO = StringIO.new(Z) LOCALHOST = '127.0.0.1'.freeze + def initialize + end + # Being explicitly single-threaded, we have certain advantages in # not having to worry about variables being clobbered :) BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow + BUFFER.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding) PARSER = HttpParser.new PARAMS = Hash.new - def initialize(logger = Configurator::DEFAULT_LOGGER) - @logger = logger - end - # Does the majority of the IO processing. It has been written in # Ruby using about 8 different IO processing strategies. # @@ -56,11 +46,6 @@ module Unicorn # 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) - # reset the parser - unless NULL_IO == (input = PARAMS[Const::RACK_INPUT]) # unlikely - input.close rescue nil - input.close! rescue nil - end PARAMS.clear PARSER.reset @@ -86,69 +71,30 @@ module Unicorn data << socket.readpartial(Const::CHUNK_SIZE, BUFFER) PARSER.execute(PARAMS, data) and return handle_body(socket) end while true - rescue HttpParserError => e - @logger.error "HTTP parse error, malformed request " \ - "(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] || - PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}" - @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \ - "PARAMS: #{PARAMS.inspect}\n---\n" - raise e end private # Handles dealing with the rest of the request - # returns a Rack environment if successful, raises an exception if not + # returns a Rack environment if successful def handle_body(socket) - http_body = PARAMS.delete(:http_body) - content_length = PARAMS[Const::CONTENT_LENGTH].to_i - - if content_length == 0 # short circuit the common case - PARAMS[Const::RACK_INPUT] = NULL_IO.closed? ? NULL_IO.reopen : NULL_IO - return PARAMS.update(DEFAULTS) + PARAMS[Const::RACK_INPUT] = if (body = PARAMS.delete(:http_body)) + length = PARAMS[Const::CONTENT_LENGTH].to_i + + if te = PARAMS[Const::HTTP_TRANSFER_ENCODING] + if /\Achunked\z/i =~ te + socket = ChunkedReader.new(PARAMS, socket, body) + length = body = nil + end + end + + TeeInput.new(socket, length, body) + else + NULL_IO.closed? ? NULL_IO.reopen(Z) : NULL_IO end - # must read more data to complete body - remain = content_length - http_body.length - - body = PARAMS[Const::RACK_INPUT] = (remain < Const::MAX_BODY) ? - StringIO.new : Tempfile.new('unicorn') - - body.binmode - body.write(http_body) - - # Some clients (like FF1.0) report 0 for body and then send a body. - # This will probably truncate them but at least the request goes through - # usually. - read_body(socket, remain, body) if remain > 0 - body.rewind - - # in case read_body overread because the client tried to pipeline - # another request, we'll truncate it. Again, we don't do pipelining - # or keepalive - body.truncate(content_length) PARAMS.update(DEFAULTS) end - # Does the heavy lifting of properly reading the larger body - # requests in small chunks. It expects PARAMS['rack.input'] to be - # an IO object, socket to be valid, It also expects any initial part - # of the body that has been read to be in the PARAMS['rack.input'] - # already. It will return true if successful and false if not. - def read_body(socket, remain, body) - begin - # write always writes the requested amount on a POSIX filesystem - remain -= body.write(socket.readpartial(Const::CHUNK_SIZE, BUFFER)) - end while remain > 0 - rescue Object => e - @logger.error "Error reading HTTP body: #{e.inspect}" - - # Any errors means we should delete the file, including if the file - # is dumped. Truncate it ASAP to help avoid page flushes to disk. - body.truncate(0) rescue nil - reset - raise e - end - end end |