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

Final tweaks to speed up the file serving a bit using sendfile and a modified file handler.

git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@124 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
zedshaw 2006-03-27 06:10:07 +00:00
parent 4d9966ee00
commit 110e92752b
4 changed files with 253 additions and 216 deletions

View file

@ -4,6 +4,7 @@
# This is where Win32::Daemon resides.
###############################################
require 'rubygems'
require 'mongrel'
require 'mongrel/rails'
require 'optparse'
require 'win32/service'
@ -78,6 +79,7 @@ class MongrelRails
@server.register("/", @rails)
dbg "delayed_initialize left"
end
def load_mime_map
@ -104,11 +106,12 @@ class MongrelRails
Dir.chdir(@rails_root)
ENV['RAILS_ENV'] = @environment
require File.join(@rails_root, 'config/environment')
require 'config/environment'
# configure the rails handler
rails = RailsHandler.new(@docroot, load_mime_map)
rails = Mongrel::Rails::RailsHandler.new(@docroot, load_mime_map)
dbg "configure_rails left"
@ -116,6 +119,7 @@ class MongrelRails
end
def start_serve
begin
dbg "start_serve entered"
@runner = Thread.new do
@ -134,6 +138,10 @@ class MongrelRails
@runner.run
dbg "start_serve left"
rescue
dbg "ERROR: #$!\r\n"
dbg $!.backtrace.join("\r\n")
end
end
def stop_serve
@ -193,6 +201,7 @@ class RailsDaemon < Win32::Daemon
end
begin
if ARGV[0] == 'service'
ARGV.shift
@ -280,12 +289,19 @@ elsif ARGV[0] == 'debug'
begin
sleep
rescue Interrupt
dbg "ERROR: #$!\r\n"
dbg $!.backtrace.join("\r\n")
puts "graceful shutdown?"
end
begin
rails.stop_serve
rescue
dbg "ERROR: #$!\r\n"
dbg $!.backtrace.join("\r\n")
end
end
rescue
dbg "ERROR: #$!\r\n"
dbg $!.backtrace.join("\r\n")
end

View file

@ -200,6 +200,7 @@ module Mongrel
@out.write(value)
@out.write("\r\n")
end
end
# Writes and controls your response to the client using the HTTP/1.1 specification.
@ -306,6 +307,10 @@ module Mongrel
end
end
def write(data)
@socket.write(data)
end
# This takes whatever has been done to header and body and then writes it in the
# proper format to make an HTTP/1.1 response.
def finished
@ -317,6 +322,7 @@ module Mongrel
def done
(@status_sent and @header_sent and @body_sent)
end
end

View file

@ -1,3 +1,11 @@
require 'rubygems'
begin
require 'sendfile'
$mongrel_has_sendfile = true
STDERR.puts "** You have sendfile installed, will use that to serve files."
rescue Object
$mongrel_has_sendfile = false
end
module Mongrel
@ -150,19 +158,28 @@ 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)
response.start(200) do |head,out|
# first we setup the headers and status then we do a very fast send on the socket directly
response.status = 200
# set the mime type from our map based on the ending
dot_at = req.rindex(".")
if dot_at
ext = req[dot_at .. -1]
if MIME_TYPES[ext]
head['Content-Type'] = MIME_TYPES[ext]
response.header['Content-Type'] = MIME_TYPES[ext]
end
end
open(req, "rb") do |f|
out.write(f.read)
end
response.header['Content-Length'] = File.size(req)
response.send_status
response.send_header
if $mongrel_has_sendfile
File.open(req, "rb") { |f| response.socket.sendfile(f) }
else
File.open(req, "rb") { |f| response.socket.write(f.read) }
end
end
@ -184,11 +201,8 @@ module Mongrel
send_file(req, response)
end
rescue => details
response.reset
response.start(403) do |head,out|
out << "Error accessing file: #{details}"
out << details.backtrace.join("\n")
end
STDERR.puts "Error accessing file: #{details}"
STDERR.puts details.backtrace.join("\n")
end
end
end

View file

@ -4,6 +4,92 @@ 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.dispath 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 lighttpd).
#
# An additional feature you can use is
class RailsHandler < Mongrel::HttpHandler
attr_reader :files
attr_reader :guard
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.dispath to have Rails go.
def process(request, response)
return if response.socket.closed?
path_info = request.params[Mongrel::Const::PATH_INFO]
page_cached = request.params[Mongrel::Const::PATH_INFO] + ".html"
if @files.can_serve(path_info)
# File exists as-is so serve it up
@files.process(request,response)
elsif @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
@guard.synchronize do
# Rails is not thread safe so must be run entirely within synchronize
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body)
end
# This finalizes the output using the proper HttpResponse way
cgi.out {""}
rescue Errno::EPIPE
# ignored
rescue Object => rails_error
STDERR.puts "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!
@guard.synchronize do
$".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
@ -86,91 +172,6 @@ module Mongrel
log "WARNING: Rails does not support signals on Win32."
end
end
# 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.dispath 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 lighttpd).
#
# An additional feature you can use is
class RailsHandler < Mongrel::HttpHandler
attr_reader :files
attr_reader :guard
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.dispath to have Rails go.
def process(request, response)
return if response.socket.closed?
path_info = request.params[Mongrel::Const::PATH_INFO]
page_cached = request.params[Mongrel::Const::PATH_INFO] + ".html"
if @files.can_serve(path_info)
# File exists as-is so serve it up
@files.process(request,response)
elsif @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
@guard.synchronize do
# Rails is not thread safe so must be run entirely within synchronize
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body)
end
# This finalizes the output using the proper HttpResponse way
cgi.out {""}
rescue Errno::EPIPE
# ignored
rescue Object => rails_error
log "Error calling Dispatcher.dispatch #{rails_error.inspect}"
log 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!
@guard.synchronize do
$".replace $orig_dollar_quote
GC.start
Dispatcher.reset_application!
ActionController::Routing::Routes.reload
end
end
end
end
end
end