From e25b558ba8d2d6812d490b365539c2a7bf219e12 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Sat, 22 Nov 2008 14:15:54 -0800 Subject: Added testing for rack support --- .gitignore | 9 +++++++++ test/unit/test_ws.rb | 15 ++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2658b88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +ext/http11/Makefile +ext/http11/http11.bundle +ext/http11/http11.o +ext/http11/http11_parser.o +ext/http11/mkmf.log +lib/http11.bundle +log/ +test_stderr.log +test_stdout.log diff --git a/test/unit/test_ws.rb b/test/unit/test_ws.rb index 9de8a45..036752a 100644 --- a/test/unit/test_ws.rb +++ b/test/unit/test_ws.rb @@ -8,13 +8,14 @@ require 'test/test_helper' include Mongrel -class TestHandler < Mongrel::HttpHandler +class TestHandler attr_reader :ran_test - def process(request, response) + def call(env) @ran_test = true - response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n") - end + # response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n") + [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']] + end end @@ -23,13 +24,13 @@ class WebServerTest < Test::Unit::TestCase def setup @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n" @port = process_based_port - + @tester = TestHandler.new + @app = Rack::URLMap.new('/test' => @tester) redirect_test_io do # We set num_processors=1 so that we can test the reaping code - @server = HttpServer.new("127.0.0.1", @port, num_processors=1) + @server = HttpServer.new("127.0.0.1", @port, @app, :num_processors => 1) end - @tester = TestHandler.new @server.register("/test", @tester) redirect_test_io do @server.run -- cgit v1.2.3-24-ge0c7 From a59c388dd518d0b0ff85d0621d3406fcc23d6173 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Sat, 22 Nov 2008 17:48:36 -0800 Subject: Non working rack implementation --- lib/mongrel.rb | 44 +++++++++++++++++++++++++------------------- lib/mongrel/http_request.rb | 21 ++++++++++++++++++++- lib/mongrel/http_response.rb | 21 +++++++++++---------- test/unit/test_ws.rb | 6 +----- 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/lib/mongrel.rb b/lib/mongrel.rb index f09a617..e3c56be 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -17,6 +17,7 @@ require 'mongrel/gems' Mongrel::Gems.require 'cgi_multipart_eof_fix' Mongrel::Gems.require 'fastthread' require 'thread' +require 'rack' # Ruby Mongrel require 'mongrel/cgi' @@ -88,21 +89,21 @@ module Mongrel # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between # socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and # actually if it is 0 then the sleep is not done at all. - def initialize(host, port, num_processors=950, throttle=0, timeout=60) - + def initialize(host, port, app, opts = {}) tries = 0 @socket = TCPServer.new(host, port) if defined?(Fcntl::FD_CLOEXEC) @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) end - @classifier = URIClassifier.new @host = host @port = port @workers = ThreadGroup.new - @throttle = throttle / 100.0 - @num_processors = num_processors - @timeout = timeout + # Set default opts + @app = app + @num_processors = opts.delete(:num_processors) + @throttle = (opts.delete(:throttle) || 0) / 100 + @timeout = opts.delete(:timeout) || 60 end # Does the majority of the IO processing. It has been written in Ruby using @@ -134,6 +135,7 @@ module Mongrel raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] + script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) if handlers @@ -154,20 +156,23 @@ module Mongrel # 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 + raise "CALLING APPPPPPP" + app_responce = @app.call(request.env) + response = HttpResponse.new(client, app_response).start # request is good so far, continue processing the response - response = HttpResponse.new(client) - - # Process each handler in registered order until we run out or one finalizes the response. - handlers.each do |handler| - handler.process(request, response) - break if response.done or client.closed? - end - - # And finally, if nobody closed the response off, we finalize it. - unless response.done or client.closed? - response.finished - end + # response = HttpResponse.new(client) + + # # Process each handler in registered order until we run out or one finalizes the response. + # handlers.each do |handler| + # handler.process(request, response) + # break if response.done or client.closed? + # end + + # # And finally, if nobody closed the response off, we finalize it. + # unless response.done or client.closed? + # response.finished + # end else # Didn't find it, return a stock 404 response. client.write(Const::ERROR_404_RESPONSE) @@ -260,7 +265,8 @@ module Mongrel # Runs the thing. It returns the thread used so you can "join" it. You can also # access the HttpServer::acceptor attribute to get the thread later. - def run + def start! + p @num_processors BasicSocket.do_not_reverse_lookup=true configure_socket_options diff --git a/lib/mongrel/http_request.rb b/lib/mongrel/http_request.rb index c8d4ce4..8df3a5a 100644 --- a/lib/mongrel/http_request.rb +++ b/lib/mongrel/http_request.rb @@ -58,6 +58,25 @@ module Mongrel @body.rewind if @body end + # returns an environment which is rackable + # http://rack.rubyforge.org/doc/files/SPEC.html + def env + env = params.clone + env.delete "HTTP_CONTENT_TYPE" + env.delete "HTTP_CONTENT_LENGTH" + env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" + env.update({"rack.version" => [0,1], + "rack.input" => @body, + "rack.errors" => STDERR, + + "rack.multithread" => true, + "rack.multiprocess" => false, # ??? + "rack.run_once" => false, + + "rack.url_scheme" => "http", + }) + end + # updates all dispatchers about our progress def update_request_progress(clen, total) return if @dispatchers.nil? || @dispatchers.empty? @@ -152,4 +171,4 @@ module Mongrel return params end end -end \ No newline at end of file +end diff --git a/lib/mongrel/http_response.rb b/lib/mongrel/http_response.rb index 3076712..08d4d75 100644 --- a/lib/mongrel/http_response.rb +++ b/lib/mongrel/http_response.rb @@ -39,12 +39,13 @@ module Mongrel attr_reader :header_sent attr_reader :status_sent - def initialize(socket) + def initialize(socket, app_responce) @socket = socket - @body = StringIO.new - @status = 404 + @app_responce = app_responce + @body = app_responce[2] + @status = app_responce[0] @reason = nil - @header = HeaderOut.new(StringIO.new) + @header = HeaderOut.new(app_responce[1]) @header[Const::DATE] = Time.now.httpdate @body_sent = false @header_sent = false @@ -59,10 +60,10 @@ module Mongrel # by simple passing "finalize=true" to the start method. By default # all handlers run and then mongrel finalizes the request when they're # all done. - def start(status=200, finalize=false, reason=nil) - @status = status.to_i - @reason = reason - yield @header, @body + # TODO: docs + def start #(status=200, finalize=false, reason=nil) + @reason = nil # TODO take this out + out.write(@body) finished if finalize end @@ -78,7 +79,7 @@ module Mongrel # XXX Dubious ( http://mongrel.rubyforge.org/ticket/19 ) @header.out.close @header = HeaderOut.new(StringIO.new) - + @body.close @body = StringIO.new end @@ -163,4 +164,4 @@ module Mongrel end end -end \ No newline at end of file +end diff --git a/test/unit/test_ws.rb b/test/unit/test_ws.rb index 036752a..7508c7f 100644 --- a/test/unit/test_ws.rb +++ b/test/unit/test_ws.rb @@ -29,11 +29,7 @@ class WebServerTest < Test::Unit::TestCase redirect_test_io do # We set num_processors=1 so that we can test the reaping code @server = HttpServer.new("127.0.0.1", @port, @app, :num_processors => 1) - end - - @server.register("/test", @tester) - redirect_test_io do - @server.run + @server.start! end end -- cgit v1.2.3-24-ge0c7 From 01a76eb1ddc67dda21cc2646dc73c15830cb3de7 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Sat, 22 Nov 2008 18:00:10 -0800 Subject: removed dispatchers --- lib/mongrel.rb | 60 ++++++++++++++------------------------------- lib/mongrel/http_request.rb | 8 +----- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/lib/mongrel.rb b/lib/mongrel.rb index e3c56be..423abeb 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -138,47 +138,25 @@ module Mongrel script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) - if handlers - params[Const::PATH_INFO] = path_info - params[Const::SCRIPT_NAME] = script_name - - # 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] = client.peeraddr.last - - # select handlers that want more detailed request notification - notifiers = handlers.select { |h| h.request_notify } - request = HttpRequest.new(params, client, notifiers) - - # 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 - raise "CALLING APPPPPPP" - app_responce = @app.call(request.env) - response = HttpResponse.new(client, app_response).start - - # request is good so far, continue processing the response - # response = HttpResponse.new(client) - - # # Process each handler in registered order until we run out or one finalizes the response. - # handlers.each do |handler| - # handler.process(request, response) - # break if response.done or client.closed? - # end - - # # And finally, if nobody closed the response off, we finalize it. - # unless response.done or client.closed? - # response.finished - # end - else - # Didn't find it, return a stock 404 response. - client.write(Const::ERROR_404_RESPONSE) - end - - break #done + params[Const::PATH_INFO] = path_info + params[Const::SCRIPT_NAME] = script_name + + # 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] = client.peeraddr.last + + # select handlers that want more detailed request notification + request = HttpRequest.new(params, client) + + # 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 + app_response = @app.call(request.env) + response = HttpResponse.new(client, app_response).start + break #done else # Parser is not done, queue up more data to read and continue parsing chunk = client.readpartial(Const::CHUNK_SIZE) diff --git a/lib/mongrel/http_request.rb b/lib/mongrel/http_request.rb index 8df3a5a..0e3790f 100644 --- a/lib/mongrel/http_request.rb +++ b/lib/mongrel/http_request.rb @@ -22,17 +22,11 @@ module Mongrel # 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, dispatchers) + def initialize(params, socket) @params = params @socket = socket - @dispatchers = dispatchers content_length = @params[Const::CONTENT_LENGTH].to_i remain = content_length - @params.http_body.length - - # tell all dispatchers the request has begun - @dispatchers.each do |dispatcher| - dispatcher.request_begins(@params) - end unless @dispatchers.nil? || @dispatchers.empty? # 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 -- cgit v1.2.3-24-ge0c7 From 18ac0fcc47f5533d695d629ca60f6d8cd2befd3f Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Sat, 22 Nov 2008 18:20:46 -0800 Subject: test_ws passes with rack --- lib/mongrel/header_out.rb | 10 ++++++++-- lib/mongrel/http_response.rb | 15 +++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/mongrel/header_out.rb b/lib/mongrel/header_out.rb index b34e95e..008bff8 100644 --- a/lib/mongrel/header_out.rb +++ b/lib/mongrel/header_out.rb @@ -10,13 +10,19 @@ module Mongrel attr_reader :out attr_accessor :allowed_duplicates - def initialize(out) + def initialize(out = StringIO.new) @sent = {} @allowed_duplicates = {"Set-Cookie" => true, "Set-Cookie2" => true, "Warning" => true, "WWW-Authenticate" => true} @out = out end + def merge!(hash) + hash.each do |key, value| + self[key] = value + end + end + # Simply writes "#{key}: #{value}" to an output buffer. def[]=(key,value) if not @sent.has_key?(key) or @allowed_duplicates.has_key?(key) @@ -25,4 +31,4 @@ module Mongrel end end end -end \ No newline at end of file +end diff --git a/lib/mongrel/http_response.rb b/lib/mongrel/http_response.rb index 08d4d75..bda2ed3 100644 --- a/lib/mongrel/http_response.rb +++ b/lib/mongrel/http_response.rb @@ -39,14 +39,15 @@ module Mongrel attr_reader :header_sent attr_reader :status_sent - def initialize(socket, app_responce) + def initialize(socket, app_response) @socket = socket - @app_responce = app_responce - @body = app_responce[2] - @status = app_responce[0] + @app_response = app_response + @body = StringIO.new(app_response[2].join('')) + @status = app_response[0] @reason = nil - @header = HeaderOut.new(app_responce[1]) + @header = HeaderOut.new @header[Const::DATE] = Time.now.httpdate + @header.merge!(app_response[1]) @body_sent = false @header_sent = false @status_sent = false @@ -62,9 +63,7 @@ module Mongrel # all done. # TODO: docs def start #(status=200, finalize=false, reason=nil) - @reason = nil # TODO take this out - out.write(@body) - finished if finalize + finished end # Primarily used in exception handling to reset the response output in order to write -- cgit v1.2.3-24-ge0c7 From 5139bbfd22c48e29ea6c66d3e47631877db65492 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Sat, 22 Nov 2008 18:56:06 -0800 Subject: Join thread at the end of start --- lib/mongrel.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 423abeb..57b849b 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -101,7 +101,7 @@ module Mongrel @workers = ThreadGroup.new # Set default opts @app = app - @num_processors = opts.delete(:num_processors) + @num_processors = opts.delete(:num_processors) || 950 @throttle = (opts.delete(:throttle) || 0) / 100 @timeout = opts.delete(:timeout) || 60 end @@ -244,7 +244,6 @@ module Mongrel # Runs the thing. It returns the thread used so you can "join" it. You can also # access the HttpServer::acceptor attribute to get the thread later. def start! - p @num_processors BasicSocket.do_not_reverse_lookup=true configure_socket_options @@ -264,7 +263,6 @@ module Mongrel end worker_list = @workers.list - if worker_list.length >= @num_processors STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection." client.close rescue nil @@ -296,7 +294,7 @@ module Mongrel end end - return @acceptor + return @acceptor.join end # Simply registers a handler with the internal URIClassifier. When the URI is -- cgit v1.2.3-24-ge0c7 From b1dd32f73eb72d861810bd96305b85d082606c5b Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Fri, 28 Nov 2008 03:23:34 -0500 Subject: Unjoined thread for now. Will fix later. --- lib/mongrel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 57b849b..9fe0daf 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -294,7 +294,7 @@ module Mongrel end end - return @acceptor.join + return @acceptor end # Simply registers a handler with the internal URIClassifier. When the URI is -- cgit v1.2.3-24-ge0c7 From c178815ef6496d3d2dfe83b77a0332138a4ae1ee Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 00:50:24 -0500 Subject: Got tests working with rack --- lib/mongrel/http_response.rb | 2 +- test/unit/test_handlers.rb | 136 ------------------------------------- test/unit/test_redirect_handler.rb | 45 ------------ test/unit/test_response.rb | 74 ++------------------ 4 files changed, 5 insertions(+), 252 deletions(-) delete mode 100644 test/unit/test_handlers.rb delete mode 100644 test/unit/test_redirect_handler.rb diff --git a/lib/mongrel/http_response.rb b/lib/mongrel/http_response.rb index bda2ed3..811570b 100644 --- a/lib/mongrel/http_response.rb +++ b/lib/mongrel/http_response.rb @@ -87,7 +87,7 @@ module Mongrel def send_status(content_length=@body.length) if not @status_sent @header['Content-Length'] = content_length if content_length and @status != 304 - write(Const::STATUS_FORMAT % [@status, @reason || HTTP_STATUS_CODES[@status]]) + write(Const::STATUS_FORMAT % [@status, HTTP_STATUS_CODES[@status]]) @status_sent = true end end diff --git a/test/unit/test_handlers.rb b/test/unit/test_handlers.rb deleted file mode 100644 index 66bf010..0000000 --- a/test/unit/test_handlers.rb +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'test/test_helper' - -class SimpleHandler < Mongrel::HttpHandler - def process(request, response) - response.start do |head,out| - head["Content-Type"] = "text/html" - results = "Your request:
#{request.params.to_yaml}
View the files." - out << results - end - end -end - -class DumbHandler < Mongrel::HttpHandler - def process(request, response) - response.start do |head,out| - head["Content-Type"] = "text/html" - out.write("test") - end - end -end - -def check_status(results, expecting) - results.each do |res| - assert(res.kind_of?(expecting), "Didn't get #{expecting}, got: #{res.class}") - end -end - -class HandlersTest < Test::Unit::TestCase - - def setup - @port = process_based_port - stats = Mongrel::StatisticsFilter.new(:sample_rate => 1) - - @config = Mongrel::Configurator.new :host => '127.0.0.1' do - listener :port => process_based_port do - uri "/", :handler => SimpleHandler.new - uri "/", :handler => stats - uri "/404", :handler => Mongrel::Error404Handler.new("Not found") - uri "/dumb", :handler => Mongrel::DeflateFilter.new - uri "/dumb", :handler => DumbHandler.new, :in_front => true - uri "/files", :handler => Mongrel::DirHandler.new("doc") - uri "/files_nodir", :handler => Mongrel::DirHandler.new("doc", listing_allowed=false, index_html="none") - uri "/status", :handler => Mongrel::StatusHandler.new(:stats_filter => stats) - uri "/relative", :handler => Mongrel::DirHandler.new(nil, listing_allowed=false, index_html="none") - end - end - - unless windows? - File.open('/tmp/testfile', 'w') do - # Do nothing - end - end - - @config.run - end - - def teardown - @config.stop(false, true) - File.delete '/tmp/testfile' unless windows? - end - - def test_registration_exception_is_not_lost - assert_raises(Mongrel::URIClassifier::RegistrationError) do - @config = Mongrel::Configurator.new do - listener do - uri "bogus", :handler => SimpleHandler.new - end - end - end - end - - def test_more_web_server - res = hit([ "http://localhost:#{@port}/test", - "http://localhost:#{@port}/dumb", - "http://localhost:#{@port}/404", - "http://localhost:#{@port}/files/rdoc/index.html", - "http://localhost:#{@port}/files/rdoc/nothere.html", - "http://localhost:#{@port}/files/rdoc/", - "http://localhost:#{@port}/files_nodir/rdoc/", - "http://localhost:#{@port}/status", - ]) - check_status res, String - end - - def test_nil_dirhandler - return if windows? - # Camping uses this internally - handler = Mongrel::DirHandler.new(nil, false) - assert handler.can_serve("/tmp/testfile") - # Not a bug! A nil @file parameter is the only circumstance under which - # we are allowed to serve any existing file - assert handler.can_serve("../../../../../../../../../../tmp/testfile") - end - - def test_non_nil_dirhandler_is_not_vulnerable_to_path_traversal - # The famous security bug of Mongrel 1.1.2 - handler = Mongrel::DirHandler.new("/doc", false) - assert_nil handler.can_serve("/tmp/testfile") - assert_nil handler.can_serve("../../../../../../../../../../tmp/testfile") - end - - def test_deflate - Net::HTTP.start("localhost", @port) do |h| - # Test that no accept-encoding returns a non-deflated response - req = h.get("/dumb") - assert( - !req['Content-Encoding'] || - !req['Content-Encoding'].include?('deflate')) - assert_equal "test", req.body - - req = h.get("/dumb", {"Accept-Encoding" => "deflate"}) - # -MAX_WBITS stops zlib from looking for a zlib header - inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - assert req['Content-Encoding'].include?('deflate') - assert_equal "test", inflater.inflate(req.body) - end - end - - # TODO: find out why this fails on win32 but nowhere else - #def test_posting_fails_dirhandler - # req = Net::HTTP::Post.new("http://localhost:#{@port}/files/rdoc/") - # req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';') - # res = hit [["http://localhost:#{@port}/files/rdoc/",req]] - # check_status res, Net::HTTPNotFound - #end - - def test_unregister - @config.listeners["127.0.0.1:#{@port}"].unregister("/") - end -end diff --git a/test/unit/test_redirect_handler.rb b/test/unit/test_redirect_handler.rb deleted file mode 100644 index e990427..0000000 --- a/test/unit/test_redirect_handler.rb +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'test/test_helper' - -class RedirectHandlerTest < Test::Unit::TestCase - - def setup - @port = process_based_port - redirect_test_io do - @server = Mongrel::HttpServer.new('127.0.0.1', @port) - end - @server.run - @client = Net::HTTP.new('127.0.0.1', @port) - end - - def teardown - @server.stop(true) - end - - def test_simple_redirect - tester = Mongrel::RedirectHandler.new('/yo') - @server.register("/test", tester) - - sleep(1) - res = @client.request_get('/test') - assert res != nil, "Didn't get a response" - assert_equal ['/yo'], res.get_fields('Location') - end - - def test_rewrite - tester = Mongrel::RedirectHandler.new(/(\w+)/, '+\1+') - @server.register("/test", tester) - - sleep(1) - res = @client.request_get('/test/something') - assert_equal ['/+test+/+something+'], res.get_fields('Location') - end - -end - - diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb index b49c9df..769b0d6 100644 --- a/test/unit/test_response.rb +++ b/test/unit/test_response.rb @@ -12,11 +12,7 @@ class ResponseTest < Test::Unit::TestCase def test_response_headers out = StringIO.new - resp = HttpResponse.new(out) - resp.status = 200 - resp.header["Accept"] = "text/plain" - resp.header["X-Whatever"] = "stuff" - resp.body.write("test") + resp = HttpResponse.new(out,[200, {"X-Whatever" => "stuff"}, ["cool"]]) resp.finished assert out.length > 0, "output didn't have data" @@ -24,56 +20,7 @@ class ResponseTest < Test::Unit::TestCase def test_response_200 io = StringIO.new - resp = HttpResponse.new(io) - resp.start do |head,out| - head["Accept"] = "text/plain" - out.write("tested") - out.write("hello!") - end - - resp.finished - assert io.length > 0, "output didn't have data" - end - - def test_response_duplicate_header_squash - io = StringIO.new - resp = HttpResponse.new(io) - resp.start do |head,out| - head["Content-Length"] = 30 - head["Content-Length"] = 0 - end - - resp.finished - - assert_equal io.length, 95, "too much output" - end - - - def test_response_some_duplicates_allowed - allowed_duplicates = ["Set-Cookie", "Set-Cookie2", "Warning", "WWW-Authenticate"] - io = StringIO.new - resp = HttpResponse.new(io) - resp.start do |head,out| - allowed_duplicates.each do |dup| - 10.times do |i| - head[dup] = i - end - end - end - - resp.finished - - assert_equal io.length, 734, "wrong amount of output" - end - - def test_response_404 - io = StringIO.new - - resp = HttpResponse.new(io) - resp.start(404) do |head,out| - head['Accept'] = "text/plain" - out.write("NOT FOUND") - end + resp = HttpResponse.new(io, [200, {}, []]) resp.finished assert io.length > 0, "output didn't have data" @@ -101,24 +48,11 @@ class ResponseTest < Test::Unit::TestCase assert io.read[-contents.length..-1] == contents, "output doesn't end with file payload" end - def test_response_with_custom_reason - reason = "You made a bad request" - io = StringIO.new - resp = HttpResponse.new(io) - resp.start(400, false, reason) { |head,out| } - resp.finished - - io.rewind - assert_match(/.* #{reason}$/, io.readline.chomp, "wrong custom reason phrase") - end - def test_response_with_default_reason code = 400 io = StringIO.new - resp = HttpResponse.new(io) - resp.start(code) { |head,out| } - resp.finished - + resp = HttpResponse.new(io, [code, {}, []]) + resp.start io.rewind assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase") end -- cgit v1.2.3-24-ge0c7 From 2482c33a3b616ad62d0b564c0d7224dd8432ffdf Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 00:57:05 -0500 Subject: Removed configurator, replaced by Racks UrlMap --- lib/mongrel/configurator.rb | 388 ----------------------------------------- test/unit/test_configurator.rb | 88 ---------- 2 files changed, 476 deletions(-) delete mode 100644 lib/mongrel/configurator.rb delete mode 100644 test/unit/test_configurator.rb diff --git a/lib/mongrel/configurator.rb b/lib/mongrel/configurator.rb deleted file mode 100644 index 439b44c..0000000 --- a/lib/mongrel/configurator.rb +++ /dev/null @@ -1,388 +0,0 @@ -require 'yaml' -require 'etc' - -module Mongrel - # Implements a simple DSL for configuring a Mongrel server for your - # purposes. More used by framework implementers to setup Mongrel - # how they like, but could be used by regular folks to add more things - # to an existing mongrel configuration. - # - # It is used like this: - # - # require 'mongrel' - # config = Mongrel::Configurator.new :host => "127.0.0.1" do - # listener :port => 3000 do - # uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml")) - # end - # run - # end - # - # This will setup a simple DirHandler at the current directory and load additional - # mime types from mimy.yaml. The :host => "127.0.0.1" is actually not - # specific to the servers but just a hash of default parameters that all - # server or uri calls receive. - # - # When you are inside the block after Mongrel::Configurator.new you can simply - # call functions that are part of Configurator (like server, uri, daemonize, etc) - # without having to refer to anything else. You can also call these functions on - # the resulting object directly for additional configuration. - # - # A major thing about Configurator is that it actually lets you configure - # multiple listeners for any hosts and ports you want. These are kept in a - # map config.listeners so you can get to them. - # - # * :pid_file => Where to write the process ID. - class Configurator - attr_reader :listeners - attr_reader :defaults - attr_reader :needs_restart - - # You pass in initial defaults and then a block to continue configuring. - def initialize(defaults={}, &block) - @listener = nil - @listener_name = nil - @listeners = {} - @defaults = defaults - @needs_restart = false - @pid_file = defaults[:pid_file] - - if block - cloaker(&block).bind(self).call - end - end - - # Change privileges of the process to specified user and group. - def change_privilege(user, group) - begin - uid, gid = Process.euid, Process.egid - target_uid = Etc.getpwnam(user).uid if user - target_gid = Etc.getgrnam(group).gid if group - - if uid != target_uid or gid != target_gid - log "Initiating groups for #{user.inspect}:#{group.inspect}." - Process.initgroups(user, target_gid) - - log "Changing group to #{group.inspect}." - Process::GID.change_privilege(target_gid) - - log "Changing user to #{user.inspect}." - Process::UID.change_privilege(target_uid) - end - rescue Errno::EPERM => e - log "Couldn't change user and group to #{user.inspect}:#{group.inspect}: #{e.to_s}." - log "Mongrel failed to start." - exit 1 - end - end - - def remove_pid_file - File.unlink(@pid_file) if @pid_file and File.exists?(@pid_file) - end - - # Writes the PID file if we're not on Windows. - def write_pid_file - if RUBY_PLATFORM !~ /djgpp|(cyg|ms|bcc)win|mingw/ - log "Writing PID file to #{@pid_file}" - open(@pid_file,"w") {|f| f.write(Process.pid) } - open(@pid_file,"w") do |f| - f.write(Process.pid) - File.chmod(0644, @pid_file) - end - end - end - - # Generates a class for cloaking the current self and making the DSL nicer. - def cloaking_class - class << self - self - end - end - - # Do not call this. You were warned. - def cloaker(&block) - cloaking_class.class_eval do - define_method :cloaker_, &block - meth = instance_method( :cloaker_ ) - remove_method :cloaker_ - meth - end - end - - # This will resolve the given options against the defaults. - # Normally just used internally. - def resolve_defaults(options) - options.merge(@defaults) - end - - # Starts a listener block. This is the only one that actually takes - # a block and then you make Configurator.uri calls in order to setup - # your URIs and handlers. If you write your Handlers as GemPlugins - # then you can use load_plugins and plugin to load them. - # - # It expects the following options (or defaults): - # - # * :host => Host name to bind. - # * :port => Port to bind. - # * :num_processors => The maximum number of concurrent threads allowed. - # * :throttle => Time to pause (in hundredths of a second) between accepting clients. - # * :timeout => Time to wait (in seconds) before killing a stalled thread. - # * :user => User to change to, must have :group as well. - # * :group => Group to change to, must have :user as well. - # - def listener(options={},&block) - raise "Cannot call listener inside another listener block." if (@listener or @listener_name) - ops = resolve_defaults(options) - ops[:num_processors] ||= 950 - ops[:throttle] ||= 0 - ops[:timeout] ||= 60 - - @listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:throttle].to_i, ops[:timeout].to_i) - @listener_name = "#{ops[:host]}:#{ops[:port]}" - @listeners[@listener_name] = @listener - - if ops[:user] and ops[:group] - change_privilege(ops[:user], ops[:group]) - end - - # Does the actual cloaking operation to give the new implicit self. - if block - cloaker(&block).bind(self).call - end - - # all done processing this listener setup, reset implicit variables - @listener = nil - @listener_name = nil - end - - - # Called inside a Configurator.listener block in order to - # add URI->handler mappings for that listener. Use this as - # many times as you like. It expects the following options - # or defaults: - # - # * :handler => HttpHandler -- Handler to use for this location. - # * :in_front => true/false -- Rather than appending, it prepends this handler. - def uri(location, options={}) - ops = resolve_defaults(options) - @listener.register(location, ops[:handler], ops[:in_front]) - end - - - # Daemonizes the current Ruby script turning all the - # listeners into an actual "server" or detached process. - # You must call this *before* frameworks that open files - # as otherwise the files will be closed by this function. - # - # Does not work for Win32 systems (the call is silently ignored). - # - # Requires the following options or defaults: - # - # * :cwd => Directory to change to. - # * :log_file => Where to write STDOUT and STDERR. - # - # It is safe to call this on win32 as it will only require the daemons - # gem/library if NOT win32. - def daemonize(options={}) - ops = resolve_defaults(options) - # save this for later since daemonize will hose it - if RUBY_PLATFORM !~ /djgpp|(cyg|ms|bcc)win|mingw/ - require 'daemons/daemonize' - - logfile = ops[:log_file] - if logfile[0].chr != "/" - logfile = File.join(ops[:cwd],logfile) - if not File.exist?(File.dirname(logfile)) - log "!!! Log file directory not found at full path #{File.dirname(logfile)}. Update your configuration to use a full path." - exit 1 - end - end - - Daemonize.daemonize(logfile) - - # change back to the original starting directory - Dir.chdir(ops[:cwd]) - - else - log "WARNING: Win32 does not support daemon mode." - end - end - - - # Uses the GemPlugin system to easily load plugins based on their - # gem dependencies. You pass in either an :includes => [] or - # :excludes => [] setting listing the names of plugins to include - # or exclude from the determining the dependencies. - def load_plugins(options={}) - ops = resolve_defaults(options) - - load_settings = {} - if ops[:includes] - ops[:includes].each do |plugin| - load_settings[plugin] = GemPlugin::INCLUDE - end - end - - if ops[:excludes] - ops[:excludes].each do |plugin| - load_settings[plugin] = GemPlugin::EXCLUDE - end - end - - GemPlugin::Manager.instance.load(load_settings) - end - - - # Easy way to load a YAML file and apply default settings. - def load_yaml(file, default={}) - default.merge(YAML.load_file(file)) - end - - - # Loads the MIME map file and checks that it is correct - # on loading. This is commonly passed to Mongrel::DirHandler - # or any framework handler that uses DirHandler to serve files. - # You can also include a set of default MIME types as additional - # settings. See Mongrel::DirHandler for how the MIME types map - # is organized. - def load_mime_map(file, mime={}) - # configure any requested mime map - mime = load_yaml(file, mime) - - # check all the mime types to make sure they are the right format - mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 } - - return mime - end - - - # Loads and creates a plugin for you based on the given - # name and configured with the selected options. The options - # are merged with the defaults prior to passing them in. - def plugin(name, options={}) - ops = resolve_defaults(options) - GemPlugin::Manager.instance.create(name, ops) - end - - # Lets you do redirects easily as described in Mongrel::RedirectHandler. - # You use it inside the configurator like this: - # - # redirect("/test", "/to/there") # simple - # redirect("/to", /t/, 'w') # regexp - # redirect("/hey", /(w+)/) {|match| ...} # block - # - def redirect(from, pattern, replacement = nil, &block) - uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block) - end - - # Works like a meta run method which goes through all the - # configured listeners. Use the Configurator.join method - # to prevent Ruby from exiting until each one is done. - def run - @listeners.each {|name,s| - s.run - } - - $mongrel_sleeper_thread = Thread.new { loop { sleep 1 } } - end - - # Calls .stop on all the configured listeners so they - # stop processing requests (gracefully). By default it - # assumes that you don't want to restart. - def stop(needs_restart=false, synchronous=false) - @listeners.each do |name,s| - s.stop(synchronous) - end - @needs_restart = needs_restart - end - - - # This method should actually be called *outside* of the - # Configurator block so that you can control it. In other words - # do it like: config.join. - def join - @listeners.values.each {|s| s.acceptor.join } - end - - - # Calling this before you register your URIs to the given location - # will setup a set of handlers that log open files, objects, and the - # parameters for each request. This helps you track common problems - # found in Rails applications that are either slow or become unresponsive - # after a little while. - # - # You can pass an extra parameter *what* to indicate what you want to - # debug. For example, if you just want to dump rails stuff then do: - # - # debug "/", what = [:rails] - # - # And it will only produce the log/mongrel_debug/rails.log file. - # Available options are: :access, :files, :objects, :threads, :rails - # - # NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick. - def debug(location, what = [:access, :files, :objects, :threads, :rails]) - require 'mongrel/debug' - handlers = { - :access => "/handlers/requestlog::access", - :files => "/handlers/requestlog::files", - :objects => "/handlers/requestlog::objects", - :threads => "/handlers/requestlog::threads", - :rails => "/handlers/requestlog::params" - } - - # turn on the debugging infrastructure, and ObjectTracker is a pig - MongrelDbg.configure - - # now we roll through each requested debug type, turn it on and load that plugin - what.each do |type| - MongrelDbg.begin_trace type - uri location, :handler => plugin(handlers[type]) - end - end - - # Used to allow you to let users specify their own configurations - # inside your Configurator setup. You pass it a script name and - # it reads it in and does an eval on the contents passing in the right - # binding so they can put their own Configurator statements. - def run_config(script) - open(script) {|f| eval(f.read, proc {self}) } - end - - # Sets up the standard signal handlers that are used on most Ruby - # It only configures if the platform is not win32 and doesn't do - # a HUP signal since this is typically framework specific. - # - # Requires a :pid_file option given to Configurator.new to indicate a file to delete. - # It sets the MongrelConfig.needs_restart attribute if - # the start command should reload. It's up to you to detect this - # and do whatever is needed for a "restart". - # - # This command is safely ignored if the platform is win32 (with a warning) - def setup_signals(options={}) - ops = resolve_defaults(options) - - # forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C) - trap("INT") { log "INT signal received."; stop(false) } - - # clean up the pid file always - at_exit { remove_pid_file } - - if RUBY_PLATFORM !~ /djgpp|(cyg|ms|bcc)win|mingw/ - # graceful shutdown - trap("TERM") { log "TERM signal received."; stop } - trap("USR1") { log "USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"; $mongrel_debug_client = !$mongrel_debug_client } - # restart - trap("USR2") { log "USR2 signal received."; stop(true) } - - log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)." - else - log "Signals ready. INT => stop (no restart)." - end - end - - # Logs a simple message to STDERR (or the mongrel log if in daemon mode). - def log(msg) - STDERR.print "** ", msg, "\n" - end - - end -end diff --git a/test/unit/test_configurator.rb b/test/unit/test_configurator.rb deleted file mode 100644 index dc9713a..0000000 --- a/test/unit/test_configurator.rb +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'test/test_helper' - -$test_plugin_fired = 0 - -class TestPlugin < GemPlugin::Plugin "/handlers" - include Mongrel::HttpHandlerPlugin - - def process(request, response) - $test_plugin_fired += 1 - end -end - - -class Sentinel < GemPlugin::Plugin "/handlers" - include Mongrel::HttpHandlerPlugin - - def process(request, response) - raise "This Sentinel plugin shouldn't run." - end -end - - -class ConfiguratorTest < Test::Unit::TestCase - - def test_base_handler_config - @port = process_based_port - @config = nil - - redirect_test_io do - @config = Mongrel::Configurator.new :host => "localhost" do - listener :port => process_based_port do - # 2 in front should run, but the sentinel shouldn't since dirhandler processes the request - uri "/", :handler => plugin("/handlers/testplugin") - uri "/", :handler => plugin("/handlers/testplugin") - uri "/", :handler => Mongrel::DirHandler.new(".") - uri "/", :handler => plugin("/handlers/testplugin") - - uri "/test", :handler => plugin("/handlers/testplugin") - uri "/test", :handler => plugin("/handlers/testplugin") - uri "/test", :handler => Mongrel::DirHandler.new(".") - uri "/test", :handler => plugin("/handlers/testplugin") - - debug "/" - setup_signals - - run_config(HERE + "/mongrel.conf") - load_mime_map(HERE + "/mime.yaml") - - run - end - end - end - - # pp @config.listeners.values.first.classifier.routes - - @config.listeners.each do |host,listener| - assert listener.classifier.uris.length == 3, "Wrong number of registered URIs" - assert listener.classifier.uris.include?("/"), "/ not registered" - assert listener.classifier.uris.include?("/test"), "/test not registered" - end - - res = Net::HTTP.get(URI.parse("http://localhost:#{@port}/test")) - assert res != nil, "Didn't get a response" - assert $test_plugin_fired == 3, "Test filter plugin didn't run 3 times." - - redirect_test_io do - res = Net::HTTP.get(URI.parse("http://localhost:#{@port}/")) - - assert res != nil, "Didn't get a response" - assert $test_plugin_fired == 6, "Test filter plugin didn't run 6 times." - end - - redirect_test_io do - @config.stop(false, true) - end - - assert_raise Errno::EBADF, Errno::ECONNREFUSED do - res = Net::HTTP.get(URI.parse("http://localhost:#{@port}/")) - end - end - -end -- cgit v1.2.3-24-ge0c7 From d2dcbb98ae7c3963c0c658c196644095ccaec1ca Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 03:45:02 -0500 Subject: Got rid of handlers and test_conditional, now people just use rack --- lib/mongrel/handlers.rb | 236 ------------------------------------------ test/unit/test_conditional.rb | 107 ------------------- 2 files changed, 343 deletions(-) delete mode 100644 test/unit/test_conditional.rb diff --git a/lib/mongrel/handlers.rb b/lib/mongrel/handlers.rb index e643025..ce24628 100644 --- a/lib/mongrel/handlers.rb +++ b/lib/mongrel/handlers.rb @@ -88,198 +88,6 @@ module Mongrel end - # - # Serves the contents of a directory. You give it the path to the root - # where the files are located, and it tries to find the files based on - # the PATH_INFO inside the directory. If the requested path is a - # directory then it returns a simple directory listing. - # - # It does a simple protection against going outside it's root path by - # converting all paths to an absolute expanded path, and then making - # sure that the final expanded path includes the root path. If it doesn't - # than it simply gives a 404. - # - # If you pass nil as the root path, it will not check any locations or - # expand any paths. This lets you serve files from multiple drives - # on win32. It should probably not be used in a public-facing way - # without additional checks. - # - # The default content type is "text/plain; charset=ISO-8859-1" but you - # can change it anything you want using the DirHandler.default_content_type - # attribute. - # - class DirHandler < HttpHandler - attr_accessor :default_content_type - attr_reader :path - - MIME_TYPES_FILE = "mime_types.yml" - MIME_TYPES = YAML.load_file(File.join(File.dirname(__FILE__), MIME_TYPES_FILE)) - - ONLY_HEAD_GET="Only HEAD and GET allowed.".freeze - - # You give it the path to the directory root and and optional listing_allowed and index_html - def initialize(path, listing_allowed=true, index_html="index.html") - @path = File.expand_path(path) if path - @listing_allowed = listing_allowed - @index_html = index_html - @default_content_type = "application/octet-stream".freeze - end - - # Checks if the given path can be served and returns the full path (or nil if not). - def can_serve(path_info) - - req_path = HttpRequest.unescape(path_info) - # Add the drive letter or root path - req_path = File.join(@path, req_path) if @path - req_path = File.expand_path req_path - - if File.exist? req_path and (!@path or req_path.index(@path) == 0) - # It exists and it's in the right location - if File.directory? req_path - # The request is for a directory - index = File.join(req_path, @index_html) - if File.exist? index - # Serve the index - return index - elsif @listing_allowed - # Serve the directory - return req_path - else - # Do not serve anything - return nil - end - else - # It's a file and it's there - return req_path - end - else - # does not exist or isn't in the right spot - return nil - end - end - - - # Returns a simplistic directory listing if they're enabled, otherwise a 403. - # Base is the base URI from the REQUEST_URI, dir is the directory to serve - # on the file system (comes from can_serve()), and response is the HttpResponse - # object to send the results on. - def send_dir_listing(base, dir, response) - # take off any trailing / so the links come out right - base = HttpRequest.unescape(base) - base.chop! if base[-1] == "/"[-1] - - if @listing_allowed - response.start(200) do |head,out| - head[Const::CONTENT_TYPE] = "text/html" - out << "Directory Listing" - Dir.entries(dir).each do |child| - next if child == "." - out << "" - out << (child == ".." ? "Up to parent.." : child) - out << "
" - end - out << "" - end - else - response.start(403) do |head,out| - out.write("Directory listings not allowed") - end - end - end - - - # Sends the contents of a file back to the user. Not terribly efficient since it's - # opening and closing the file for each read. - def send_file(req_path, request, response, header_only=false) - - stat = File.stat(req_path) - - # Set the last modified times as well and etag for all files - mtime = stat.mtime - # Calculated the same as apache, not sure how well the works on win32 - etag = Const::ETAG_FORMAT % [mtime.to_i, stat.size, stat.ino] - - modified_since = request.params[Const::HTTP_IF_MODIFIED_SINCE] - none_match = request.params[Const::HTTP_IF_NONE_MATCH] - - # test to see if this is a conditional request, and test if - # the response would be identical to the last response - same_response = case - when modified_since && !last_response_time = Time.httpdate(modified_since) rescue nil : false - when modified_since && last_response_time > Time.now : false - when modified_since && mtime > last_response_time : false - when none_match && none_match == '*' : false - when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) : false - else modified_since || none_match # validation successful if we get this far and at least one of the header exists - end - - header = response.header - header[Const::ETAG] = etag - - if same_response - response.start(304) {} - else - - # First we setup the headers and status then we do a very fast send on the socket directly - - # Support custom responses except 404, which is the default. A little awkward. - response.status = 200 if response.status == 404 - header[Const::LAST_MODIFIED] = mtime.httpdate - - # Set the mime type from our map based on the ending - dot_at = req_path.rindex('.') - if dot_at - header[Const::CONTENT_TYPE] = MIME_TYPES[req_path[dot_at .. -1]] || @default_content_type - else - header[Const::CONTENT_TYPE] = @default_content_type - end - - # send a status with out content length - response.send_status(stat.size) - response.send_header - - if not header_only - response.send_file(req_path, stat.size < Const::CHUNK_SIZE * 2) - end - end - end - - # Process the request to either serve a file or a directory listing - # if allowed (based on the listing_allowed parameter to the constructor). - def process(request, response) - req_method = request.params[Const::REQUEST_METHOD] || Const::GET - req_path = can_serve request.params[Const::PATH_INFO] - if not req_path - # not found, return a 404 - response.start(404) do |head,out| - out << "File not found" - end - else - begin - if File.directory? req_path - send_dir_listing(request.params[Const::REQUEST_URI], req_path, response) - elsif req_method == Const::HEAD - send_file(req_path, request, response, true) - elsif req_method == Const::GET - send_file(req_path, request, response, false) - else - response.start(403) {|head,out| out.write(ONLY_HEAD_GET) } - end - rescue => details - STDERR.puts "Error sending file #{req_path}: #{details}" - end - end - end - - # There is a small number of default mime types for extensions, but - # this lets you add any others you'll need when serving content. - def DirHandler::add_mime_type(extension, type) - MIME_TYPES[extension] = type - end - - end - - # When added to a config script (-S in mongrel_rails) it will # look at the client's allowed response types and then gzip # compress anything that is going out. @@ -421,48 +229,4 @@ module Mongrel end end end - - # This handler allows you to redirect one url to another. - # You can use it like String#gsub, where the string is the REQUEST_URI. - # REQUEST_URI is the full path with GET parameters. - # - # Eg. /test/something?help=true&disclaimer=false - # - # == Examples - # - # h = Mongrel::HttpServer.new('0.0.0.0') - # h.register '/test', Mongrel::RedirectHandler.new('/to/there') # simple - # h.register '/to', Mongrel::RedirectHandler.new(/t/, 'w') # regexp - # # and with a block - # h.register '/hey', Mongrel::RedirectHandler.new(/(\w+)/) { |match| ... } - # - class RedirectHandler < Mongrel::HttpHandler - # You set the rewrite rules when building the object. - # - # pattern => What to look for or replacement if used alone - # - # replacement, block => One of them is used to replace the found text - - def initialize(pattern, replacement = nil, &block) - unless replacement or block - @pattern, @replacement = nil, pattern - else - @pattern, @replacement, @block = pattern, replacement, block - end - end - - # Process the request and return a redirect response - def process(request, response) - unless @pattern - response.socket.write(Mongrel::Const::REDIRECT % @replacement) - else - if @block - new_path = request.params['REQUEST_URI'].gsub(@pattern, &@block) - else - new_path = request.params['REQUEST_URI'].gsub(@pattern, @replacement) - end - response.socket.write(Mongrel::Const::REDIRECT % new_path) - end - end - end end diff --git a/test/unit/test_conditional.rb b/test/unit/test_conditional.rb deleted file mode 100644 index 64517db..0000000 --- a/test/unit/test_conditional.rb +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'test/test_helper' - -include Mongrel - -class ConditionalResponseTest < Test::Unit::TestCase - def setup - @server = HttpServer.new('127.0.0.1', process_based_port) - @server.register('/', Mongrel::DirHandler.new('.')) - @server.run - - @http = Net::HTTP.new(@server.host, @server.port) - - # get the ETag and Last-Modified headers - @path = '/README' - res = @http.start { |http| http.get(@path) } - assert_not_nil @etag = res['ETag'] - assert_not_nil @last_modified = res['Last-Modified'] - assert_not_nil @content_length = res['Content-Length'] - end - - def teardown - @server.stop(true) - end - - # status should be 304 Not Modified when If-None-Match is the matching ETag - def test_not_modified_via_if_none_match - assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag - end - - # status should be 304 Not Modified when If-Modified-Since is the matching Last-Modified date - def test_not_modified_via_if_modified_since - assert_status_for_get_and_head Net::HTTPNotModified, 'If-Modified-Since' => @last_modified - end - - # status should be 304 Not Modified when If-None-Match is the matching ETag - # and If-Modified-Since is the matching Last-Modified date - def test_not_modified_via_if_none_match_and_if_modified_since - assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => @last_modified - end - - # status should be 200 OK when If-None-Match is invalid - def test_invalid_if_none_match - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid' - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid', 'If-Modified-Since' => @last_modified - end - - # status should be 200 OK when If-Modified-Since is invalid - def test_invalid_if_modified_since - assert_status_for_get_and_head Net::HTTPOK, 'If-Modified-Since' => 'invalid' - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => 'invalid' - end - - # status should be 304 Not Modified when If-Modified-Since is greater than the Last-Modified header, but less than the system time - def test_if_modified_since_greater_than_last_modified - sleep 2 - last_modified_plus_1 = (Time.httpdate(@last_modified) + 1).httpdate - assert_status_for_get_and_head Net::HTTPNotModified, 'If-Modified-Since' => last_modified_plus_1 - assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_plus_1 - end - - # status should be 200 OK when If-Modified-Since is less than the Last-Modified header - def test_if_modified_since_less_than_last_modified - last_modified_minus_1 = (Time.httpdate(@last_modified) - 1).httpdate - assert_status_for_get_and_head Net::HTTPOK, 'If-Modified-Since' => last_modified_minus_1 - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_minus_1 - end - - # status should be 200 OK when If-Modified-Since is a date in the future - def test_future_if_modified_since - the_future = Time.at(2**31-1).httpdate - assert_status_for_get_and_head Net::HTTPOK, 'If-Modified-Since' => the_future - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => the_future - end - - # status should be 200 OK when If-None-Match is a wildcard - def test_wildcard_match - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*' - assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*', 'If-Modified-Since' => @last_modified - end - - private - - # assert the response status is correct for GET and HEAD - def assert_status_for_get_and_head(response_class, headers = {}) - %w{ get head }.each do |method| - res = @http.send(method, @path, headers) - assert_kind_of response_class, res - assert_equal @etag, res['ETag'] - case response_class.to_s - when 'Net::HTTPNotModified' then - assert_nil res['Last-Modified'] - assert_nil res['Content-Length'] - when 'Net::HTTPOK' then - assert_equal @last_modified, res['Last-Modified'] - assert_equal @content_length, res['Content-Length'] - else - fail "Incorrect response class: #{response_class}" - end - end - end -end -- cgit v1.2.3-24-ge0c7 From 384e40051b90087323ad764712e94a3bb86bc49b Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 03:45:43 -0500 Subject: Got rid of send_file test, moved to rack --- test/unit/test_response.rb | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb index 769b0d6..f0efdb1 100644 --- a/test/unit/test_response.rb +++ b/test/unit/test_response.rb @@ -26,28 +26,6 @@ class ResponseTest < Test::Unit::TestCase assert io.length > 0, "output didn't have data" end - def test_response_file - contents = "PLAIN TEXT\r\nCONTENTS\r\n" - require 'tempfile' - tmpf = Tempfile.new("test_response_file") - tmpf.binmode - tmpf.write(contents) - tmpf.rewind - - io = StringIO.new - resp = HttpResponse.new(io) - resp.start(200) do |head,out| - head['Content-Type'] = 'text/plain' - resp.send_header - resp.send_file(tmpf.path) - end - io.rewind - tmpf.close - - assert io.length > 0, "output didn't have data" - assert io.read[-contents.length..-1] == contents, "output doesn't end with file payload" - end - def test_response_with_default_reason code = 400 io = StringIO.new @@ -56,6 +34,5 @@ class ResponseTest < Test::Unit::TestCase io.rewind assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase") end - end -- cgit v1.2.3-24-ge0c7 From 62c94742f39c511cd98e976f008e0de0140381c1 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 15:47:57 -0800 Subject: Removed configurator completely --- lib/mongrel.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 9fe0daf..251e2d4 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -24,7 +24,6 @@ require 'mongrel/cgi' require 'mongrel/handlers' require 'mongrel/command' require 'mongrel/tcphack' -require 'mongrel/configurator' require 'mongrel/uri_classifier' require 'mongrel/const' require 'mongrel/http_request' -- cgit v1.2.3-24-ge0c7 From 9c82be1259a951a8e34459b63f3ad27ea9b95a30 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 15:52:42 -0800 Subject: Removed request_progress, make it a middlewear or something --- lib/mongrel/http_request.rb | 14 ------ test/unit/test_request_progress.rb | 100 ------------------------------------- 2 files changed, 114 deletions(-) delete mode 100644 test/unit/test_request_progress.rb diff --git a/lib/mongrel/http_request.rb b/lib/mongrel/http_request.rb index 0e3790f..e20434b 100644 --- a/lib/mongrel/http_request.rb +++ b/lib/mongrel/http_request.rb @@ -33,7 +33,6 @@ module Mongrel # we've got everything, pack it up @body = StringIO.new @body.write @params.http_body - update_request_progress(0, content_length) elsif remain > 0 # must read more data to complete body if remain > Const::MAX_BODY @@ -71,15 +70,6 @@ module Mongrel }) end - # updates all dispatchers about our progress - def update_request_progress(clen, total) - return if @dispatchers.nil? || @dispatchers.empty? - @dispatchers.each do |dispatcher| - dispatcher.request_progress(@params, clen, total) - end - end - private :update_request_progress - # Does the heavy lifting of properly reading the larger body requests in # small chunks. It expects @body to be an IO object, @socket to be valid, # and will set @body = nil if the request fails. It also expects any initial @@ -91,15 +81,11 @@ module Mongrel remain -= @body.write(@params.http_body) - update_request_progress(remain, total) - # 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) - - update_request_progress(remain, total) end rescue Object => e STDERR.puts "#{Time.now}: Error reading HTTP body: #{e.inspect}" diff --git a/test/unit/test_request_progress.rb b/test/unit/test_request_progress.rb deleted file mode 100644 index a100426..0000000 --- a/test/unit/test_request_progress.rb +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'test/test_helper' - -class UploadBeginHandler < Mongrel::HttpHandler - attr_reader :request_began, :request_progressed, :request_processed - - def initialize - @request_notify = true - end - - def reset - @request_began = false - @request_progressed = false - @request_processed = false - end - - def request_begins(params) - @request_began = true - end - - def request_progress(params,len,total) - @request_progressed = true - end - - def process(request, response) - @request_processed = true - response.start do |head,body| - body.write("test") - end - end - -end - -class RequestProgressTest < Test::Unit::TestCase - def setup - @port = process_based_port - redirect_test_io do - @server = Mongrel::HttpServer.new("127.0.0.1", @port) - end - @handler = UploadBeginHandler.new - @server.register("/upload", @handler) - @server.run - end - - def teardown - @server.stop(true) - end - - def test_begin_end_progress - Net::HTTP.get("localhost", "/upload", @port) - assert @handler.request_began - assert @handler.request_progressed - assert @handler.request_processed - end - - def call_and_assert_handlers_in_turn(handlers) - # reset all handlers - handlers.each { |h| h.reset } - - # make the call - Net::HTTP.get("localhost", "/upload", @port) - - # assert that each one was fired - handlers.each { |h| - assert h.request_began && h.request_progressed && h.request_processed, - "Callbacks NOT fired for #{h}" - } - end - - def test_more_than_one_begin_end_progress - handlers = [@handler] - - second = UploadBeginHandler.new - @server.register("/upload", second) - handlers << second - call_and_assert_handlers_in_turn(handlers) - - # check three handlers - third = UploadBeginHandler.new - @server.register("/upload", third) - handlers << third - call_and_assert_handlers_in_turn(handlers) - - # remove handlers to make sure they've all gone away - @server.unregister("/upload") - handlers.each { |h| h.reset } - Net::HTTP.get("localhost", "/upload", @port) - handlers.each { |h| - assert !h.request_began && !h.request_progressed && !h.request_processed - } - - # re-register upload to the state before this test - @server.register("/upload", @handler) - end -end -- cgit v1.2.3-24-ge0c7 From 8fd2a302ca5121eca4c96ddd10f3f705a422d378 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Mon, 1 Dec 2008 15:59:21 -0800 Subject: Doc fixes --- lib/mongrel/http_request.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/mongrel/http_request.rb b/lib/mongrel/http_request.rb index e20434b..2416b04 100644 --- a/lib/mongrel/http_request.rb +++ b/lib/mongrel/http_request.rb @@ -1,21 +1,10 @@ module Mongrel - # - # When a handler is found for a registered URI then this class is constructed - # and passed to your HttpHandler::process method. You should assume that - # *one* handler processes all requests. Included in the HttpRequest is a - # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body - # which is a string containing the request body (raw for now). # # 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. - # - # The HttpHandler.request_notify system is implemented by having HttpRequest call - # HttpHandler.request_begins, HttpHandler.request_progress, HttpHandler.process during - # the IO processing. This adds a small amount of overhead but lets you implement - # finer controlled handlers and filters. - # + # class HttpRequest attr_reader :body, :params @@ -53,6 +42,7 @@ module Mongrel # returns an environment which is rackable # http://rack.rubyforge.org/doc/files/SPEC.html + # copied directly from racks mongrel handler def env env = params.clone env.delete "HTTP_CONTENT_TYPE" -- cgit v1.2.3-24-ge0c7 From 98e8c726b75d1ecd6ca91c66397a3e2a8d88f13f Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Wed, 3 Dec 2008 18:44:51 -0800 Subject: Dont need camping adapter anymore, rack does it for us --- lib/mongrel/camping.rb | 107 ------------------------------------------------- 1 file changed, 107 deletions(-) delete mode 100644 lib/mongrel/camping.rb diff --git a/lib/mongrel/camping.rb b/lib/mongrel/camping.rb deleted file mode 100644 index 31bd196..0000000 --- a/lib/mongrel/camping.rb +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'mongrel' - - -module Mongrel - # Support for the Camping micro framework at http://camping.rubyforge.org - # This implements the unusually long Postamble that Camping usually - # needs and shrinks it down to just a single line or two. - # - # Your Postamble would now be: - # - # Mongrel::Camping::start("0.0.0.0",3001,"/tepee",Tepee).join - # - # If you wish to get fancier than this then you can use the - # Camping::CampingHandler directly instead and do your own - # wiring: - # - # h = Mongrel::HttpServer.new(server, port) - # h.register(uri, CampingHandler.new(Tepee)) - # h.register("/favicon.ico", Mongrel::Error404Handler.new("")) - # - # I add the /favicon.ico since camping apps typically don't - # have them and it's just annoying anyway. - module Camping - - # This is a specialized handler for Camping applications - # that has them process the request and then translates - # the results into something the Mongrel::HttpResponse - # needs. - class CampingHandler < Mongrel::HttpHandler - attr_reader :files - attr_reader :guard - @@file_only_methods = ["GET","HEAD"] - - def initialize(klass) - @files = Mongrel::DirHandler.new(nil, false) - @guard = Mutex.new - @klass = klass - end - - def process(request, response) - if response.socket.closed? - return - end - - controller = nil - @guard.synchronize { - controller = @klass.run(request.body, request.params) - } - - sendfile, clength = nil - response.status = controller.status - controller.headers.each do |k, v| - if k =~ /^X-SENDFILE$/i - sendfile = v - elsif k =~ /^CONTENT-LENGTH$/i - clength = v.to_i - else - [*v].each do |vi| - response.header[k] = vi - end - end - end - - if sendfile - request.params[Mongrel::Const::PATH_INFO] = sendfile - @files.process(request, response) - elsif controller.body.respond_to? :read - response.send_status(clength) - response.send_header - while chunk = controller.body.read(16384) - response.write(chunk) - end - if controller.body.respond_to? :close - controller.body.close - end - else - body = controller.body.to_s - response.send_status(body.length) - response.send_header - response.write(body) - end - end - end - - # This is a convenience method that wires up a CampingHandler - # for your application on a given port and uri. It's pretty - # much all you need for a camping application to work right. - # - # It returns the Mongrel::HttpServer which you should either - # join or somehow manage. The thread is running when - # returned. - - def Camping.start(server, port, uri, klass) - h = Mongrel::HttpServer.new(server, port) - h.register(uri, CampingHandler.new(klass)) - h.register("/favicon.ico", Mongrel::Error404Handler.new("")) - h.run - return h - end - end -end -- cgit v1.2.3-24-ge0c7 From a5b87dcbe64f4fbfd7733e8762ec7b843ca7d39b Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Wed, 3 Dec 2008 18:45:27 -0800 Subject: Dont need rails adapter anymore, rack handles it --- lib/mongrel/rails.rb | 185 --------------------------------------------------- 1 file changed, 185 deletions(-) delete mode 100644 lib/mongrel/rails.rb diff --git a/lib/mongrel/rails.rb b/lib/mongrel/rails.rb deleted file mode 100644 index 7f66a5e..0000000 --- a/lib/mongrel/rails.rb +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'mongrel' -require 'cgi' - - -module Mongrel - module Rails - # Implements a handler that can run Rails and serve files out of the - # Rails application's public directory. This lets you run your Rails - # application with Mongrel during development and testing, then use it - # also in production behind a server that's better at serving the - # static files. - # - # The RailsHandler takes a mime_map parameter which is a simple suffix=mimetype - # mapping that it should add to the list of valid mime types. - # - # It also supports page caching directly and will try to resolve a request - # in the following order: - # - # * If the requested exact PATH_INFO exists as a file then serve it. - # * If it exists at PATH_INFO+".html" exists then serve that. - # * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispatch to have Rails go. - # - # This means that if you are using page caching it will actually work with Mongrel - # and you should see a decent speed boost (but not as fast as if you use a static - # server like Apache or Litespeed). - class RailsHandler < Mongrel::HttpHandler - attr_reader :files - attr_reader :guard - @@file_only_methods = ["GET","HEAD"] - - def initialize(dir, mime_map = {}) - @files = Mongrel::DirHandler.new(dir,false) - @guard = Mutex.new - - # Register the requested MIME types - mime_map.each {|k,v| Mongrel::DirHandler::add_mime_type(k,v) } - end - - # Attempts to resolve the request as follows: - # - # * If the requested exact PATH_INFO exists as a file then serve it. - # * If it exists at PATH_INFO+".html" exists then serve that. - # * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispatch to have Rails go. - def process(request, response) - return if response.socket.closed? - - path_info = request.params[Mongrel::Const::PATH_INFO] - rest_operator = request.params[Mongrel::Const::REQUEST_URI][/^#{Regexp.escape path_info}(;[^\?]+)/, 1].to_s - path_info.chomp!("/") - - page_cached = path_info + rest_operator + ActionController::Base.page_cache_extension - get_or_head = @@file_only_methods.include? request.params[Mongrel::Const::REQUEST_METHOD] - - if get_or_head and @files.can_serve(path_info) - # File exists as-is so serve it up - @files.process(request,response) - elsif get_or_head and @files.can_serve(page_cached) - # Possible cached page, serve it up - request.params[Mongrel::Const::PATH_INFO] = page_cached - @files.process(request,response) - else - begin - cgi = Mongrel::CGIWrapper.new(request, response) - cgi.handler = self - # We don't want the output to be really final until we're out of the lock - cgi.default_really_final = false - - @guard.synchronize { - @active_request_path = request.params[Mongrel::Const::PATH_INFO] - Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body) - @active_request_path = nil - } - - # This finalizes the output using the proper HttpResponse way - cgi.out("text/html",true) {""} - rescue Errno::EPIPE - response.socket.close - rescue Object => rails_error - STDERR.puts "#{Time.now}: Error calling Dispatcher.dispatch #{rails_error.inspect}" - STDERR.puts rails_error.backtrace.join("\n") - end - end - end - - # Does the internal reload for Rails. It might work for most cases, but - # sometimes you get exceptions. In that case just do a real restart. - def reload! - begin - @guard.synchronize { - $".replace $orig_dollar_quote - GC.start - Dispatcher.reset_application! - ActionController::Routing::Routes.reload - } - end - end - end - - # Creates Rails specific configuration options for people to use - # instead of the base Configurator. - class RailsConfigurator < Mongrel::Configurator - - # Creates a single rails handler and returns it so you - # can add it to a URI. You can actually attach it to - # as many URIs as you want, but this returns the - # same RailsHandler for each call. - # - # Requires the following options: - # - # * :docroot => The public dir to serve from. - # * :environment => Rails environment to use. - # * :cwd => The change to working directory - # - # And understands the following optional settings: - # - # * :mime => A map of mime types. - # - # Because of how Rails is designed you can only have - # one installed per Ruby interpreter (talk to them - # about thread safety). Because of this the first - # time you call this function it does all the config - # needed to get your Rails working. After that - # it returns the one handler you've configured. - # This lets you attach Rails to any URI(s) you want, - # but it still protects you from threads destroying - # your handler. - def rails(options={}) - - return @rails_handler if @rails_handler - - ops = resolve_defaults(options) - - # fix up some defaults - ops[:environment] ||= "development" - ops[:docroot] ||= "public" - ops[:mime] ||= {} - - $orig_dollar_quote = $".clone - ENV['RAILS_ENV'] = ops[:environment] - env_location = "#{ops[:cwd]}/config/environment" - require env_location - require 'dispatcher' - require 'mongrel/rails' - - ActionController::AbstractRequest.relative_url_root = ops[:prefix] if ops[:prefix] - - @rails_handler = RailsHandler.new(ops[:docroot], ops[:mime]) - end - - # Reloads Rails. This isn't too reliable really, but it - # should work for most minimal reload purposes. The only reliable - # way to reload properly is to stop and then start the process. - def reload! - if not @rails_handler - raise "Rails was not configured. Read the docs for RailsConfigurator." - end - - log "Reloading Rails..." - @rails_handler.reload! - log "Done reloading Rails." - - end - - # Takes the exact same configuration as Mongrel::Configurator (and actually calls that) - # but sets up the additional HUP handler to call reload!. - def setup_rails_signals(options={}) - ops = resolve_defaults(options) - setup_signals(options) - - if RUBY_PLATFORM !~ /djgpp|(cyg|ms|bcc)win|mingw/ - # rails reload - trap("HUP") { log "HUP signal received."; reload! } - - log "Rails signals registered. HUP => reload (without restart). It might not work well." - end - end - end - end -end -- cgit v1.2.3-24-ge0c7 From 0d838c607c0c709e5190b24aff116306f4d02255 Mon Sep 17 00:00:00 2001 From: Ian Ownbey Date: Thu, 4 Dec 2008 15:36:59 -0800 Subject: Removed uri_classifier, now we just use racks --- lib/mongrel.rb | 9 +- lib/mongrel/uri_classifier.rb | 76 ------------ test/unit/test_uriclassifier.rb | 261 ---------------------------------------- 3 files changed, 2 insertions(+), 344 deletions(-) delete mode 100644 lib/mongrel/uri_classifier.rb delete mode 100644 test/unit/test_uriclassifier.rb diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 251e2d4..e0c2b01 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -24,7 +24,6 @@ require 'mongrel/cgi' require 'mongrel/handlers' require 'mongrel/command' require 'mongrel/tcphack' -require 'mongrel/uri_classifier' require 'mongrel/const' require 'mongrel/http_request' require 'mongrel/header_out' @@ -94,7 +93,6 @@ module Mongrel if defined?(Fcntl::FD_CLOEXEC) @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) end - @classifier = URIClassifier.new @host = host @port = port @workers = ThreadGroup.new @@ -134,11 +132,8 @@ module Mongrel raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] - - script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) - - params[Const::PATH_INFO] = path_info - params[Const::SCRIPT_NAME] = script_name + params[Const::PATH_INFO] = params[Const::REQUEST_PATH] + params[Const::SCRIPT_NAME] = Const::SLASH # From http://www.ietf.org/rfc/rfc3875 : # "Script authors should be aware that the REMOTE_ADDR and REMOTE_HOST diff --git a/lib/mongrel/uri_classifier.rb b/lib/mongrel/uri_classifier.rb deleted file mode 100644 index f39ccc9..0000000 --- a/lib/mongrel/uri_classifier.rb +++ /dev/null @@ -1,76 +0,0 @@ - -module Mongrel - class URIClassifier - - class RegistrationError < RuntimeError - end - class UsageError < RuntimeError - end - - attr_reader :handler_map - - # Returns the URIs that have been registered with this classifier so far. - def uris - @handler_map.keys - end - - def initialize - @handler_map = {} - @matcher = // - @root_handler = nil - end - - # Register a handler object at a particular URI. The handler can be whatever - # you want, including an array. It's up to you what to do with it. - # - # Registering a handler is not necessarily threadsafe, so be careful if you go - # mucking around once the server is running. - def register(uri, handler) - raise RegistrationError, "#{uri.inspect} is already registered" if @handler_map[uri] - raise RegistrationError, "URI is empty" if !uri or uri.empty? - raise RegistrationError, "URI must begin with a \"#{Const::SLASH}\"" unless uri[0..0] == Const::SLASH - @handler_map[uri.dup] = handler - rebuild - end - - # Unregister a particular URI and its handler. - def unregister(uri) - handler = @handler_map.delete(uri) - raise RegistrationError, "#{uri.inspect} was not registered" unless handler - rebuild - handler - end - - # Resolve a request URI by finding the best partial match in the registered - # handler URIs. - def resolve(request_uri) - if @root_handler - # Optimization for the pathological case of only one handler on "/"; e.g. Rails - [Const::SLASH, request_uri, @root_handler] - elsif match = @matcher.match(request_uri) - uri = match.to_s - # A root mounted ("/") handler must resolve such that path info matches the original URI. - [uri, (uri == Const::SLASH ? request_uri : match.post_match), @handler_map[uri]] - else - [nil, nil, nil] - end - end - - private - - def rebuild - if @handler_map.size == 1 and @handler_map[Const::SLASH] - @root_handler = @handler_map.values.first - else - @root_handler = nil - routes = @handler_map.keys.sort.sort_by do |uri| - -uri.length - end - @matcher = Regexp.new(routes.map do |uri| - Regexp.new('^' + Regexp.escape(uri)) - end.join('|')) - end - end - - end -end \ No newline at end of file diff --git a/test/unit/test_uriclassifier.rb b/test/unit/test_uriclassifier.rb deleted file mode 100644 index a438065..0000000 --- a/test/unit/test_uriclassifier.rb +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright (c) 2005 Zed A. Shaw -# You can redistribute it and/or modify it under the same terms as Ruby. -# -# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html -# for more information. - -require 'test/test_helper' - -include Mongrel - -class URIClassifierTest < Test::Unit::TestCase - - def test_uri_finding - uri_classifier = URIClassifier.new - uri_classifier.register("/test", 1) - - script_name, path_info, value = uri_classifier.resolve("/test") - assert_equal 1, value - assert_equal "/test", script_name - end - - def test_root_handler_only - uri_classifier = URIClassifier.new - uri_classifier.register("/", 1) - - script_name, path_info, value = uri_classifier.resolve("/test") - assert_equal 1, value - assert_equal "/", script_name - assert_equal "/test", path_info - end - - def test_uri_prefix_ops - test = "/pre/fix/test" - prefix = "/pre" - - uri_classifier = URIClassifier.new - uri_classifier.register(prefix,1) - - script_name, path_info, value = uri_classifier.resolve(prefix) - script_name, path_info, value = uri_classifier.resolve(test) - assert_equal 1, value - assert_equal prefix, script_name - assert_equal test[script_name.length .. -1], path_info - - assert uri_classifier.inspect - assert_equal prefix, uri_classifier.uris[0] - end - - def test_not_finding - test = "/cant/find/me" - uri_classifier = URIClassifier.new - uri_classifier.register(test, 1) - - script_name, path_info, value = uri_classifier.resolve("/nope/not/here") - assert_nil script_name - assert_nil path_info - assert_nil value - end - - def test_exceptions - uri_classifier = URIClassifier.new - - uri_classifier.register("/test", 1) - - failed = false - begin - uri_classifier.register("/test", 1) - rescue => e - failed = true - end - - assert failed - - failed = false - begin - uri_classifier.register("", 1) - rescue => e - failed = true - end - - assert failed - end - - - def test_register_unregister - uri_classifier = URIClassifier.new - - 100.times do - uri_classifier.register("/stuff", 1) - value = uri_classifier.unregister("/stuff") - assert_equal 1, value - end - - uri_classifier.register("/things",1) - script_name, path_info, value = uri_classifier.resolve("/things") - assert_equal 1, value - - uri_classifier.unregister("/things") - script_name, path_info, value = uri_classifier.resolve("/things") - assert_nil value - - end - - - def test_uri_branching - uri_classifier = URIClassifier.new - uri_classifier.register("/test", 1) - uri_classifier.register("/test/this",2) - - script_name, path_info, handler = uri_classifier.resolve("/test") - script_name, path_info, handler = uri_classifier.resolve("/test/that") - assert_equal "/test", script_name, "failed to properly find script off branch portion of uri" - assert_equal "/that", path_info - assert_equal 1, handler, "wrong result for branching uri" - end - - def test_all_prefixing - tests = ["/test","/test/that","/test/this"] - uri = "/test/this/that" - uri_classifier = URIClassifier.new - - current = "" - uri.each_byte do |c| - current << c.chr - uri_classifier.register(current, c) - end - - - # Try to resolve everything with no asserts as a fuzzing - tests.each do |prefix| - current = "" - prefix.each_byte do |c| - current << c.chr - script_name, path_info, handler = uri_classifier.resolve(current) - assert script_name - assert path_info - assert handler - end - end - - # Assert that we find stuff - tests.each do |t| - script_name, path_info, handler = uri_classifier.resolve(t) - assert handler - end - - # Assert we don't find stuff - script_name, path_info, handler = uri_classifier.resolve("chicken") - assert_nil handler - assert_nil script_name - assert_nil path_info - end - - - # Verifies that a root mounted ("/") handler resolves - # such that path info matches the original URI. - # This is needed to accommodate real usage of handlers. - def test_root_mounted - uri_classifier = URIClassifier.new - root = "/" - path = "/this/is/a/test" - - uri_classifier.register(root, 1) - - script_name, path_info, handler = uri_classifier.resolve(root) - assert_equal 1, handler - assert_equal root, path_info - assert_equal root, script_name - - script_name, path_info, handler = uri_classifier.resolve(path) - assert_equal path, path_info - assert_equal root, script_name - assert_equal 1, handler - end - - # Verifies that a root mounted ("/") handler - # is the default point, doesn't matter the order we use - # to register the URIs - def test_classifier_order - tests = ["/before", "/way_past"] - root = "/" - path = "/path" - - uri_classifier = URIClassifier.new - uri_classifier.register(path, 1) - uri_classifier.register(root, 2) - - tests.each do |uri| - script_name, path_info, handler = uri_classifier.resolve(uri) - assert_equal root, script_name, "#{uri} did not resolve to #{root}" - assert_equal uri, path_info - assert_equal 2, handler - end - end - - if ENV['BENCHMARK'] - # Eventually we will have a suite of benchmarks instead of lamely installing a test - - def test_benchmark - - # This URI set should favor a TST. Both versions increase linearly until you hit 14 - # URIs, then the TST flattens out. - @uris = %w( - / - /dag /dig /digbark /dog /dogbark /dog/bark /dug /dugbarking /puppy - /c /cat /cat/tree /cat/tree/mulberry /cats /cot /cot/tree/mulberry /kitty /kittycat -# /eag /eig /eigbark /eog /eogbark /eog/bark /eug /eugbarking /iuppy -# /f /fat /fat/tree /fat/tree/mulberry /fats /fot /fot/tree/mulberry /jitty /jittyfat -# /gag /gig /gigbark /gog /gogbark /gog/bark /gug /gugbarking /kuppy -# /h /hat /hat/tree /hat/tree/mulberry /hats /hot /hot/tree/mulberry /litty /littyhat -# /ceag /ceig /ceigbark /ceog /ceogbark /ceog/cbark /ceug /ceugbarking /ciuppy -# /cf /cfat /cfat/ctree /cfat/ctree/cmulberry /cfats /cfot /cfot/ctree/cmulberry /cjitty /cjittyfat -# /cgag /cgig /cgigbark /cgog /cgogbark /cgog/cbark /cgug /cgugbarking /ckuppy -# /ch /chat /chat/ctree /chat/ctree/cmulberry /chats /chot /chot/ctree/cmulberry /citty /cittyhat - ) - - @requests = %w( - / - /dig - /digging - /dogging - /dogbarking/ - /puppy/barking - /c - /cat - /cat/shrub - /cat/tree - /cat/tree/maple - /cat/tree/mulberry/tree - /cat/tree/oak - /cats/ - /cats/tree - /cod - /zebra - ) - - @classifier = URIClassifier.new - @uris.each do |uri| - @classifier.register(uri, 1) - end - - puts "#{@uris.size} URIs / #{@requests.size * 10000} requests" - - Benchmark.bm do |x| - x.report do - # require 'ruby-prof' - # profile = RubyProf.profile do - 10000.times do - @requests.each do |request| - @classifier.resolve(request) - end - end - # end - # File.open("profile.html", 'w') { |file| RubyProf::GraphHtmlPrinter.new(profile).print(file, 0) } - end - end - end - end - -end - -- cgit v1.2.3-24-ge0c7