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:
parent
75fa5fd2fd
commit
537bc21593
16 changed files with 756 additions and 27 deletions
1
Gemfile
1
Gemfile
|
@ -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'
|
||||
|
|
2
Rakefile
2
Rakefile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
107
lib/puma/commonlogger.rb
Normal 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
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
56
lib/puma/rack/backports/uri/common_18.rb
Normal file
56
lib/puma/rack/backports/uri/common_18.rb
Normal 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
|
52
lib/puma/rack/backports/uri/common_192.rb
Normal file
52
lib/puma/rack/backports/uri/common_192.rb
Normal 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
|
29
lib/puma/rack/backports/uri/common_193.rb
Normal file
29
lib/puma/rack/backports/uri/common_193.rb
Normal 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
298
lib/puma/rack/builder.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
123
lib/puma/util.rb
123
lib/puma/util.rb
|
@ -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
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue