From 5d57a6f2525c3c1feea98d64e1caf2f32147cf7f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 22 Mar 2009 20:39:33 -0700 Subject: Streamline rack environment generation Ensure constants are used as hash keys and cleanup unused constants. This gives a 10-15% improvement with test/benchmark/request.rb --- lib/unicorn/const.rb | 34 +++-------------------- lib/unicorn/http_request.rb | 66 ++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 57 deletions(-) diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb index 8f9e978..f3dabec 100644 --- a/lib/unicorn/const.rb +++ b/lib/unicorn/const.rb @@ -48,10 +48,6 @@ module Unicorn # the constant just refers to a string with the same contents. Using these constants # gave about a 3% to 10% performance improvement over using the strings directly. # Symbols did not really improve things much compared to constants. - # - # While Unicorn does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT, - # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or - # too taxing on performance. module Const DATE="Date".freeze @@ -61,10 +57,7 @@ module Unicorn # Request body HTTP_BODY="HTTP_BODY".freeze - # This is the initial part that your handler is identified as by URIClassifier. - SCRIPT_NAME="SCRIPT_NAME".freeze - - # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME. + # The original URI requested by the client. REQUEST_URI='REQUEST_URI'.freeze REQUEST_PATH='REQUEST_PATH'.freeze @@ -76,14 +69,6 @@ module Unicorn DEFAULT_PORT = "8080".freeze # default TCP listen port DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}".freeze - # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff. - ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Unicorn #{UNICORN_VERSION}\r\n\r\nNOT FOUND".freeze - - CONTENT_LENGTH="CONTENT_LENGTH".freeze - - # A common header for indicating the server is too busy. Not used yet. - ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze - # The basic max request size we'll try to read. CHUNK_SIZE=(16 * 1024) @@ -95,22 +80,11 @@ module Unicorn MAX_BODY=MAX_HEADER # A frozen format for this is about 15% faster - CONTENT_TYPE = "Content-Type".freeze - LAST_MODIFIED = "Last-Modified".freeze - ETAG = "ETag".freeze - REQUEST_METHOD="REQUEST_METHOD".freeze - GET="GET".freeze - HEAD="HEAD".freeze - # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32) - ETAG_FORMAT="\"%x-%x-%x\"".freeze - LINE_END="\r\n".freeze + CONTENT_LENGTH="CONTENT_LENGTH".freeze REMOTE_ADDR="REMOTE_ADDR".freeze HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze - HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze - HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze - REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze - HOST = "HOST".freeze - CONNECTION = "Connection".freeze + QUERY_STRING="QUERY_STRING".freeze + RACK_INPUT="rack.input".freeze end end diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index 411c56c..63d0f2e 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -13,6 +13,20 @@ 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.url_scheme" => "http", + "rack.version" => [0, 1], + "SCRIPT_NAME" => "", + + # this is not in the Rack spec, but some apps may rely on it + "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}" + }.freeze + def initialize(logger) @logger = logger @body = nil @@ -52,17 +66,7 @@ module Unicorn nparsed = @parser.execute(@params, data, nparsed) if @parser.finished? - # 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) - # may not identify the ultimate source of the request. They - # 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." - @params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr - - handle_body(socket) and return rack_env # success! - return nil # fail + return handle_body(socket) ? rack_env(socket) : nil else # Parser is not done, queue up more data to read and continue # parsing @@ -123,23 +127,31 @@ module Unicorn # Returns an environment which is rackable: # http://rack.rubyforge.org/doc/files/SPEC.html # Based on Rack's old Mongrel handler. - def rack_env + def rack_env(socket) + # I'm considering enabling "unicorn.client". It gives + # applications some rope to do some "interesting" things like + # replacing a worker with another process that has full control + # over the HTTP response. + # @params["unicorn.client"] = socket + + # 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) + # may not identify the ultimate source of the request. They + # 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." + @params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr + # It might be a dumbass full host request header - @params[Const::REQUEST_PATH] ||= - URI.parse(@params[Const::REQUEST_URI]).path - raise "No REQUEST PATH" unless @params[Const::REQUEST_PATH] - - @params["QUERY_STRING"] ||= '' - @params.update({ "rack.version" => [0,1], - "rack.input" => @body, - "rack.errors" => $stderr, - "rack.multithread" => false, - "rack.multiprocess" => true, - "rack.run_once" => false, - "rack.url_scheme" => "http", - Const::PATH_INFO => @params[Const::REQUEST_PATH], - Const::SCRIPT_NAME => "", - }) + @params[Const::PATH_INFO] = ( + @params[Const::REQUEST_PATH] ||= + URI.parse(@params[Const::REQUEST_URI]).path) or + raise "No REQUEST_PATH" + + @params[Const::QUERY_STRING] ||= '' + @params[Const::RACK_INPUT] = @body + @params.update(DEF_PARAMS) end # Does the heavy lifting of properly reading the larger body requests in -- cgit v1.2.3-24-ge0c7