1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Implements minor little tweaks to reduce String cycling. Adds ETag and Last-Modified headers to DirHandler so static files are cached by the browser.

git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@132 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
zedshaw 2006-03-30 09:31:14 +00:00
parent 57ff055dec
commit b87e5685a2
3 changed files with 52 additions and 27 deletions

View file

@ -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()
{

View file

@ -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

View file

@ -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}"