about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-03-30 09:31:14 +0000
committerzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-03-30 09:31:14 +0000
commitb87e5685a25acc5b9bb08dcbb811e58718068273 (patch)
tree6e8614e3a668a122094e399800636e5c589b58bb /lib
parent57ff055decb205ef18da1e5a9d6f21372cb9878d (diff)
downloadunicorn-b87e5685a25acc5b9bb08dcbb811e58718068273.tar.gz
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@132 19e92222-5c0b-0410-8929-a290d50e31e9
Diffstat (limited to 'lib')
-rw-r--r--lib/mongrel.rb53
-rw-r--r--lib/mongrel/handlers.rb22
2 files changed, 50 insertions, 25 deletions
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}"