diff --git a/ext/http11/http11_parser.h b/ext/http11/http11_parser.h index 8d49a423..2927459f 100644 --- a/ext/http11/http11_parser.h +++ b/ext/http11/http11_parser.h @@ -35,7 +35,6 @@ int http_parser_finish(http_parser *parser); size_t http_parser_execute(http_parser *parser, const char *data, size_t len ); int http_parser_has_error(http_parser *parser); int http_parser_is_finished(http_parser *parser); -void http_parser_destroy(http_parser *parser); #define http_parser_nread(parser) (parser)->nread diff --git a/lib/mongrel.rb b/lib/mongrel.rb index edf8527c..60932dfb 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -144,6 +144,8 @@ module Mongrel LINE_END="\r\n".freeze REMOTE_ADDR="REMOTE_ADDR".freeze HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze + HTTP_IF_UNMODIFIED_SINCE="HTTP_IF_UNMODIFIED_SINCE".freeze + HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze end @@ -371,7 +373,7 @@ module Mongrel @socket.write(chunk) end end - end + end rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF # ignore these since it means the client closed off early STDERR.puts "Client closed socket requesting file #{req}: #$!" @@ -724,6 +726,7 @@ module Mongrel # or defaults: # # * :handler => Handler to use for this location. + # * :in_front => Rather than appending, it prepends this handler. def uri(location, options={}) ops = resolve_defaults(options) @listener.register(location, ops[:handler], in_front=ops[:in_front]) diff --git a/lib/mongrel/command.rb b/lib/mongrel/command.rb index a500ce05..d538010d 100644 --- a/lib/mongrel/command.rb +++ b/lib/mongrel/command.rb @@ -58,7 +58,11 @@ module Mongrel @opt.parse! argv end - + + def configure + options [] + end + # Returns true/false depending on whether the command is configured properly. def validate return @valid diff --git a/lib/mongrel/handlers.rb b/lib/mongrel/handlers.rb index f9ad95f2..de328b74 100644 --- a/lib/mongrel/handlers.rb +++ b/lib/mongrel/handlers.rb @@ -95,26 +95,26 @@ module Mongrel # Checks if the given path can be served and returns the full path (or nil if not). def can_serve(path_info) - req = File.expand_path(File.join(@path,path_info), @path) + req_path = File.expand_path(File.join(@path,path_info), @path) - if req.index(@path) == 0 and File.exist? req + if req_path.index(@path) == 0 and File.exist? req_path # it exists and it's in the right location - if File.directory? req + if File.directory? req_path # the request is for a directory - index = File.join(req, @index_html) + index = File.join(req_path, @index_html) if File.exist? index # serve the index return index elsif @listing_allowed # serve the directory - req + return req_path else # do not serve anything return nil end else # it's a file and it's there - return req + return req_path end else # does not exist or isn't in the right spot @@ -156,30 +156,51 @@ module Mongrel # 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, response, header_only=false) + def send_file(req_path, request, response, header_only=false) - # first we setup the headers and status then we do a very fast send on the socket directly - response.status = 200 - stat = File.stat(req) - header = response.header + stat = File.stat(req_path) # Set the last modified times as well and etag for all files - header[Const::LAST_MODIFIED] = stat.mtime.httpdate + mtime = stat.mtime # Calculated the same as apache, not sure how well the works on win32 - header[Const::ETAG] = Const::ETAG_FORMAT % [stat.mtime.to_i, stat.size, stat.ino] - - # set the mime type from our map based on the ending - dot_at = req.rindex(".") - if dot_at - header[Const::CONTENT_TYPE] = MIME_TYPES[req[dot_at .. -1]] || @default_content_type + etag = Const::ETAG_FORMAT % [mtime.to_i, stat.size, stat.ino] + + unmodified_since = request.params[Const::HTTP_IF_UNMODIFIED_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 unmodified_since && !last_response_time = Time.httpdate(unmodified_since) rescue nil : false + when unmodified_since && last_response_time > Time.now : false + when unmodified_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 unmodified_since || none_match # validation successful if we get this far and at least one of the header exists end - # send a status with out content length - response.send_status(stat.size) - response.send_header + 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 + response.status = 200 + header = response.header + header[Const::LAST_MODIFIED] = mtime.httpdate + header[Const::ETAG] = etag - if not header_only - response.send_file(req) + # 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 + 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) + end end end @@ -187,25 +208,25 @@ module Mongrel # if allowed (based on the listing_allowed paramter to the constructor). def process(request, response) req_method = request.params[Const::REQUEST_METHOD] || Const::GET - req = can_serve request.params[Const::PATH_INFO] - if not req + 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 - send_dir_listing(request.params[Const::REQUEST_URI],req, response) + if File.directory? req_path + send_dir_listing(request.params[Const::REQUEST_URI], req_path, response) elsif req_method == Const::HEAD - send_file(req, response, true) - elsif req_method == Const::GET - send_file(req, response, false) - else - response.start(403) {|head,out| out.write(ONLY_HEAD_GET) } + 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 accessing file #{req}: #{details}" + STDERR.puts "Error accessing file #{req_path}: #{details}" STDERR.puts details.backtrace.join("\n") end end diff --git a/test/test_ws.rb b/test/test_ws.rb index dd7fa773..c3bf8a7f 100644 --- a/test/test_ws.rb +++ b/test/test_ws.rb @@ -20,13 +20,13 @@ class WSTest < Test::Unit::TestCase def test_simple_server h = HttpServer.new("0.0.0.0", 9998) tester = TestHandler.new - h.register("/test", tester) + h.register("/test", tester) h.run sleep(1) res = Net::HTTP.get(URI.parse('http://localhost:9998/test')) assert res != nil, "Didn't get a response" - assert tester.ran_test, "Handler didn't really run" + assert tester.ran_test, "Handler didn't really run" end end