From d426c68c11d5733dd4462caafe92b28f436f30e7 Mon Sep 17 00:00:00 2001 From: Evan Weaver Date: Sat, 31 Jan 2009 21:40:40 -0800 Subject: No more special params hash. --- CHANGELOG | 2 ++ ext/http11/http11.c | 7 +++-- lib/mongrel.rb | 67 +++++++++++++++++------------------------- lib/mongrel/const.rb | 11 ++++--- lib/mongrel/http_request.rb | 68 ++++++++++--------------------------------- test/unit/test_http_parser.rb | 17 ----------- 6 files changed, 54 insertions(+), 118 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6075d77..8fa519f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ +v2.0. Rack support. + v1.1.4. Fix camping handler. Correct treatment of @throttle parameter. v1.1.3. Fix security flaw of DirHandler; reported on mailing list. diff --git a/ext/http11/http11.c b/ext/http11/http11.c index a228019..6b3ea34 100644 --- a/ext/http11/http11.c +++ b/ext/http11/http11.c @@ -20,7 +20,6 @@ static VALUE cHttpParser; static VALUE eHttpParserError; #define id_handler_map rb_intern("@handler_map") -#define id_http_body rb_intern("@http_body") #define HTTP_PREFIX "HTTP_" #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1) @@ -34,6 +33,7 @@ static VALUE global_http_content_length; static VALUE global_request_path; static VALUE global_content_type; static VALUE global_http_content_type; +static VALUE global_http_body; static VALUE global_gateway_interface; static VALUE global_gateway_interface_value; static VALUE global_server_name; @@ -306,8 +306,8 @@ void header_done(void *data, const char *at, size_t length) } } - /* grab the initial body and stuff it into an ivar */ - rb_ivar_set(req, id_http_body, rb_str_new(at, length)); + /* grab the initial body and stuff it into the hash */ + rb_hash_aset(req, global_http_body, rb_str_new(at, length)); rb_hash_aset(req, global_server_protocol, global_server_protocol_value); rb_hash_aset(req, global_server_software, global_mongrel_version); } @@ -499,6 +499,7 @@ void Init_http11() DEF_GLOBAL(request_path, "REQUEST_PATH"); DEF_GLOBAL(content_length, "CONTENT_LENGTH"); DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH"); + DEF_GLOBAL(http_body, "HTTP_BODY"); DEF_GLOBAL(content_type, "CONTENT_TYPE"); DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE"); DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE"); diff --git a/lib/mongrel.rb b/lib/mongrel.rb index b81f07e..39659a2 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -33,11 +33,6 @@ module Mongrel # A logger instance that conforms to the API of stdlib's Logger. attr_accessor :logger - # By default, will return an instance of stdlib's Logger logging to STDERR - def logger - @logger ||= Logger.new(STDERR) - end - def run(app, options = {}) HttpServer.new(app, options).start.join end @@ -52,11 +47,6 @@ module Mongrel # Thrown by HttpServer#stop if the server is not started. class AcceptorError < StandardError; end - # A Hash with one extra parameter for the HTTP body, used internally. - class HttpParams < Hash - attr_accessor :http_body - end - # # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier # make up the majority of how the server functions. It's a very simple class that just @@ -64,20 +54,15 @@ module Mongrel # to do the heavy lifting with the IO and Ruby. # class HttpServer - attr_reader :acceptor - attr_reader :workers - attr_reader :host - attr_reader :port - attr_reader :timeout - attr_reader :max_queued_threads - attr_reader :max_concurrent_threads + attr_reader :acceptor, :workers, :logger, :host, :port, :timeout, :max_queued_threads, :max_concurrent_threads DEFAULTS = { - :Max_queued_threads => 20, - :Max_concurrent_threads => 20, - :Timeout => 60, - :Host => '0.0.0.0', - :Port => 8080 + :timeout => 60, + :host => '0.0.0.0', + :port => 8080, + :logger => Logger.new(STDERR), + :max_queued_threads => 20, + :max_concurrent_threads => 20 } # Creates a working server on host:port (strange things happen if port isn't a Number). @@ -110,7 +95,7 @@ module Mongrel def process_client(client) begin parser = HttpParser.new - params = HttpParams.new + params = Hash.new request = nil data = client.readpartial(Const::CHUNK_SIZE) nparsed = 0 @@ -123,13 +108,13 @@ module Mongrel nparsed = parser.execute(params, data, nparsed) if parser.finished? - if not params[Const::REQUEST_PATH] - # it might be a dumbass full host request header + if !params[Const::REQUEST_PATH] + # It might be a dumbass full host request header uri = URI.parse(params[Const::REQUEST_URI]) params[Const::REQUEST_PATH] = uri.path end - raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] + raise "No REQUEST PATH" if !params[Const::REQUEST_PATH] params[Const::PATH_INFO] = params[Const::REQUEST_PATH] params[Const::SCRIPT_NAME] = Const::SLASH @@ -142,8 +127,8 @@ module Mongrel # or other intermediary acting on behalf of the actual source client." params[Const::REMOTE_ADDR] = client.peeraddr.last - # select handlers that want more detailed request notification - request = HttpRequest.new(params, client) + # Select handlers that want more detailed request notification + request = HttpRequest.new(params, client, logger) # in the case of large file uploads the user could close the socket, so skip those requests break if request.body == nil # nil signals from HttpRequest::initialize that the request was aborted @@ -164,21 +149,21 @@ module Mongrel rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF client.close rescue nil rescue HttpParserError => e - Mongrel.logger.error "#{Time.now}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}" - Mongrel.logger.error "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" + logger.error "#HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}" + logger.error "#REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" rescue Errno::EMFILE reap_dead_workers('too many files') rescue Object => e - Mongrel.logger.error "#{Time.now}: Read error: #{e.inspect}" - Mongrel.logger.error e.backtrace.join("\n") + logger.error "#Read error: #{e.inspect}" + logger.error e.backtrace.join("\n") ensure begin client.close rescue IOError # Already closed rescue Object => e - Mongrel.logger.error "#{Time.now}: Client error: #{e.inspect}" - Mongrel.logger.error e.backtrace.join("\n") + logger.error "#Client error: #{e.inspect}" + logger.error e.backtrace.join("\n") end request.body.close! if request and request.body.class == Tempfile end @@ -190,14 +175,14 @@ module Mongrel # after the reap is done. It only runs if there are workers to reap. def reap_dead_workers(reason='unknown') if @workers.list.length > 0 - Mongrel.logger.info "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'" + logger.info "#Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'" error_msg = "Mongrel timed out this thread: #{reason}" mark = Time.now @workers.list.each do |worker| worker[:started_on] = Time.now if not worker[:started_on] if mark - worker[:started_on] > @timeout - Mongrel.logger.info "Thread #{worker.inspect} is too old, killing." + logger.info "Thread #{worker.inspect} is too old, killing." worker.raise(TimeoutError.new(error_msg)) end end @@ -211,7 +196,7 @@ module Mongrel # via mongrel_rails. def graceful_shutdown while reap_dead_workers("shutdown") > 0 - Mongrel.logger.info "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout} seconds." + logger.info "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout} seconds." sleep @timeout / 10 end end @@ -257,7 +242,7 @@ module Mongrel worker_list = @workers.list if worker_list.length >= @max_queued_threads - Mongrel.logger.error "Server overloaded with #{worker_list.length} processors (#@max_queued_threads max). Dropping connection." + logger.error "Server overloaded with #{worker_list.length} processors (#@max_queued_threads max). Dropping connection." client.close rescue nil reap_dead_workers("max processors") else @@ -274,14 +259,14 @@ module Mongrel # client closed the socket even before accept client.close rescue nil rescue Object => e - Mongrel.logger.error "#{Time.now}: Unhandled listen loop exception #{e.inspect}." - Mongrel.logger.error e.backtrace.join("\n") + logger.error "Unhandled listen loop exception #{e.inspect}." + logger.error e.backtrace.join("\n") end end graceful_shutdown ensure @socket.close - # Mongrel.logger.info "#{Time.now}: Closed socket." + logger.info "Closed socket." end end diff --git a/lib/mongrel/const.rb b/lib/mongrel/const.rb index a9b100e..994a2bf 100644 --- a/lib/mongrel/const.rb +++ b/lib/mongrel/const.rb @@ -53,10 +53,13 @@ module Mongrel # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or # too taxing on performance. module Const - DATE = "Date".freeze + DATE="Date".freeze - # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this. + # This is the part of the path after the SCRIPT_NAME. PATH_INFO="PATH_INFO".freeze + + # 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 @@ -64,8 +67,8 @@ module Mongrel # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME. REQUEST_URI='REQUEST_URI'.freeze REQUEST_PATH='REQUEST_PATH'.freeze - - MONGREL_VERSION="1.2.0".freeze + + MONGREL_VERSION="2.0".freeze MONGREL_TMP_BASE="mongrel".freeze diff --git a/lib/mongrel/http_request.rb b/lib/mongrel/http_request.rb index 70f236f..f76e0bb 100644 --- a/lib/mongrel/http_request.rb +++ b/lib/mongrel/http_request.rb @@ -6,22 +6,24 @@ module Mongrel # a StringIO object. To be safe, you should assume it works like a file. # class HttpRequest - attr_reader :body, :params + attr_reader :body, :params, :logger # You don't really call this. It's made for you. # Main thing it does is hook up the params, and store any remaining # body data into the HttpRequest.body attribute. - def initialize(params, socket) + def initialize(params, socket, logger) @params = params @socket = socket + @logger = logger + content_length = @params[Const::CONTENT_LENGTH].to_i - remain = content_length - @params.http_body.length + remain = content_length - @params[Const::HTTP_BODY].length # 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. if remain <= 0 # we've got everything, pack it up @body = StringIO.new - @body.write @params.http_body + @body.write @params[Const::HTTP_BODY] elsif remain > 0 # must read more data to complete body if remain > Const::MAX_BODY @@ -33,7 +35,7 @@ module Mongrel @body = StringIO.new end - @body.write @params.http_body + @body.write @params[Const::HTTP_BODY] read_body(remain, content_length) end @@ -66,21 +68,20 @@ module Mongrel # part of the body that has been read to be in the @body already. def read_body(remain, total) begin - # write the odd sized chunk first - @params.http_body = read_socket(remain % Const::CHUNK_SIZE) + # Write the odd sized chunk first + @params[Const::HTTP_BODY] = read_socket(remain % Const::CHUNK_SIZE) - remain -= @body.write(@params.http_body) + remain -= @body.write(@params[Const::HTTP_BODY]) - # then stream out nothing but perfectly sized chunks + # Then stream out nothing but perfectly sized chunks until remain <= 0 or @socket.closed? # ASSUME: we are writing to a disk and these writes always write the requested amount - @params.http_body = read_socket(Const::CHUNK_SIZE) - remain -= @body.write(@params.http_body) + @params[Const::HTTP_BODY] = read_socket(Const::CHUNK_SIZE) + remain -= @body.write(@params[Const::HTTP_BODY]) end rescue Object => e - Mongrel.logger.error "#{Time.now}: Error reading HTTP body: #{e.inspect}" - Mongrel.logger.error e.backtrace.join("\n") - # any errors means we should delete the file, including if the file is dumped + logger.error "Error reading HTTP body: #{e.inspect}" + # Any errors means we should delete the file, including if the file is dumped @socket.close rescue nil @body.close! if @body.class == Tempfile @body = nil # signals that there was a problem @@ -101,44 +102,5 @@ module Mongrel raise "Socket already closed when reading." end end - - # Performs URI escaping so that you can construct proper - # query strings faster. Use this rather than the cgi.rb - # version since it's faster. (Stolen from Camping). - def self.escape(s) - s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { - '%'+$1.unpack('H2'*$1.size).join('%').upcase - }.tr(' ', '+') - end - - - # Unescapes a URI escaped string. (Stolen from Camping). - def self.unescape(s) - s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ - [$1.delete('%')].pack('H*') - } - end - - # Parses a query string by breaking it up at the '&' - # and ';' characters. You can also use this to parse - # cookies by changing the characters used in the second - # parameter (which defaults to '&;'. - def self.query_parse(qs, d = '&;') - params = {} - (qs||'').split(/[#{d}] */n).inject(params) { |h,p| - k, v=unescape(p).split('=',2) - if cur = params[k] - if cur.class == Array - params[k] << v - else - params[k] = [cur, v] - end - else - params[k] = v - end - } - - return params - end end end diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb index c89cd0d..91482cd 100644 --- a/test/unit/test_http_parser.rb +++ b/test/unit/test_http_parser.rb @@ -157,22 +157,5 @@ class HttpParserTest < Test::Unit::TestCase end end - - - - def test_query_parse - res = HttpRequest.query_parse("zed=1&frank=#{HttpRequest.escape('&&& ')}") - assert res["zed"], "didn't get the request right" - assert res["frank"], "no frank" - assert_equal "1", res["zed"], "wrong result" - assert_equal "&&& ", HttpRequest.unescape(res["frank"]), "wrong result" - - res = HttpRequest.query_parse("zed=1&zed=2&zed=3&frank=11;zed=45") - assert res["zed"], "didn't get the request right" - assert res["frank"], "no frank" - assert_equal 4,res["zed"].length, "wrong number for zed" - assert_equal "11",res["frank"], "wrong number for frank" - end - end -- cgit v1.2.3-24-ge0c7