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

Remove rack dependency. Fixes #705

Because frameworks like rails dependent on rack, if puma truly wants to
be able to reload new code and thus new versions of rails, it has to be
able to reload rack as well.

Having a dependency on rack held by puma prevented that from happening
and so that dependency has been removed.
This commit is contained in:
Evan Phoenix 2015-07-14 10:28:59 -07:00
parent 75fa5fd2fd
commit 537bc21593
16 changed files with 756 additions and 27 deletions

View file

@ -5,7 +5,6 @@ gem "hoe-git"
gem "hoe-ignore"
gem "rdoc"
gem "rake-compiler"
gem "rack"
gem "test-unit", "~> 3.0"
gem 'minitest', '~> 4.0'

View file

@ -20,7 +20,7 @@ HOE = Hoe.spec "puma" do
require_ruby_version ">= 1.8.7"
dependency "rack", [">= 1.1", "< 2.0"]
dependency "rack", [">= 1.1", "< 2.0"], :development
extra_dev_deps << ["rake-compiler", "~> 0.8"]
end

View file

@ -4,6 +4,8 @@ module Puma
class Binder
include Puma::Const
RACK_VERSION = [1,3].freeze
def initialize(events)
@events = events
@listeners = []
@ -11,7 +13,7 @@ module Puma
@unix_paths = []
@proto_env = {
"rack.version".freeze => Rack::VERSION,
"rack.version".freeze => RACK_VERSION,
"rack.errors".freeze => events.stderr,
"rack.multithread".freeze => true,
"rack.multiprocess".freeze => false,
@ -87,7 +89,7 @@ module Puma
logger.log "* Inherited #{str}"
io = inherit_tcp_listener uri.host, uri.port, fd
else
params = Rack::Utils.parse_query uri.query
params = Util.parse_query uri.query
opt = params.key?('low_latency')
bak = params.fetch('backlog', 1024).to_i
@ -110,7 +112,7 @@ module Puma
mode = nil
if uri.query
params = Rack::Utils.parse_query uri.query
params = Util.parse_query uri.query
if u = params['umask']
# Use Integer() to respect the 0 prefix as octal
umask = Integer(u)
@ -126,7 +128,7 @@ module Puma
@listeners << [str, io]
when "ssl"
params = Rack::Utils.parse_query uri.query
params = Util.parse_query uri.query
require 'puma/minissl'
ctx = MiniSSL::Context.new

View file

@ -11,8 +11,7 @@ require 'puma/util'
require 'puma/single'
require 'puma/cluster'
require 'rack/commonlogger'
require 'rack/utils'
require 'puma/commonlogger'
module Puma
class << self

107
lib/puma/commonlogger.rb Normal file
View file

@ -0,0 +1,107 @@
module Puma
# Rack::CommonLogger forwards every request to the given +app+, and
# logs a line in the
# {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
# to the +logger+.
#
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
# an instance of Rack::NullLogger.
#
# +logger+ can be any class, including the standard library Logger, and is
# expected to have either +write+ or +<<+ method, which accepts the CommonLogger::FORMAT.
# According to the SPEC, the error stream must also respond to +puts+
# (which takes a single argument that responds to +to_s+), and +flush+
# (which is called without arguments in order to make the error appear for
# sure)
class CommonLogger
# Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
#
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
#
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
def initialize(app, logger=nil)
@app = app
@logger = logger
end
def call(env)
began_at = Time.now
status, header, body = @app.call(env)
header = Util::HeaderHash.new(header)
# If we've been hijacked, then output a special line
if env['rack.hijack_io']
log_hijacking(env, 'HIJACK', header, began_at)
else
ary = env['rack.after_reply']
ary << lambda { log(env, status, header, began_at) }
end
[status, header, body]
end
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
private
def log_hijacking(env, status, header, began_at)
now = Time.now
logger = @logger || env['rack.errors']
logger.write HIJACK_FORMAT % [
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
env["REMOTE_USER"] || "-",
now.strftime("%d/%b/%Y %H:%M:%S"),
env["REQUEST_METHOD"],
env["PATH_INFO"],
env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
env["HTTP_VERSION"],
now - began_at ]
end
PATH_INFO = 'PATH_INFO'.freeze
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
QUERY_STRING = 'QUERY_STRING'.freeze
CACHE_CONTROL = 'Cache-Control'.freeze
CONTENT_LENGTH = 'Content-Length'.freeze
CONTENT_TYPE = 'Content-Type'.freeze
GET = 'GET'.freeze
HEAD = 'HEAD'.freeze
def log(env, status, header, began_at)
now = Time.now
length = extract_content_length(header)
msg = FORMAT % [
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
env["REMOTE_USER"] || "-",
now.strftime("%d/%b/%Y:%H:%M:%S %z"),
env[REQUEST_METHOD],
env[PATH_INFO],
env[QUERY_STRING].empty? ? "" : "?"+env[QUERY_STRING],
env["HTTP_VERSION"],
status.to_s[0..3],
length,
now - began_at ]
logger = @logger || env['rack.errors']
# Standard library logger doesn't support write but it supports << which actually
# calls to write on the log device without formatting
if logger.respond_to?(:write)
logger.write(msg)
else
logger << msg
end
end
def extract_content_length(headers)
value = headers[CONTENT_LENGTH] or return '-'
value.to_s == '0' ? '-' : value
end
end
end

View file

@ -1,4 +1,4 @@
require 'rack/builder'
require 'puma/rack/builder'
module Puma
@ -79,7 +79,7 @@ module Puma
if !@options[:quiet] and @options[:environment] == "development"
logger = @options[:logger] || STDOUT
found = Rack::CommonLogger.new(found, logger)
found = CommonLogger.new(found, logger)
end
ConfigMiddleware.new(self, found)
@ -101,7 +101,7 @@ module Puma
def load_rackup
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
rack_app, rack_options = Rack::Builder.parse_file(rackup)
rack_app, rack_options = Puma::Rack::Builder.parse_file(rackup)
@options.merge!(rack_options)
config_ru_binds = rack_options.each_with_object([]) do |(k, v), b|

View file

@ -1,5 +1,3 @@
require 'rack'
module Puma
class UnsupportedOption < RuntimeError
end
@ -8,12 +6,85 @@ module Puma
# Every standard HTTP code mapped to the appropriate message. These are
# used so frequently that they are placed directly in Puma for easy
# access rather than Puma::Const itself.
HTTP_STATUS_CODES = Rack::Utils::HTTP_STATUS_CODES
# Every standard HTTP code mapped to the appropriate message.
# Generated with:
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
HTTP_STATUS_CODES = {
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Payload Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
426 => 'Upgrade Required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
510 => 'Not Extended',
511 => 'Network Authentication Required'
}
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
}.flatten]
# For some HTTP status codes the client only expects headers.
STATUS_WITH_NO_ENTITY_BODY = Hash[Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.map { |s|
[s, true]
}]
#
no_body = {}
((100..199).to_a << 204 << 205 << 304).each do |code|
no_body[code] = true
end
STATUS_WITH_NO_ENTITY_BODY = no_body
# Frequently used constants when constructing requests or responses. Many times
# the constant just refers to a string with the same contents. Using these constants

View file

@ -0,0 +1,56 @@
# :stopdoc:
# Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
#
# https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
#
#
module URI
TBLENCWWWCOMP_ = {} # :nodoc:
256.times do |i|
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
end
TBLENCWWWCOMP_[' '] = '+'
TBLENCWWWCOMP_.freeze
TBLDECWWWCOMP_ = {} # :nodoc:
256.times do |i|
h, l = i>>4, i&15
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
end
TBLDECWWWCOMP_['+'] = ' '
TBLDECWWWCOMP_.freeze
# Encode given +s+ to URL-encoded form data.
#
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
# (ASCII space) to + and converts others to %XX.
#
# This is an implementation of
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
#
# See URI.decode_www_form_component, URI.encode_www_form
def self.encode_www_form_component(s)
str = s.to_s
if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
'%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
end.tr(' ', '+')
else
str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
end
end
# Decode given +str+ of URL-encoded form data.
#
# This decodes + to SP.
#
# See URI.encode_www_form_component, URI.decode_www_form
def self.decode_www_form_component(str, enc=nil)
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
end
end

View file

@ -0,0 +1,52 @@
# :stopdoc:
# Stolen from ruby core's uri/common.rb @32618ba to fix DoS issues in 1.9.2
#
# https://github.com/ruby/ruby/blob/32618ba7438a2247042bba9b5d85b5d49070f5e5/lib/uri/common.rb
#
# Issue:
# http://redmine.ruby-lang.org/issues/5149
#
# Relevant Fixes:
# https://github.com/ruby/ruby/commit/b5f91deee04aa6ccbe07c23c8222b937c22a799b
# https://github.com/ruby/ruby/commit/93177c1e5c3906abf14472ae0b905d8b5c72ce1b
#
# This should probably be removed once there is a Ruby 1.9.2 patch level that
# includes this fix.
require 'uri/common'
module URI
TBLDECWWWCOMP_ = {} unless const_defined?(:TBLDECWWWCOMP_) #:nodoc:
if TBLDECWWWCOMP_.empty?
256.times do |i|
h, l = i>>4, i&15
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
end
TBLDECWWWCOMP_['+'] = ' '
TBLDECWWWCOMP_.freeze
end
def self.decode_www_form(str, enc=Encoding::UTF_8)
return [] if str.empty?
unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
end
ary = []
$&.scan(/([^=;&]+)=([^;&]*)/) do
ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
end
ary
end
def self.decode_www_form_component(str, enc=Encoding::UTF_8)
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
end
remove_const :WFKV_ if const_defined?(:WFKV_)
WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
end

View file

@ -0,0 +1,29 @@
# :stopdoc:
require 'uri/common'
# Issue:
# http://bugs.ruby-lang.org/issues/5925
#
# Relevant commit:
# https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1
module URI
256.times do |i|
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
end
TBLENCWWWCOMP_[' '] = '+'
TBLENCWWWCOMP_.freeze
256.times do |i|
h, l = i>>4, i&15
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
end
TBLDECWWWCOMP_['+'] = ' '
TBLDECWWWCOMP_.freeze
end
# :startdoc:

298
lib/puma/rack/builder.rb Normal file
View file

@ -0,0 +1,298 @@
module PumaRackCompat
BINDING = binding
def self.const_missing(cm)
if cm == :Rack
require 'rack'
Rack
else
raise NameError, "constant undefined: #{cm}"
end
end
end
module Puma::Rack
class Options
def parse!(args)
options = {}
opt_parser = OptionParser.new("", 24, ' ') do |opts|
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
opts.separator ""
opts.separator "Ruby options:"
lineno = 1
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
eval line, TOPLEVEL_BINDING, "-e", lineno
lineno += 1
}
opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
options[:builder] = line
}
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
options[:debug] = true
}
opts.on("-w", "--warn", "turn warnings on for your script") {
options[:warn] = true
}
opts.on("-q", "--quiet", "turn off logging") {
options[:quiet] = true
}
opts.on("-I", "--include PATH",
"specify $LOAD_PATH (may be used more than once)") { |path|
(options[:include] ||= []).concat(path.split(":"))
}
opts.on("-r", "--require LIBRARY",
"require the library, before executing your script") { |library|
options[:require] = library
}
opts.separator ""
opts.separator "Rack options:"
opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
options[:server] = s
}
opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
options[:Host] = host
}
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
options[:Port] = port
}
opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
name, value = name.split('=', 2)
value = true if value.nil?
options[name.to_sym] = value
}
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
options[:environment] = e
}
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
options[:daemonize] = d ? true : false
}
opts.on("-P", "--pid FILE", "file to store PID") { |f|
options[:pid] = ::File.expand_path(f)
}
opts.separator ""
opts.separator "Common options:"
opts.on_tail("-h", "-?", "--help", "Show this message") do
puts opts
puts handler_opts(options)
exit
end
opts.on_tail("--version", "Show version") do
puts "Rack #{Rack.version} (Release: #{Rack.release})"
exit
end
end
begin
opt_parser.parse! args
rescue OptionParser::InvalidOption => e
warn e.message
abort opt_parser.to_s
end
options[:config] = args.last if args.last
options
end
def handler_opts(options)
begin
info = []
server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
if server && server.respond_to?(:valid_options)
info << ""
info << "Server-specific options for #{server.name}:"
has_options = false
server.valid_options.each do |name, description|
next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
info << " -O %-21s %s" % [name, description]
has_options = true
end
return "" if !has_options
end
info.join("\n")
rescue NameError
return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
end
end
end
# Rack::Builder implements a small DSL to iteratively construct Rack
# applications.
#
# Example:
#
# require 'rack/lobster'
# app = Rack::Builder.new do
# use Rack::CommonLogger
# use Rack::ShowExceptions
# map "/lobster" do
# use Rack::Lint
# run Rack::Lobster.new
# end
# end
#
# run app
#
# Or
#
# app = Rack::Builder.app do
# use Rack::CommonLogger
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
# end
#
# run app
#
# +use+ adds middleware to the stack, +run+ dispatches to an application.
# You can use +map+ to construct a Rack::URLMap in a convenient way.
class Builder
def self.parse_file(config, opts = Options.new)
options = {}
if config =~ /\.ru$/
cfgfile = ::File.read(config)
if cfgfile[/^#\\(.*)/] && opts
options = opts.parse! $1.split(/\s+/)
end
cfgfile.sub!(/^__END__\n.*\Z/m, '')
app = new_from_string cfgfile, config
else
require config
app = Object.const_get(::File.basename(config, '.rb').capitalize)
end
return app, options
end
def self.new_from_string(builder_script, file="(rackup)")
eval "Puma::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
PumaRackCompat::BINDING, file, 0
end
def initialize(default_app = nil,&block)
@use, @map, @run, @warmup = [], nil, default_app, nil
instance_eval(&block) if block_given?
end
def self.app(default_app = nil, &block)
self.new(default_app, &block).to_app
end
# Specifies middleware to use in a stack.
#
# class Middleware
# def initialize(app)
# @app = app
# end
#
# def call(env)
# env["rack.some_header"] = "setting an example"
# @app.call(env)
# end
# end
#
# use Middleware
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
#
# All requests through to this application will first be processed by the middleware class.
# The +call+ method in this example sets an additional environment key which then can be
# referenced in the application if required.
def use(middleware, *args, &block)
if @map
mapping, @map = @map, nil
@use << proc { |app| generate_map app, mapping }
end
@use << proc { |app| middleware.new(app, *args, &block) }
end
# Takes an argument that is an object that responds to #call and returns a Rack response.
# The simplest form of this is a lambda object:
#
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
#
# However this could also be a class:
#
# class Heartbeat
# def self.call(env)
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
# end
# end
#
# run Heartbeat
def run(app)
@run = app
end
# Takes a lambda or block that is used to warm-up the application.
#
# warmup do |app|
# client = Rack::MockRequest.new(app)
# client.get('/')
# end
#
# use SomeMiddleware
# run MyApp
def warmup(prc=nil, &block)
@warmup = prc || block
end
# Creates a route within the application.
#
# Rack::Builder.app do
# map '/' do
# run Heartbeat
# end
# end
#
# The +use+ method can also be used here to specify middleware to run under a specific path:
#
# Rack::Builder.app do
# map '/' do
# use Middleware
# run Heartbeat
# end
# end
#
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
#
def map(path, &block)
@map ||= {}
@map[path] = block
end
def to_app
app = @map ? generate_map(@run, @map) : @run
fail "missing run or map statement" unless app
app = @use.reverse.inject(app) { |a,e| e[a] }
@warmup.call(app) if @warmup
app
end
def call(env)
to_app.call(env)
end
private
def generate_map(default_app, mapping)
mapped = default_app ? {'/' => default_app} : {}
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
URLMap.new(mapped)
end
end
end

View file

@ -1,6 +1,6 @@
require 'rack/commonlogger'
require 'puma/rack/commonlogger'
module Rack
module Puma::Rack
# Patch CommonLogger to use after_reply.
#
# Simply request this file and CommonLogger will be a bit more

View file

@ -1,4 +1,3 @@
require 'rack'
require 'stringio'
require 'puma/thread_pool'
@ -13,8 +12,6 @@ require 'puma/delegation'
require 'puma/accept_nonblock'
require 'puma/util'
require 'puma/rack_patch'
require 'puma/puma_http11'
unless Puma.const_defined? "IOBuffer"

View file

@ -1,3 +1,15 @@
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
if major == 1 && minor < 9
require 'puma/rack/backports/uri/common_18'
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
require 'puma/rack/backports/uri/common_192'
elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
require 'puma/rack/backports/uri/common_193'
else
require 'uri/common'
end
module Puma
module Util
module_function
@ -5,5 +17,116 @@ module Puma
def pipe
IO.pipe
end
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
# target encoding of the string returned, and it defaults to UTF-8
if defined?(::Encoding)
def unescape(s, encoding = Encoding::UTF_8)
URI.decode_www_form_component(s, encoding)
end
else
def unescape(s, encoding = nil)
URI.decode_www_form_component(s, encoding)
end
end
module_function :unescape
DEFAULT_SEP = /[&;] */n
# Stolen from Mongrel, with some small modifications:
# Parses a query string by breaking it up at the '&'
# and ';' characters. You can also use this to parse
# cookies by changing the characters used in the second
# parameter (which defaults to '&;').
def parse_query(qs, d = nil, &unescaper)
unescaper ||= method(:unescape)
params = {}
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
next if p.empty?
k, v = p.split('=', 2).map(&unescaper)
if cur = params[k]
if cur.class == Array
params[k] << v
else
params[k] = [cur, v]
end
else
params[k] = v
end
end
return params
end
# A case-insensitive Hash that preserves the original case of a
# header when set.
class HeaderHash < Hash
def self.new(hash={})
HeaderHash === hash ? hash : super(hash)
end
def initialize(hash={})
super()
@names = {}
hash.each { |k, v| self[k] = v }
end
def each
super do |k, v|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
end
end
def to_hash
hash = {}
each { |k,v| hash[k] = v }
hash
end
def [](k)
super(k) || super(@names[k.downcase])
end
def []=(k, v)
canonical = k.downcase
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
@names[k] = @names[canonical] = k
super k, v
end
def delete(k)
canonical = k.downcase
result = super @names.delete(canonical)
@names.delete_if { |name,| name.downcase == canonical }
result
end
def include?(k)
@names.include?(k) || @names.include?(k.downcase)
end
alias_method :has_key?, :include?
alias_method :member?, :include?
alias_method :key?, :include?
def merge!(other)
other.each { |k, v| self[k] = v }
self
end
def merge(other)
hash = dup
hash.merge! other
end
def replace(other)
clear
other.each { |k, v| self[k] = v }
self
end
end
end
end

View file

@ -36,18 +36,15 @@ Gem::Specification.new do |s|
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
s.add_development_dependency(%q<rdoc>, ["~> 4.0"])
s.add_development_dependency(%q<rake-compiler>, ["~> 0.8.0"])
s.add_development_dependency(%q<hoe>, ["~> 3.6"])
else
s.add_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
s.add_dependency(%q<rdoc>, ["~> 4.0"])
s.add_dependency(%q<rake-compiler>, ["~> 0.8.0"])
s.add_dependency(%q<hoe>, ["~> 3.6"])
end
else
s.add_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
s.add_dependency(%q<rdoc>, ["~> 4.0"])
s.add_dependency(%q<rake-compiler>, ["~> 0.8.0"])
s.add_dependency(%q<hoe>, ["~> 3.6"])

View file

@ -2,8 +2,7 @@ require 'test/unit'
require 'puma'
require 'rack/lint'
require 'test/testhelp'
require 'rack/commonlogger'
require 'puma/rack_patch'
require 'puma/commonlogger'
class TestRackServer < Test::Unit::TestCase