mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Prune out custom handlers, rack rules.
This commit is contained in:
parent
190a81c55a
commit
000deba3c1
15 changed files with 77 additions and 1215 deletions
|
@ -77,9 +77,9 @@ module Puma
|
|||
def run
|
||||
if @generate
|
||||
@generate = File.expand_path(@generate)
|
||||
STDERR.puts "** Writing config to \"#@generate\"."
|
||||
@stderr.puts "** Writing config to \"#@generate\"."
|
||||
open(@generate, "w") {|f| f.write(settings.to_yaml) }
|
||||
STDERR.puts "** Finished. Run \"puma_rails start -C #@generate\" to use the config file."
|
||||
@stderr.puts "** Finished. Run \"puma_rails start -C #@generate\" to use the config file."
|
||||
exit 0
|
||||
end
|
||||
|
||||
|
@ -147,7 +147,7 @@ module Puma
|
|||
if config.defaults[:daemon]
|
||||
system cmd
|
||||
else
|
||||
STDERR.puts "Can't restart unless in daemon mode."
|
||||
@stderr.puts "Can't restart unless in daemon mode."
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
|
@ -161,7 +161,7 @@ module Puma
|
|||
begin
|
||||
settings = YAML.load_file(@config_file)
|
||||
ensure
|
||||
STDERR.puts "** Loading settings from #{@config_file} (they override command line)." unless @daemon || settings[:daemon]
|
||||
@stderr.puts "** Loading settings from #{@config_file} (they override command line)." unless @daemon || settings[:daemon]
|
||||
end
|
||||
|
||||
settings[:includes] ||= ["puma"]
|
||||
|
@ -276,9 +276,7 @@ module Puma
|
|||
end
|
||||
|
||||
|
||||
GemPlugin::Manager.instance.load "puma" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
|
||||
GemPlugin::Manager.instance.load "puma" => GemPlugin::INCLUDE,
|
||||
"rails" => GemPlugin::EXCLUDE
|
||||
|
||||
|
||||
if not Puma::Command::Registry.instance.run ARGV
|
||||
exit 1
|
||||
end
|
||||
exit 1 unless Puma::Command::Registry.instance.run(ARGV)
|
|
@ -22,7 +22,6 @@ require 'puma/gems'
|
|||
require 'thread'
|
||||
|
||||
# Ruby Puma
|
||||
require 'puma/handlers'
|
||||
require 'puma/command'
|
||||
require 'puma/tcphack'
|
||||
require 'puma/configurator'
|
||||
|
|
|
@ -42,6 +42,9 @@ module Puma
|
|||
# The call is destructive on argv since it uses the OptionParser#parse! function.
|
||||
def initialize(options={})
|
||||
argv = options[:argv] || []
|
||||
@stderr = options[:stderr] || $stderr
|
||||
@stdout = options[:stdout] || $stderr
|
||||
|
||||
@opt = OptionParser.new
|
||||
@opt.banner = Puma::Command::BANNER
|
||||
@valid = true
|
||||
|
@ -54,13 +57,13 @@ module Puma
|
|||
# I need to add my own -h definition to prevent the -h by default from exiting.
|
||||
@opt.on_tail("-h", "--help", "Show this message") do
|
||||
@done_validating = true
|
||||
puts @opt
|
||||
@stdout.puts @opt
|
||||
end
|
||||
|
||||
# I need to add my own -v definition to prevent the -v from exiting by default as well.
|
||||
@opt.on_tail("--version", "Show version") do
|
||||
@done_validating = true
|
||||
puts "Version #{Puma::Const::PUMA_VERSION}"
|
||||
@stdout.puts "Version #{Puma::Const::PUMA_VERSION}"
|
||||
end
|
||||
|
||||
@opt.parse! argv
|
||||
|
@ -134,14 +137,21 @@ module Puma
|
|||
|
||||
# Just a simple method to display failure until something better is developed.
|
||||
def failure(message)
|
||||
STDERR.puts "!!! #{message}"
|
||||
@stderr.puts "!!! #{message}"
|
||||
end
|
||||
end
|
||||
|
||||
# A Singleton class that manages all of the available commands
|
||||
# and handles running them.
|
||||
class Registry
|
||||
include Singleton
|
||||
def self.instance
|
||||
@global ||= new
|
||||
end
|
||||
|
||||
def initialize(stdout=STDOUT, stderr=STDERR)
|
||||
@stdout = stdout
|
||||
@stderr = stderr
|
||||
end
|
||||
|
||||
# Builds a list of possible commands from the Command derivates list
|
||||
def commands
|
||||
|
@ -152,18 +162,17 @@ module Puma
|
|||
|
||||
# Prints a list of available commands.
|
||||
def print_command_list
|
||||
puts "#{Puma::Command::BANNER}\nAvailable commands are:\n\n"
|
||||
@stdout.puts "#{Puma::Command::BANNER}\nAvailable commands are:\n\n"
|
||||
|
||||
self.commands.each do |name|
|
||||
if /puma::/ =~ name
|
||||
name = name[9 .. -1]
|
||||
end
|
||||
|
||||
puts " - #{name[1 .. -1]}\n"
|
||||
@stdout.puts " - #{name[1 .. -1]}\n"
|
||||
end
|
||||
|
||||
puts "\nEach command takes -h as an option to get help."
|
||||
|
||||
@stdout.puts "\nEach command takes -h as an option to get help."
|
||||
end
|
||||
|
||||
|
||||
|
@ -177,7 +186,7 @@ module Puma
|
|||
print_command_list
|
||||
return true
|
||||
elsif cmd_name == "--version"
|
||||
puts "Puma Web Server #{Puma::Const::PUMA_VERSION}"
|
||||
@stdout.puts "Puma Web Server #{Puma::Const::PUMA_VERSION}"
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -187,14 +196,21 @@ module Puma
|
|||
cmd_name = "puma::" + cmd_name
|
||||
end
|
||||
|
||||
command = GemPlugin::Manager.instance.create("/commands/#{cmd_name}", :argv => args)
|
||||
opts = {
|
||||
:argv => args,
|
||||
:stderr => @stderr,
|
||||
:stdout => @stdout
|
||||
}
|
||||
|
||||
command = GemPlugin::Manager.instance.create("/commands/#{cmd_name}", opts)
|
||||
|
||||
rescue OptionParser::InvalidOption
|
||||
STDERR.puts "#$! for command '#{cmd_name}'"
|
||||
STDERR.puts "Try #{cmd_name} -h to get help."
|
||||
@stderr.puts "#$! for command '#{cmd_name}'"
|
||||
@stderr.puts "Try #{cmd_name} -h to get help."
|
||||
return false
|
||||
rescue
|
||||
STDERR.puts "ERROR RUNNING '#{cmd_name}': #$!"
|
||||
STDERR.puts "Use help command to get help"
|
||||
@stderr.puts "ERROR RUNNING '#{cmd_name}': #$!"
|
||||
@stderr.puts "Use help command to get help"
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -203,7 +219,7 @@ module Puma
|
|||
# needed so the command is already valid so we can skip it.
|
||||
if not command.done_validating
|
||||
if not command.validate
|
||||
STDERR.puts "#{cmd_name} reported an error. Use puma_rails #{cmd_name} -h to get help."
|
||||
@stderr.puts "#{cmd_name} reported an error. Use puma_rails #{cmd_name} -h to get help."
|
||||
return false
|
||||
else
|
||||
command.run
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
|
||||
require 'logger'
|
||||
require 'set'
|
||||
require 'socket'
|
||||
require 'fileutils'
|
||||
|
||||
module PumaDbg
|
||||
SETTINGS = { :tracing => {}}
|
||||
LOGGING = { }
|
||||
|
||||
def PumaDbg::configure(log_dir = File.join("log","puma_debug"))
|
||||
FileUtils.mkdir_p(log_dir)
|
||||
@log_dir = log_dir
|
||||
$objects_out=open(File.join("log","puma_debug","objects.log"),"w")
|
||||
$objects_out.puts "run,classname,last,count,delta,lenmean,lensd,lenmax"
|
||||
$objects_out.sync = true
|
||||
$last_stat = nil
|
||||
$run_count = 0
|
||||
end
|
||||
|
||||
|
||||
def PumaDbg::trace(target, message)
|
||||
if SETTINGS[:tracing][target] and LOGGING[target]
|
||||
LOGGING[target].log(Logger::DEBUG, message)
|
||||
end
|
||||
end
|
||||
|
||||
def PumaDbg::begin_trace(target)
|
||||
SETTINGS[:tracing][target] = true
|
||||
if not LOGGING[target]
|
||||
LOGGING[target] = Logger.new(File.join(@log_dir, "#{target.to_s}.log"))
|
||||
end
|
||||
PumaDbg::trace(target, "TRACING ON #{Time.now}")
|
||||
end
|
||||
|
||||
def PumaDbg::end_trace(target)
|
||||
SETTINGS[:tracing][target] = false
|
||||
PumaDbg::trace(target, "TRACING OFF #{Time.now}")
|
||||
LOGGING[target].close
|
||||
LOGGING[target] = nil
|
||||
end
|
||||
|
||||
def PumaDbg::tracing?(target)
|
||||
SETTINGS[:tracing][target]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
$open_files = {}
|
||||
|
||||
class IO
|
||||
alias_method :orig_open, :open
|
||||
alias_method :orig_close, :close
|
||||
|
||||
def open(*arg, &blk)
|
||||
$open_files[self] = args.inspect
|
||||
orig_open(*arg,&blk)
|
||||
end
|
||||
|
||||
def close(*arg,&blk)
|
||||
$open_files.delete self
|
||||
orig_close(*arg,&blk)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module Kernel
|
||||
alias_method :orig_open, :open
|
||||
|
||||
def open(*arg, &blk)
|
||||
$open_files[self] = arg[0]
|
||||
orig_open(*arg,&blk)
|
||||
end
|
||||
|
||||
def log_open_files
|
||||
open_counts = {}
|
||||
$open_files.each do |f,args|
|
||||
open_counts[args] ||= 0
|
||||
open_counts[args] += 1
|
||||
end
|
||||
PumaDbg::trace(:files, open_counts.to_yaml)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
module RequestLog
|
||||
|
||||
# Just logs whatever requests it gets to STDERR (which ends up in the puma
|
||||
# log when daemonized).
|
||||
class Access < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request,response)
|
||||
p = request.params
|
||||
STDERR.puts "#{p['REMOTE_ADDR']} - [#{Time.now.httpdate}] \"#{p['REQUEST_METHOD']} #{p["REQUEST_URI"]} HTTP/1.1\""
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Files < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request, response)
|
||||
PumaDbg::trace(:files, "#{Time.now} FILES OPEN BEFORE REQUEST #{request.params['PATH_INFO']}")
|
||||
log_open_files
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# stolen from Robert Klemme
|
||||
class Objects < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request,response)
|
||||
begin
|
||||
stats = Hash.new(0)
|
||||
lengths = {}
|
||||
begin
|
||||
ObjectSpace.each_object do |o|
|
||||
begin
|
||||
if o.respond_to? :length
|
||||
len = o.length
|
||||
lengths[o.class] ||= Puma::Stats.new(o.class)
|
||||
lengths[o.class].sample(len)
|
||||
end
|
||||
rescue Object
|
||||
end
|
||||
|
||||
stats[o.class] += 1
|
||||
end
|
||||
rescue Object # Ignore since ObjectSpace might not be loaded on JRuby
|
||||
end
|
||||
|
||||
stats.sort {|(k1,v1),(k2,v2)| v2 <=> v1}.each do |k,v|
|
||||
if $last_stat
|
||||
delta = v - $last_stat[k]
|
||||
if v > 10 and delta != 0
|
||||
if lengths[k]
|
||||
$objects_out.printf "%d,%s,%d,%d,%d,%f,%f,%f\n", $run_count, k, $last_stat[k], v, delta,lengths[k].mean,lengths[k].sd,lengths[k].max
|
||||
else
|
||||
$objects_out.printf "%d,%s,%d,%d,%d,,,\n", $run_count, k, $last_stat[k], v, delta
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$run_count += 1
|
||||
$last_stat = stats
|
||||
rescue Object
|
||||
STDERR.puts "object.log ERROR: #$!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Params < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request, response)
|
||||
PumaDbg::trace(:rails, "#{Time.now} REQUEST #{request.params['PATH_INFO']}")
|
||||
PumaDbg::trace(:rails, request.params.to_yaml)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Threads < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request, response)
|
||||
PumaDbg::trace(:threads, "#{Time.now} REQUEST #{request.params['PATH_INFO']}")
|
||||
begin
|
||||
ObjectSpace.each_object do |obj|
|
||||
begin
|
||||
if obj.class == Puma::HttpServer
|
||||
worker_list = obj.workers.list
|
||||
|
||||
if worker_list.length > 0
|
||||
keys = "-----\n\tKEYS:"
|
||||
worker_list.each {|t| keys << "\n\t\t-- #{t}: #{t.keys.inspect}" }
|
||||
end
|
||||
|
||||
PumaDbg::trace(:threads, "#{obj.host}:#{obj.port} -- THREADS: #{worker_list.length} #{keys}")
|
||||
end
|
||||
rescue Object # Ignore since obj.class can sometimes take parameters
|
||||
end
|
||||
end
|
||||
rescue Object # Ignore since ObjectSpace might not be loaded on JRuby
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
END {
|
||||
PumaDbg::trace(:files, "FILES OPEN AT EXIT")
|
||||
log_open_files
|
||||
}
|
|
@ -1,465 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
|
||||
require 'puma/stats'
|
||||
require 'zlib'
|
||||
require 'yaml'
|
||||
|
||||
module Puma
|
||||
|
||||
# You implement your application handler with this. It's very light giving
|
||||
# just the minimum necessary for you to handle a request and shoot back
|
||||
# a response. Look at the HttpRequest and HttpResponse objects for how
|
||||
# to use them.
|
||||
#
|
||||
# This is used for very simple handlers that don't require much to operate.
|
||||
# More extensive plugins or those you intend to distribute as GemPlugins
|
||||
# should be implemented using the HttpHandlerPlugin mixin.
|
||||
#
|
||||
class HttpHandler
|
||||
attr_reader :request_notify
|
||||
attr_accessor :listener
|
||||
|
||||
# This will be called by Puma if HttpHandler.request_notify set to *true*.
|
||||
# You only get the parameters for the request, with the idea that you'd "bound"
|
||||
# the beginning of the request processing and the first call to process.
|
||||
def request_begins(params)
|
||||
end
|
||||
|
||||
# Called by Puma for each IO chunk that is received on the request socket
|
||||
# from the client, allowing you to track the progress of the IO and monitor
|
||||
# the input. This will be called by Puma only if HttpHandler.request_notify
|
||||
# set to *true*.
|
||||
def request_progress(params, clen, total)
|
||||
end
|
||||
|
||||
def process(request, response)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# This is used when your handler is implemented as a GemPlugin.
|
||||
# The plugin always takes an options hash which you can modify
|
||||
# and then access later. They are stored by default for
|
||||
# the process method later.
|
||||
module HttpHandlerPlugin
|
||||
attr_reader :options
|
||||
attr_reader :request_notify
|
||||
attr_accessor :listener
|
||||
|
||||
def request_begins(params)
|
||||
end
|
||||
|
||||
def request_progress(params, clen, total)
|
||||
end
|
||||
|
||||
def initialize(options={})
|
||||
@options = options
|
||||
@header_only = false
|
||||
end
|
||||
|
||||
def process(request, response)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# The server normally returns a 404 response if an unknown URI is requested, but it
|
||||
# also returns a lame empty message. This lets you do a 404 response
|
||||
# with a custom message for special URIs.
|
||||
#
|
||||
class Error404Handler < HttpHandler
|
||||
|
||||
# Sets the message to return. This is constructed once for the handler
|
||||
# so it's pretty efficient.
|
||||
def initialize(msg)
|
||||
@response = Const::ERROR_404_RESPONSE + msg
|
||||
end
|
||||
|
||||
# Just kicks back the standard 404 response with your special message.
|
||||
def process(request, response)
|
||||
response.socket.write(@response)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# Serves the contents of a directory. You give it the path to the root
|
||||
# where the files are located, and it tries to find the files based on
|
||||
# the PATH_INFO inside the directory. If the requested path is a
|
||||
# directory then it returns a simple directory listing.
|
||||
#
|
||||
# It does a simple protection against going outside it's root path by
|
||||
# converting all paths to an absolute expanded path, and then making
|
||||
# sure that the final expanded path includes the root path. If it doesn't
|
||||
# than it simply gives a 404.
|
||||
#
|
||||
# If you pass nil as the root path, it will not check any locations or
|
||||
# expand any paths. This lets you serve files from multiple drives
|
||||
# on win32. It should probably not be used in a public-facing way
|
||||
# without additional checks.
|
||||
#
|
||||
# The default content type is "text/plain; charset=ISO-8859-1" but you
|
||||
# can change it anything you want using the DirHandler.default_content_type
|
||||
# attribute.
|
||||
#
|
||||
class DirHandler < HttpHandler
|
||||
attr_accessor :default_content_type
|
||||
attr_reader :path
|
||||
|
||||
MIME_TYPES_FILE = "mime_types.yml"
|
||||
MIME_TYPES = YAML.load_file(File.join(File.dirname(__FILE__), MIME_TYPES_FILE))
|
||||
|
||||
ONLY_HEAD_GET="Only HEAD and GET allowed.".freeze
|
||||
|
||||
# You give it the path to the directory root and and optional listing_allowed and index_html
|
||||
def initialize(path, listing_allowed=true, index_html="index.html")
|
||||
@path = (File.expand_path(path) if path)
|
||||
@listing_allowed = listing_allowed
|
||||
@index_html = index_html
|
||||
@default_content_type = "application/octet-stream".freeze
|
||||
end
|
||||
|
||||
# Checks if the given path can be served and returns the full path (or nil if not).
|
||||
def can_serve(path_info)
|
||||
|
||||
req_path = HttpRequest.unescape(path_info)
|
||||
# Add the drive letter or root path
|
||||
req_path = File.join(@path, req_path) if @path
|
||||
req_path = File.expand_path req_path
|
||||
|
||||
if File.exist? req_path and (!@path or req_path.index(@path) == 0)
|
||||
# It exists and it's in the right location
|
||||
if File.directory? req_path
|
||||
# The request is for a directory
|
||||
index = File.join(req_path, @index_html)
|
||||
if File.exist? index
|
||||
# Serve the index
|
||||
return index
|
||||
elsif @listing_allowed
|
||||
# Serve the directory
|
||||
return req_path
|
||||
else
|
||||
# Do not serve anything
|
||||
return nil
|
||||
end
|
||||
else
|
||||
# It's a file and it's there
|
||||
return req_path
|
||||
end
|
||||
else
|
||||
# does not exist or isn't in the right spot
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns a simplistic directory listing if they're enabled, otherwise a 403.
|
||||
# Base is the base URI from the REQUEST_URI, dir is the directory to serve
|
||||
# on the file system (comes from can_serve()), and response is the HttpResponse
|
||||
# object to send the results on.
|
||||
def send_dir_listing(base, dir, response)
|
||||
# take off any trailing / so the links come out right
|
||||
base = HttpRequest.unescape(base)
|
||||
base.chop! if base[-1] == "/"[-1]
|
||||
|
||||
if @listing_allowed
|
||||
response.start(200) do |head,out|
|
||||
head[Const::CONTENT_TYPE] = "text/html"
|
||||
out << "<html><head><title>Directory Listing</title></head><body>"
|
||||
Dir.entries(dir).each do |child|
|
||||
next if child == "."
|
||||
out << "<a href=\"#{base}/#{ HttpRequest.escape(child)}\">"
|
||||
out << (child == ".." ? "Up to parent.." : child)
|
||||
out << "</a><br/>"
|
||||
end
|
||||
out << "</body></html>"
|
||||
end
|
||||
else
|
||||
response.start(403) do |head,out|
|
||||
out.write("Directory listings not allowed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# 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_path, request, response, header_only=false)
|
||||
|
||||
stat = File.stat(req_path)
|
||||
|
||||
# Set the last modified times as well and etag for all files
|
||||
mtime = stat.mtime
|
||||
# Calculated the same as apache, not sure how well the works on win32
|
||||
etag = Const::ETAG_FORMAT % [mtime.to_i, stat.size, stat.ino]
|
||||
|
||||
modified_since = request.params[Const::HTTP_IF_MODIFIED_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 modified_since && !last_response_time = Time.httpdate(modified_since) rescue nil then false
|
||||
when modified_since && last_response_time > Time.now then false
|
||||
when modified_since && mtime > last_response_time then false
|
||||
when none_match && none_match == '*' then false
|
||||
when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) then false
|
||||
else modified_since || none_match # validation successful if we get this far and at least one of the header exists
|
||||
end
|
||||
|
||||
header = response.header
|
||||
header[Const::ETAG] = etag
|
||||
|
||||
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
|
||||
|
||||
# Support custom responses except 404, which is the default. A little awkward.
|
||||
response.status = 200 if response.status == 404
|
||||
header[Const::LAST_MODIFIED] = mtime.httpdate
|
||||
|
||||
# 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
|
||||
else
|
||||
header[Const::CONTENT_TYPE] = @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, stat.size < Const::CHUNK_SIZE * 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Process the request to either serve a file or a directory listing
|
||||
# if allowed (based on the listing_allowed parameter to the constructor).
|
||||
def process(request, response)
|
||||
req_method = request.params[Const::REQUEST_METHOD] || Const::GET
|
||||
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_path
|
||||
send_dir_listing(request.params[Const::REQUEST_URI], req_path, response)
|
||||
elsif req_method == Const::HEAD
|
||||
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 sending file #{req_path}: #{details}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# There is a small number of default mime types for extensions, but
|
||||
# this lets you add any others you'll need when serving content.
|
||||
def DirHandler::add_mime_type(extension, type)
|
||||
MIME_TYPES[extension] = type
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# When added to a config script (-S in puma_rails) it will
|
||||
# look at the client's allowed response types and then gzip
|
||||
# compress anything that is going out.
|
||||
#
|
||||
# Valid option is :always_deflate => false which tells the handler to
|
||||
# deflate everything even if the client can't handle it.
|
||||
class DeflateFilter < HttpHandler
|
||||
include Zlib
|
||||
HTTP_ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING"
|
||||
|
||||
def initialize(ops={})
|
||||
@options = ops
|
||||
@always_deflate = ops[:always_deflate] || false
|
||||
end
|
||||
|
||||
def process(request, response)
|
||||
accepts = request.params[HTTP_ACCEPT_ENCODING]
|
||||
# only process if they support compression
|
||||
if @always_deflate or (accepts and (accepts.include? "deflate" and not response.body_sent))
|
||||
response.header["Content-Encoding"] = "deflate"
|
||||
response.body = deflate(response.body)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def deflate(stream)
|
||||
deflater = Deflate.new(
|
||||
DEFAULT_COMPRESSION,
|
||||
# drop the zlib header which causes both Safari and IE to choke
|
||||
-MAX_WBITS,
|
||||
DEF_MEM_LEVEL,
|
||||
DEFAULT_STRATEGY)
|
||||
|
||||
stream.rewind
|
||||
gzout = StringIO.new(deflater.deflate(stream.read, FINISH))
|
||||
stream.close
|
||||
gzout.rewind
|
||||
gzout
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Implements a few basic statistics for a particular URI. Register it anywhere
|
||||
# you want in the request chain and it'll quickly gather some numbers for you
|
||||
# to analyze. It is pretty fast, but don't put it out in production.
|
||||
#
|
||||
# You should pass the filter to StatusHandler as StatusHandler.new(:stats_filter => stats).
|
||||
# This lets you then hit the status URI you want and get these stats from a browser.
|
||||
#
|
||||
# StatisticsFilter takes an option of :sample_rate. This is a number that's passed to
|
||||
# rand and if that number gets hit then a sample is taken. This helps reduce the load
|
||||
# and keeps the statistics valid (since sampling is a part of how they work).
|
||||
#
|
||||
# The exception to :sample_rate is that inter-request time is sampled on every request.
|
||||
# If this wasn't done then it wouldn't be accurate as a measure of time between requests.
|
||||
class StatisticsFilter < HttpHandler
|
||||
attr_reader :stats
|
||||
|
||||
def initialize(ops={})
|
||||
@sample_rate = ops[:sample_rate] || 300
|
||||
|
||||
@processors = Puma::Stats.new("processors")
|
||||
@reqsize = Puma::Stats.new("request Kb")
|
||||
@headcount = Puma::Stats.new("req param count")
|
||||
@respsize = Puma::Stats.new("response Kb")
|
||||
@interreq = Puma::Stats.new("inter-request time")
|
||||
end
|
||||
|
||||
|
||||
def process(request, response)
|
||||
if rand(@sample_rate)+1 == @sample_rate
|
||||
@headcount.sample(request.params.length)
|
||||
@reqsize.sample(request.body.length / 1024.0)
|
||||
@respsize.sample((response.body.length + response.header.out.length) / 1024.0)
|
||||
end
|
||||
@interreq.tick
|
||||
end
|
||||
|
||||
def dump
|
||||
"#{@reqsize.to_s}\n#{@headcount.to_s}\n#{@respsize.to_s}\n#{@interreq.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# The :stats_filter is basically any configured stats filter that you've added to this same
|
||||
# URI. This lets the status handler print out statistics on how Puma is doing.
|
||||
class StatusHandler < HttpHandler
|
||||
def initialize(ops={})
|
||||
@stats = ops[:stats_filter]
|
||||
end
|
||||
|
||||
def table(title, rows)
|
||||
results = "<table border=\"1\"><tr><th colspan=\"#{rows[0].length}\">#{title}</th></tr>"
|
||||
rows.each do |cols|
|
||||
results << "<tr>"
|
||||
cols.each {|col| results << "<td>#{col}</td>" }
|
||||
results << "</tr>"
|
||||
end
|
||||
results + "</table>"
|
||||
end
|
||||
|
||||
def describe_listener
|
||||
results = ""
|
||||
results << "<h1>Listener #{listener.host}:#{listener.port}</h1>"
|
||||
results << table("settings", [
|
||||
["host",listener.host],
|
||||
["port",listener.port],
|
||||
["throttle",listener.throttle],
|
||||
["timeout",listener.timeout],
|
||||
["concurrency",listener.concurrent],
|
||||
])
|
||||
|
||||
if @stats
|
||||
results << "<h2>Statistics</h2><p>N means the number of samples, pay attention to MEAN, SD, MIN and MAX."
|
||||
results << "<pre>#{@stats.dump}</pre>"
|
||||
end
|
||||
|
||||
results << "<h2>Registered Handlers</h2>"
|
||||
handler_map = listener.classifier.handler_map
|
||||
results << table("handlers", handler_map.map {|uri,handlers|
|
||||
[uri,
|
||||
"<pre>" +
|
||||
handlers.map {|h| h.class.to_s }.join("\n") +
|
||||
"</pre>"
|
||||
]
|
||||
})
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def process(request, response)
|
||||
response.start do |head,out|
|
||||
out.write <<-END
|
||||
<html><body><title>Puma Server Status</title>
|
||||
#{describe_listener}
|
||||
</body></html>
|
||||
END
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This handler allows you to redirect one url to another.
|
||||
# You can use it like String#gsub, where the string is the REQUEST_URI.
|
||||
# REQUEST_URI is the full path with GET parameters.
|
||||
#
|
||||
# Eg. /test/something?help=true&disclaimer=false
|
||||
#
|
||||
# == Examples
|
||||
#
|
||||
# h = Puma::HttpServer.new('0.0.0.0')
|
||||
# h.register '/test', Puma::RedirectHandler.new('/to/there') # simple
|
||||
# h.register '/to', Puma::RedirectHandler.new(/t/, 'w') # regexp
|
||||
# # and with a block
|
||||
# h.register '/hey', Puma::RedirectHandler.new(/(\w+)/) { |match| ... }
|
||||
#
|
||||
class RedirectHandler < Puma::HttpHandler
|
||||
# You set the rewrite rules when building the object.
|
||||
#
|
||||
# pattern => What to look for or replacement if used alone
|
||||
#
|
||||
# replacement, block => One of them is used to replace the found text
|
||||
|
||||
def initialize(pattern, replacement = nil, &block)
|
||||
unless replacement or block
|
||||
@pattern, @replacement = nil, pattern
|
||||
else
|
||||
@pattern, @replacement, @block = pattern, replacement, block
|
||||
end
|
||||
end
|
||||
|
||||
# Process the request and return a redirect response
|
||||
def process(request, response)
|
||||
unless @pattern
|
||||
response.socket.write(Puma::Const::REDIRECT % @replacement)
|
||||
else
|
||||
if @block
|
||||
new_path = request.params['REQUEST_URI'].gsub(@pattern, &@block)
|
||||
else
|
||||
new_path = request.params['REQUEST_URI'].gsub(@pattern, @replacement)
|
||||
end
|
||||
response.socket.write(Puma::Const::REDIRECT % new_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -38,6 +38,10 @@ module Puma
|
|||
attr_reader :timeout
|
||||
attr_reader :concurrent
|
||||
|
||||
attr_accessor :app
|
||||
|
||||
attr_accessor :stderr, :stdout
|
||||
|
||||
# Creates a working server on host:port (strange things happen if port
|
||||
# isn't a Number).
|
||||
#
|
||||
|
@ -67,6 +71,9 @@ module Puma
|
|||
@thread_pool = ThreadPool.new(0, concurrent) do |client|
|
||||
process_client(client)
|
||||
end
|
||||
|
||||
@stderr = STDERR
|
||||
@stdout = STDOUT
|
||||
end
|
||||
|
||||
def handle_request(params, client, body)
|
||||
|
@ -145,12 +152,12 @@ module Puma
|
|||
client.close rescue nil
|
||||
|
||||
rescue HttpParserError => e
|
||||
STDERR.puts "#{Time.now}: HTTP parse error, malformed request (#{params[HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}"
|
||||
STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
|
||||
@stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{params[HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}"
|
||||
@stderr.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
|
||||
|
||||
rescue Object => e
|
||||
STDERR.puts "#{Time.now}: Read error: #{e.inspect}"
|
||||
STDERR.puts e.backtrace.join("\n")
|
||||
@stderr.puts "#{Time.now}: Read error: #{e.inspect}"
|
||||
@stderr.puts e.backtrace.join("\n")
|
||||
|
||||
ensure
|
||||
begin
|
||||
|
@ -158,8 +165,8 @@ module Puma
|
|||
rescue IOError
|
||||
# Already closed
|
||||
rescue Object => e
|
||||
STDERR.puts "#{Time.now}: Client error: #{e.inspect}"
|
||||
STDERR.puts e.backtrace.join("\n")
|
||||
@stderr.puts "#{Time.now}: Client error: #{e.inspect}"
|
||||
@stderr.puts e.backtrace.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -240,116 +247,20 @@ module Puma
|
|||
# client closed the socket even before accept
|
||||
client.close rescue nil
|
||||
rescue Object => e
|
||||
STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
|
||||
STDERR.puts e.backtrace.join("\n")
|
||||
@stderr.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
|
||||
@stderr.puts e.backtrace.join("\n")
|
||||
end
|
||||
end
|
||||
graceful_shutdown
|
||||
ensure
|
||||
@socket.close
|
||||
# STDERR.puts "#{Time.now}: Closed socket."
|
||||
# @stderr.puts "#{Time.now}: Closed socket."
|
||||
end
|
||||
end
|
||||
|
||||
return @acceptor
|
||||
end
|
||||
|
||||
# Stops the acceptor thread and then causes the worker threads to finish
|
||||
# off the request queue before finally exiting.
|
||||
def stop(sync=false)
|
||||
@notify << STOP_COMMAND
|
||||
|
||||
@acceptor.join if sync
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class HttpServer < Server
|
||||
attr_reader :classifier
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
@classifier = URIClassifier.new
|
||||
end
|
||||
|
||||
def process(params, client, body)
|
||||
script_name, path_info, handlers =
|
||||
@classifier.resolve(params[REQUEST_PATH])
|
||||
|
||||
if handlers
|
||||
params[PATH_INFO] = path_info
|
||||
params[SCRIPT_NAME] = script_name
|
||||
|
||||
begin
|
||||
request = HttpRequest.new(params, client, body)
|
||||
|
||||
# in the case of large file uploads the user could close
|
||||
# the socket, so skip those requests
|
||||
rescue BodyReadError => e
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
# request is good so far, continue processing the response
|
||||
response = HttpResponse.new(client)
|
||||
|
||||
# Process each handler in registered order until we run out
|
||||
# or one finalizes the response.
|
||||
handlers.each do |handler|
|
||||
handler.process(request, response)
|
||||
break if response.done or client.closed?
|
||||
end
|
||||
|
||||
# And finally, if nobody closed the response off, we finalize it.
|
||||
unless response.done or client.closed?
|
||||
response.finished
|
||||
end
|
||||
ensure
|
||||
request.close_body
|
||||
end
|
||||
else
|
||||
# Didn't find it, return a stock 404 response.
|
||||
client.write(ERROR_404_RESPONSE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Simply registers a handler with the internal URIClassifier.
|
||||
# When the URI is found in the prefix of a request then your handler's
|
||||
# HttpHandler::process method is called.
|
||||
# See Puma::URIClassifier#register for more information.
|
||||
#
|
||||
# If you set in_front=true then the passed in handler will be put in
|
||||
# the front of the list for that particular URI. Otherwise it's placed
|
||||
# at the end of the list.
|
||||
def register(uri, handler, in_front=false)
|
||||
begin
|
||||
@classifier.register(uri, [handler])
|
||||
rescue URIClassifier::RegistrationError => e
|
||||
handlers = @classifier.resolve(uri)[2]
|
||||
if handlers
|
||||
# Already registered
|
||||
method_name = in_front ? 'unshift' : 'push'
|
||||
handlers.send(method_name, handler)
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
handler.listener = self
|
||||
end
|
||||
|
||||
# Removes any handlers registered at the given URI. See Puma::URIClassifier#unregister
|
||||
# for more information. Remember this removes them *all* so the entire
|
||||
# processing chain goes away.
|
||||
def unregister(uri)
|
||||
@classifier.unregister(uri)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class RackServer < Server
|
||||
attr_accessor :app
|
||||
|
||||
def process(env, client, body)
|
||||
begin
|
||||
request = HttpRequest.new(env, client, body)
|
||||
|
@ -411,6 +322,15 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
# Stops the acceptor thread and then causes the worker threads to finish
|
||||
# off the request queue before finally exiting.
|
||||
def stop(sync=false)
|
||||
@notify << STOP_COMMAND
|
||||
|
||||
@acceptor.join if sync
|
||||
end
|
||||
end
|
||||
|
||||
HttpServer = Server
|
||||
end
|
||||
|
|
|
@ -41,13 +41,15 @@ class CommandTest < Test::Unit::TestCase
|
|||
|
||||
def setup
|
||||
$test_command_ran = false
|
||||
@stdout = StringIO.new
|
||||
@stderr = StringIO.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
end
|
||||
|
||||
def run_cmd(args)
|
||||
Puma::Command::Registry.instance.run args
|
||||
Puma::Command::Registry.new(@stdout, @stderr).run args
|
||||
end
|
||||
|
||||
def test_run_command
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
||||
# for more information.
|
||||
|
||||
require 'test/testhelp'
|
||||
|
||||
include Puma
|
||||
|
||||
class ConditionalResponseTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@server = HttpServer.new('127.0.0.1', 3501)
|
||||
@server.register('/', Puma::DirHandler.new('.'))
|
||||
@server.run
|
||||
|
||||
@http = Net::HTTP.new(@server.host, @server.port)
|
||||
|
||||
# get the ETag and Last-Modified headers
|
||||
@path = '/README.txt'
|
||||
res = @http.start { |http| http.get(@path) }
|
||||
assert_not_nil @etag = res['ETag']
|
||||
assert_not_nil @last_modified = res['Last-Modified']
|
||||
assert_not_nil @content_length = res['Content-Length']
|
||||
end
|
||||
|
||||
def teardown
|
||||
@server.stop(true)
|
||||
end
|
||||
|
||||
# status should be 304 Not Modified when If-None-Match is the matching ETag
|
||||
def test_not_modified_via_if_none_match
|
||||
assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag
|
||||
end
|
||||
|
||||
# status should be 304 Not Modified when If-Modified-Since is the matching Last-Modified date
|
||||
def test_not_modified_via_if_modified_since
|
||||
assert_status_for_get_and_head Net::HTTPNotModified, 'If-Modified-Since' => @last_modified
|
||||
end
|
||||
|
||||
# status should be 304 Not Modified when If-None-Match is the matching ETag
|
||||
# and If-Modified-Since is the matching Last-Modified date
|
||||
def test_not_modified_via_if_none_match_and_if_modified_since
|
||||
assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => @last_modified
|
||||
end
|
||||
|
||||
# status should be 200 OK when If-None-Match is invalid
|
||||
def test_invalid_if_none_match
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid'
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid', 'If-Modified-Since' => @last_modified
|
||||
end
|
||||
|
||||
# status should be 200 OK when If-Modified-Since is invalid
|
||||
def test_invalid_if_modified_since
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-Modified-Since' => 'invalid'
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => 'invalid'
|
||||
end
|
||||
|
||||
# status should be 304 Not Modified when If-Modified-Since is greater than the Last-Modified header, but less than the system time
|
||||
def test_if_modified_since_greater_than_last_modified
|
||||
sleep 2
|
||||
last_modified_plus_1 = (Time.httpdate(@last_modified) + 1).httpdate
|
||||
assert_status_for_get_and_head Net::HTTPNotModified, 'If-Modified-Since' => last_modified_plus_1
|
||||
assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_plus_1
|
||||
end
|
||||
|
||||
# status should be 200 OK when If-Modified-Since is less than the Last-Modified header
|
||||
def test_if_modified_since_less_than_last_modified
|
||||
last_modified_minus_1 = (Time.httpdate(@last_modified) - 1).httpdate
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-Modified-Since' => last_modified_minus_1
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_minus_1
|
||||
end
|
||||
|
||||
# status should be 200 OK when If-Modified-Since is a date in the future
|
||||
def test_future_if_modified_since
|
||||
the_future = Time.at(2**31-1).httpdate
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-Modified-Since' => the_future
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => the_future
|
||||
end
|
||||
|
||||
# status should be 200 OK when If-None-Match is a wildcard
|
||||
def test_wildcard_match
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*'
|
||||
assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*', 'If-Modified-Since' => @last_modified
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# assert the response status is correct for GET and HEAD
|
||||
def assert_status_for_get_and_head(response_class, headers = {})
|
||||
%w{ get head }.each do |method|
|
||||
res = @http.send(method, @path, headers)
|
||||
assert_kind_of response_class, res
|
||||
assert_equal @etag, res['ETag']
|
||||
case response_class.to_s
|
||||
when 'Net::HTTPNotModified' then
|
||||
assert_nil res['Last-Modified']
|
||||
assert_nil res['Content-Length']
|
||||
when 'Net::HTTPOK' then
|
||||
assert_equal @last_modified, res['Last-Modified']
|
||||
assert_equal @content_length, res['Content-Length']
|
||||
else
|
||||
fail "Incorrect response class: #{response_class}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,87 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
# Additional work donated by contributors. See http://puma.rubyforge.org/attributions.html
|
||||
# for more information.
|
||||
|
||||
require 'test/testhelp'
|
||||
|
||||
$test_plugin_fired = 0
|
||||
|
||||
class TestPlugin < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request, response)
|
||||
$test_plugin_fired += 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Sentinel < GemPlugin::Plugin "/handlers"
|
||||
include Puma::HttpHandlerPlugin
|
||||
|
||||
def process(request, response)
|
||||
raise "This Sentinel plugin shouldn't run."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ConfiguratorTest < Test::Unit::TestCase
|
||||
|
||||
def test_base_handler_config
|
||||
@config = nil
|
||||
|
||||
redirect_test_io do
|
||||
@config = Puma::Configurator.new :host => "localhost" do
|
||||
listener :port => 4501 do
|
||||
# 2 in front should run, but the sentinel shouldn't since dirhandler processes the request
|
||||
uri "/", :handler => plugin("/handlers/testplugin")
|
||||
uri "/", :handler => plugin("/handlers/testplugin")
|
||||
uri "/", :handler => Puma::DirHandler.new(".")
|
||||
uri "/", :handler => plugin("/handlers/testplugin")
|
||||
|
||||
uri "/test", :handler => plugin("/handlers/testplugin")
|
||||
uri "/test", :handler => plugin("/handlers/testplugin")
|
||||
uri "/test", :handler => Puma::DirHandler.new(".")
|
||||
uri "/test", :handler => plugin("/handlers/testplugin")
|
||||
|
||||
debug "/"
|
||||
setup_signals
|
||||
|
||||
run_config(File.dirname(__FILE__) + "/../test/puma.conf")
|
||||
load_mime_map(File.dirname(__FILE__) + "/../test/mime.yaml")
|
||||
|
||||
run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# pp @config.listeners.values.first.classifier.routes
|
||||
|
||||
@config.listeners.each do |host,listener|
|
||||
assert listener.classifier.uris.length == 3, "Wrong number of registered URIs"
|
||||
assert listener.classifier.uris.include?("/"), "/ not registered"
|
||||
assert listener.classifier.uris.include?("/test"), "/test not registered"
|
||||
end
|
||||
|
||||
res = Net::HTTP.get(URI.parse('http://localhost:4501/test'))
|
||||
assert res != nil, "Didn't get a response"
|
||||
assert $test_plugin_fired == 3, "Test filter plugin didn't run 3 times."
|
||||
|
||||
redirect_test_io do
|
||||
res = Net::HTTP.get(URI.parse('http://localhost:4501/'))
|
||||
|
||||
assert res != nil, "Didn't get a response"
|
||||
assert $test_plugin_fired == 6, "Test filter plugin didn't run 6 times."
|
||||
end
|
||||
|
||||
redirect_test_io do
|
||||
@config.stop(false, true)
|
||||
end
|
||||
|
||||
assert_raise Errno::EBADF, Errno::ECONNREFUSED do
|
||||
res = Net::HTTP.get(URI.parse("http://localhost:4501/"))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
# Additional work donated by contributors. See http://puma.rubyforge.org/attributions.html
|
||||
# for more information.
|
||||
|
||||
require 'test/testhelp'
|
||||
require 'puma/debug'
|
||||
|
||||
class PumaDbgTest < Test::Unit::TestCase
|
||||
|
||||
def test_tracing_to_log
|
||||
FileUtils.rm_rf "log/puma_debug"
|
||||
|
||||
PumaDbg::configure
|
||||
out = StringIO.new
|
||||
|
||||
PumaDbg::begin_trace(:rails)
|
||||
PumaDbg::trace(:rails, "Good stuff")
|
||||
PumaDbg::end_trace(:rails)
|
||||
|
||||
assert File.exist?("log/puma_debug"), "Didn't make logging directory"
|
||||
end
|
||||
|
||||
end
|
|
@ -1,135 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
||||
# for more information.
|
||||
|
||||
require 'test/testhelp'
|
||||
|
||||
class SimpleHandler < Puma::HttpHandler
|
||||
def process(request, response)
|
||||
response.start do |head,out|
|
||||
head["Content-Type"] = "text/html"
|
||||
results = "<html><body>Your request:<br /><pre>#{request.params.to_yaml}</pre><a href=\"/files\">View the files.</a></body></html>"
|
||||
out << results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DumbHandler < Puma::HttpHandler
|
||||
def process(request, response)
|
||||
response.start do |head,out|
|
||||
head["Content-Type"] = "text/html"
|
||||
out.write("test")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_status(results, expecting)
|
||||
results.each do |res|
|
||||
assert(res.kind_of?(expecting), "Didn't get #{expecting}, got: #{res.class}")
|
||||
end
|
||||
end
|
||||
|
||||
class HandlersTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
stats = Puma::StatisticsFilter.new(:sample_rate => 1)
|
||||
|
||||
@config = Puma::Configurator.new :host => '127.0.0.1', :port => 9998 do
|
||||
listener do
|
||||
uri "/", :handler => SimpleHandler.new
|
||||
uri "/", :handler => stats
|
||||
uri "/404", :handler => Puma::Error404Handler.new("Not found")
|
||||
uri "/dumb", :handler => Puma::DeflateFilter.new
|
||||
uri "/dumb", :handler => DumbHandler.new, :in_front => true
|
||||
uri "/files", :handler => Puma::DirHandler.new("doc")
|
||||
uri "/files_nodir", :handler => Puma::DirHandler.new("doc", listing_allowed=false, index_html="none")
|
||||
uri "/status", :handler => Puma::StatusHandler.new(:stats_filter => stats)
|
||||
uri "/relative", :handler => Puma::DirHandler.new(nil, listing_allowed=false, index_html="none")
|
||||
end
|
||||
end
|
||||
|
||||
unless windows?
|
||||
File.open("/tmp/testfile", 'w') { } # Do nothing
|
||||
end
|
||||
|
||||
@config.run
|
||||
end
|
||||
|
||||
def teardown
|
||||
@config.stop(false, true)
|
||||
File.delete "/tmp/testfile" unless windows?
|
||||
end
|
||||
|
||||
def test_registration_exception_is_not_lost
|
||||
assert_raises(Puma::URIClassifier::RegistrationError) do
|
||||
@config = Puma::Configurator.new do
|
||||
listener do
|
||||
uri "bogus", :handler => SimpleHandler.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_more_web_server
|
||||
res = hit([ "http://localhost:9998/test",
|
||||
"http://localhost:9998/dumb",
|
||||
"http://localhost:9998/404",
|
||||
"http://localhost:9998/files/rdoc/index.html",
|
||||
"http://localhost:9998/files/rdoc/nothere.html",
|
||||
"http://localhost:9998/files/rdoc/",
|
||||
"http://localhost:9998/files_nodir/rdoc/",
|
||||
"http://localhost:9998/status",
|
||||
])
|
||||
check_status res, String
|
||||
end
|
||||
|
||||
def test_nil_dirhandler
|
||||
return if windows?
|
||||
|
||||
# Camping uses this internally
|
||||
handler = Puma::DirHandler.new(nil, false)
|
||||
assert handler.can_serve("/tmp/testfile")
|
||||
# Not a bug! A nil @file parameter is the only circumstance under which
|
||||
# we are allowed to serve any existing file
|
||||
assert handler.can_serve("../../../../../../../../../../tmp/testfile")
|
||||
end
|
||||
|
||||
def test_non_nil_dirhandler_is_not_vulnerable_to_path_traversal
|
||||
# The famous security bug of Puma 1.1.2
|
||||
handler = Puma::DirHandler.new("/doc", false)
|
||||
assert_nil handler.can_serve("/tmp/testfile")
|
||||
assert_nil handler.can_serve("../../../../../../../../../../tmp/testfile")
|
||||
end
|
||||
|
||||
def test_deflate
|
||||
Net::HTTP.start("localhost", 9998) do |h|
|
||||
# Test that no accept-encoding returns a non-deflated response
|
||||
req = h.get("/dumb")
|
||||
assert(
|
||||
!req['Content-Encoding'] ||
|
||||
!req['Content-Encoding'].include?('deflate'))
|
||||
assert_equal "test", req.body
|
||||
|
||||
req = h.get("/dumb", {"Accept-Encoding" => "deflate"})
|
||||
# -MAX_WBITS stops zlib from looking for a zlib header
|
||||
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
||||
assert req['Content-Encoding'].include?('deflate')
|
||||
assert_equal "test", inflater.inflate(req.body)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: find out why this fails on win32 but nowhere else
|
||||
#def test_posting_fails_dirhandler
|
||||
# req = Net::HTTP::Post.new("http://localhost:9998/files/rdoc/")
|
||||
# req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';')
|
||||
# res = hit [["http://localhost:9998/files/rdoc/",req]]
|
||||
# check_status res, Net::HTTPNotFound
|
||||
#end
|
||||
|
||||
def test_unregister
|
||||
@config.listeners["127.0.0.1:9998"].unregister("/")
|
||||
end
|
||||
end
|
||||
|
|
@ -42,7 +42,7 @@ class TestRackServer < Test::Unit::TestCase
|
|||
def setup
|
||||
@valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
||||
|
||||
@server = Puma::RackServer.new("127.0.0.1", 9998)
|
||||
@server = Puma::Server.new("127.0.0.1", 9998)
|
||||
@simple = lambda { |env| [200, { "X-Header" => "Works" }, "Hello"] }
|
||||
@server.app = @simple
|
||||
end
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
# Copyright (c) 2005 Zed A. Shaw
|
||||
# You can redistribute it and/or modify it under the same terms as Ruby.
|
||||
#
|
||||
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
||||
# for more information.
|
||||
|
||||
require 'test/testhelp'
|
||||
|
||||
class RedirectHandlerTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
redirect_test_io do
|
||||
@server = Puma::HttpServer.new('127.0.0.1', 9998)
|
||||
end
|
||||
@server.run
|
||||
@client = Net::HTTP.new('127.0.0.1', 9998)
|
||||
end
|
||||
|
||||
def teardown
|
||||
@server.stop(true)
|
||||
end
|
||||
|
||||
def test_simple_redirect
|
||||
tester = Puma::RedirectHandler.new('/yo')
|
||||
@server.register("/test", tester)
|
||||
|
||||
sleep(1)
|
||||
res = @client.request_get('/test')
|
||||
assert res != nil, "Didn't get a response"
|
||||
assert_equal ['/yo'], res.get_fields('Location')
|
||||
end
|
||||
|
||||
def test_rewrite
|
||||
tester = Puma::RedirectHandler.new(/(\w+)/, '+\1+')
|
||||
@server.register("/test", tester)
|
||||
|
||||
sleep(1)
|
||||
res = @client.request_get('/test/something')
|
||||
assert_equal ['/+test+/+something+'], res.get_fields('Location')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -8,27 +8,28 @@ require 'test/testhelp'
|
|||
|
||||
include Puma
|
||||
|
||||
class TestHandler < Puma::HttpHandler
|
||||
class TestHandler
|
||||
attr_reader :ran_test
|
||||
|
||||
def process(request, response)
|
||||
def call(env)
|
||||
@ran_test = true
|
||||
response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n")
|
||||
|
||||
[200, {"Content-Type" => "text/plain"}, ["hello!"]]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WebServerTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
|
||||
|
||||
redirect_test_io do
|
||||
@server = HttpServer.new("127.0.0.1", 9998)
|
||||
end
|
||||
@server = HttpServer.new("127.0.0.1", 9998)
|
||||
@server.stderr = StringIO.new
|
||||
|
||||
@tester = TestHandler.new
|
||||
@server.register("/test", @tester)
|
||||
|
||||
@server.app = @tester
|
||||
|
||||
redirect_test_io do
|
||||
@server.run
|
||||
end
|
||||
|
|
|
@ -31,17 +31,7 @@ if ENV['DEBUG']
|
|||
end
|
||||
|
||||
def redirect_test_io
|
||||
orig_err = STDERR.dup
|
||||
orig_out = STDOUT.dup
|
||||
STDERR.reopen("test_stderr.log")
|
||||
STDOUT.reopen("test_stdout.log")
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
STDERR.reopen(orig_err)
|
||||
STDOUT.reopen(orig_out)
|
||||
end
|
||||
yield
|
||||
end
|
||||
|
||||
# Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
|
||||
|
|
Loading…
Add table
Reference in a new issue