diff options
Diffstat (limited to 'lib/unicorn/http_request.rb')
-rw-r--r-- | lib/unicorn/http_request.rb | 78 |
1 files changed, 35 insertions, 43 deletions
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index 424a54f..368305f 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -12,19 +12,22 @@ module Unicorn # class HttpRequest - # default parameters we merge into the request env for Rack handlers - DEF_PARAMS = { - "rack.errors" => $stderr, - "rack.multiprocess" => true, - "rack.multithread" => false, - "rack.run_once" => false, - "rack.version" => [1, 0].freeze, - "SCRIPT_NAME" => "".freeze, - - # this is not in the Rack spec, but some apps may rely on it - "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze - }.freeze - + # default parameters we merge into the request env for Rack handlers + DEFAULTS = { + "rack.errors" => $stderr, + "rack.multiprocess" => true, + "rack.multithread" => false, + "rack.run_once" => false, + "rack.version" => [1, 0].freeze, + "SCRIPT_NAME" => "".freeze, + + # this is not in the Rack spec, but some apps may rely on it + "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 LOCALHOST = '127.0.0.1'.freeze # Being explicitly single-threaded, we have certain advantages in @@ -35,14 +38,6 @@ module Unicorn def initialize(logger) @logger = logger - reset - end - - def reset - PARAMS[Const::RACK_INPUT].close rescue nil - PARAMS[Const::RACK_INPUT].close! rescue nil - PARSER.reset - PARAMS.clear end # Does the majority of the IO processing. It has been written in @@ -59,6 +54,14 @@ 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 + # From http://www.ietf.org/rfc/rfc3875: # "Script authors should be aware that the REMOTE_ADDR and # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9) @@ -70,15 +73,15 @@ module Unicorn TCPSocket === socket ? socket.peeraddr.last : LOCALHOST # short circuit the common case with small GET requests first - PARSER.execute(PARAMS, read_socket(socket)) and + PARSER.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and return handle_body(socket) - data = BUFFER.dup # read_socket will clobber BUFFER + data = BUFFER.dup # socket.readpartial will clobber BUFFER # 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 begin - data << read_socket(socket) + data << socket.readpartial(Const::CHUNK_SIZE, BUFFER) PARSER.execute(PARAMS, data) and return handle_body(socket) end while true rescue HttpParserError => e @@ -99,8 +102,8 @@ module Unicorn content_length = PARAMS[Const::CONTENT_LENGTH].to_i if content_length == 0 # short circuit the common case - PARAMS[Const::RACK_INPUT] = StringIO.new - return PARAMS.update(DEF_PARAMS) + PARAMS[Const::RACK_INPUT] = NULL_IO.closed? ? NULL_IO.reopen : NULL_IO + return PARAMS.update(DEFAULTS) end # must read more data to complete body @@ -110,21 +113,19 @@ module Unicorn StringIO.new : Tempfile.new('unicorn') body.binmode - body.sync = true - body.syswrite(http_body) + 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 - body.sysseek(0) if body.respond_to?(:sysseek) # 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(DEF_PARAMS) + PARAMS.update(DEFAULTS) end # Does the heavy lifting of properly reading the larger body @@ -133,10 +134,10 @@ module Unicorn # 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) - while remain > 0 - # writes always write the requested amount on a POSIX filesystem - remain -= body.syswrite(read_socket(socket)) - end + 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}" @@ -147,14 +148,5 @@ module Unicorn raise e end - # read(2) on "slow" devices like sockets can be interrupted by signals - def read_socket(socket) - begin - socket.sysread(Const::CHUNK_SIZE, BUFFER) - rescue Errno::EINTR - retry - end - end - end end |