Skip to content

Commit

Permalink
Remove rack dependency. Fixes puma#705
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
evanphx committed Jul 14, 2015
1 parent 75fa5fd commit 537bc21
Show file tree
Hide file tree
Showing 16 changed files with 756 additions and 27 deletions.
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions lib/puma/binder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ module Puma
class Binder
include Puma::Const

RACK_VERSION = [1,3].freeze

def initialize(events)
@events = events
@listeners = []
@inherited_fds = {}
@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,
Expand Down Expand Up @@ -87,7 +89,7 @@ def parse(binds, logger)
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
Expand All @@ -110,7 +112,7 @@ def parse(binds, logger)
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)
Expand All @@ -126,7 +128,7 @@ def parse(binds, logger)

@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
Expand Down
3 changes: 1 addition & 2 deletions lib/puma/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
require 'puma/single'
require 'puma/cluster'

require 'rack/commonlogger'
require 'rack/utils'
require 'puma/commonlogger'

module Puma
class << self
Expand Down
107 changes: 107 additions & 0 deletions lib/puma/commonlogger.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions lib/puma/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'rack/builder'
require 'puma/rack/builder'

module Puma

Expand Down Expand Up @@ -79,7 +79,7 @@ def app

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)
Expand All @@ -101,7 +101,7 @@ def infer_tag
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|
Expand Down
83 changes: 77 additions & 6 deletions lib/puma/const.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'rack'

module Puma
class UnsupportedOption < RuntimeError
end
Expand All @@ -8,12 +6,85 @@ class UnsupportedOption < RuntimeError
# 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
Expand Down
56 changes: 56 additions & 0 deletions lib/puma/rack/backports/uri/common_18.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 537bc21

Please sign in to comment.