diff options
author | zedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9> | 2006-03-30 09:31:14 +0000 |
---|---|---|
committer | zedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9> | 2006-03-30 09:31:14 +0000 |
commit | b87e5685a25acc5b9bb08dcbb811e58718068273 (patch) | |
tree | 6e8614e3a668a122094e399800636e5c589b58bb | |
parent | 57ff055decb205ef18da1e5a9d6f21372cb9878d (diff) | |
download | unicorn-b87e5685a25acc5b9bb08dcbb811e58718068273.tar.gz |
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@132 19e92222-5c0b-0410-8929-a290d50e31e9
-rw-r--r-- | ext/http11/http11.c | 4 | ||||
-rw-r--r-- | lib/mongrel.rb | 53 | ||||
-rw-r--r-- | lib/mongrel/handlers.rb | 22 |
3 files changed, 52 insertions, 27 deletions
diff --git a/ext/http11/http11.c b/ext/http11/http11.c index 7062a02..b04012e 100644 --- a/ext/http11/http11.c +++ b/ext/http11/http11.c @@ -33,6 +33,8 @@ static VALUE global_mongrel_version; static VALUE global_server_software; static VALUE global_port_80; +#define DEF_GLOBAL(name, val) global_##name = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##name); + void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen) { char *ch, *end; @@ -451,8 +453,6 @@ VALUE URIClassifier_resolve(VALUE self, VALUE uri) return result; } -#define DEF_GLOBAL(name, val) global_##name = rb_str_new2(val); rb_global_variable(&global_##name) - void Init_http11() { diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 68e878a..34f610b 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -88,34 +88,52 @@ 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 + # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this. - PATH_INFO="PATH_INFO" + PATH_INFO="PATH_INFO".freeze # This is the intial part that your handler is identified as by URIClassifier. - SCRIPT_NAME="SCRIPT_NAME" + SCRIPT_NAME="SCRIPT_NAME".freeze # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME. - REQUEST_URI='REQUEST_URI' + REQUEST_URI='REQUEST_URI'.freeze - MONGREL_VERSION="0.3.12" + MONGREL_VERSION="0.3.12".freeze # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff. - ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND" + ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze - CONTENT_LENGTH="CONTENT_LENGTH" + CONTENT_LENGTH="CONTENT_LENGTH".freeze # A common header for indicating the server is too busy. Not used yet. - ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY" + ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze # The basic max request size we'll try to read. CHUNK_SIZE=(16 * 1024) + # Format to generate a correct RFC 1123 date. rdoc for Time is wrong, there is no httpdate function. + RFC_1123_DATE_FORMAT="%a, %d %B %Y %H:%M:%S GMT".freeze + + # A frozen format for this is about 15% faster + STATUS_FORMAT = "HTTP/1.1 %d %s\r\nContent-Length: %d\r\nConnection: close\r\n".freeze + CONTENT_TYPE = "Content-Type".freeze + LAST_MODIFIED = "Last-Modified".freeze + ETAG = "ETag".freeze + SLASH = "/".freeze + REQUEST_METHOD="REQUEST_METHOD".freeze + GET="GET".freeze + HEAD="HEAD".freeze + # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32) + ETAG_FORMAT="\"%x-%x-%x\"".freeze + HEADER_FORMAT="%s: %s\r\n".freeze + LINE_END="\r\n".freeze end # 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 HttpReqeust is a + # *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). # @@ -195,10 +213,7 @@ module Mongrel # Simply writes "#{key}: #{value}" to an output buffer. def[]=(key,value) - @out.write(key) - @out.write(": ") - @out.write(value) - @out.write("\r\n") + @out.write(Const::HEADER_FORMAT % [key, value]) end end @@ -247,6 +262,7 @@ module Mongrel @body = StringIO.new @status = 404 @header = HeaderOut.new(StringIO.new) + @header[Const::DATE] = HttpServer.httpdate(Time.now) @filter = filter @body_sent = false @header_sent = false @@ -284,8 +300,7 @@ module Mongrel def send_status(content_length=nil) if not @status_sent content_length ||= @body.length - status = "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status]}\r\nContent-Length: #{content_length}\r\nConnection: close\r\n" - @socket.write(status) + @socket.write(Const::STATUS_FORMAT % [status, HTTP_STATUS_CODES[@status], content_length]) @status_sent = true end end @@ -293,8 +308,7 @@ module Mongrel def send_header if not @header_sent @header.out.rewind - @socket.write(@header.out.read) - @socket.write("\r\n") + @socket.write(@header.out.read + Const::LINE_END) @header_sent = true end end @@ -502,7 +516,7 @@ module Mongrel if not handlers @classifier.register(uri, [handler]) else - if path_info.length == 0 or (script_name == "/" and path_info == "/") + if path_info.length == 0 or (script_name == Const::SLASH and path_info == Const::SLASH) handlers << handler else @classifier.register(uri, [handler]) @@ -527,6 +541,11 @@ module Mongrel stopper.priority = 10 end + # Given the a time object it converts it to GMT and applies the RFC1123 format to it. + def HttpServer.httpdate(date) + date.gmtime.strftime(Const::RFC_1123_DATE_FORMAT) + end + end diff --git a/lib/mongrel/handlers.rb b/lib/mongrel/handlers.rb index 1b2a7d4..b308194 100644 --- a/lib/mongrel/handlers.rb +++ b/lib/mongrel/handlers.rb @@ -84,6 +84,7 @@ module Mongrel ".txt" => "text/plain" } + ONLY_HEAD_GET="Only HEAD and GET allowed.".freeze attr_reader :path @@ -134,7 +135,7 @@ module Mongrel if @listing_allowed response.start(200) do |head,out| - head['Content-Type'] = "text/html" + head[Const::CONTENT_TYPE] = "text/html" out << "<html><head><title>Directory Listing</title></head><body>" Dir.entries(dir).each do |child| next if child == "." @@ -167,7 +168,12 @@ module Mongrel if dot_at ext = req[dot_at .. -1] if MIME_TYPES[ext] - response.header['Content-Type'] = MIME_TYPES[ext] + stat = File.stat(req) + response.header[Const::CONTENT_TYPE] = MIME_TYPES[ext] + # TODO: Confirm this works for rfc 1123 + response.header[Const::LAST_MODIFIED] = HttpServer.httpdate(stat.mtime) + # TODO that this is a valid way to calculate an etag + response.header[Const::ETAG] = Const::ETAG_FORMAT % [stat.mtime.to_i, stat.size, stat.ino] end end @@ -193,8 +199,8 @@ module Mongrel # Process the request to either serve a file or a directory listing # if allowed (based on the listing_allowed paramter to the constructor). def process(request, response) - req_method = request.params['REQUEST_METHOD'] || "GET" - req = can_serve request.params['PATH_INFO'] + req_method = request.params[Const::REQUEST_METHOD] || Const::GET + req = can_serve request.params[Const::PATH_INFO] if not req # not found, return a 404 response.start(404) do |head,out| @@ -203,13 +209,13 @@ module Mongrel else begin if File.directory? req - send_dir_listing(request.params["REQUEST_URI"],req, response) - elsif req_method == "HEAD" + send_dir_listing(request.params[Const::REQUEST_URI],req, response) + elsif req_method == Const::HEAD send_file(req, response, true) - elsif req_method == "GET" + elsif req_method == Const::GET send_file(req, response, false) else - response.start(403) {|head,out| out.write("Only HEAD and GET allowed.") } + response.start(403) {|head,out| out.write(ONLY_HEAD_GET) } end rescue => details STDERR.puts "Error accessing file #{req}: #{details}" |