rack 1.1.6 → 1.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/COPYING +1 -1
- data/HISTORY.md +375 -0
- data/KNOWN-ISSUES +23 -0
- data/README.rdoc +312 -0
- data/Rakefile +124 -0
- data/SPEC +125 -32
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rack_logo.svg +1 -1
- data/contrib/rdoc.css +412 -0
- data/example/protectedlobster.rb +1 -1
- data/lib/rack/auth/abstract/handler.rb +4 -4
- data/lib/rack/auth/abstract/request.rb +7 -5
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +7 -3
- data/lib/rack/auth/digest/nonce.rb +1 -1
- data/lib/rack/auth/digest/params.rb +7 -9
- data/lib/rack/auth/digest/request.rb +10 -9
- data/lib/rack/backports/uri/common_18.rb +56 -0
- data/lib/rack/backports/uri/common_192.rb +52 -0
- data/lib/rack/backports/uri/common_193.rb +29 -0
- data/lib/rack/body_proxy.rb +39 -0
- data/lib/rack/builder.rb +106 -22
- data/lib/rack/cascade.rb +17 -6
- data/lib/rack/chunked.rb +44 -24
- data/lib/rack/commonlogger.rb +36 -13
- data/lib/rack/conditionalget.rb +49 -17
- data/lib/rack/config.rb +5 -0
- data/lib/rack/content_length.rb +14 -6
- data/lib/rack/content_type.rb +7 -1
- data/lib/rack/deflater.rb +73 -15
- data/lib/rack/directory.rb +18 -8
- data/lib/rack/etag.rb +59 -9
- data/lib/rack/file.rb +106 -44
- data/lib/rack/handler/cgi.rb +11 -11
- data/lib/rack/handler/fastcgi.rb +18 -6
- data/lib/rack/handler/lsws.rb +2 -4
- data/lib/rack/handler/mongrel.rb +22 -6
- data/lib/rack/handler/scgi.rb +16 -8
- data/lib/rack/handler/thin.rb +19 -4
- data/lib/rack/handler/webrick.rb +72 -19
- data/lib/rack/handler.rb +47 -14
- data/lib/rack/head.rb +10 -2
- data/lib/rack/lint.rb +260 -75
- data/lib/rack/lobster.rb +13 -8
- data/lib/rack/lock.rb +13 -3
- data/lib/rack/logger.rb +0 -2
- data/lib/rack/methodoverride.rb +27 -8
- data/lib/rack/mime.rb +625 -167
- data/lib/rack/mock.rb +78 -53
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +253 -0
- data/lib/rack/multipart/uploaded_file.rb +34 -0
- data/lib/rack/multipart.rb +34 -0
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/recursive.rb +10 -5
- data/lib/rack/reloader.rb +3 -2
- data/lib/rack/request.rb +201 -74
- data/lib/rack/response.rb +41 -28
- data/lib/rack/rewindable_input.rb +15 -11
- data/lib/rack/runtime.rb +16 -3
- data/lib/rack/sendfile.rb +47 -29
- data/lib/rack/server.rb +223 -47
- data/lib/rack/session/abstract/id.rb +289 -30
- data/lib/rack/session/cookie.rb +133 -44
- data/lib/rack/session/memcache.rb +30 -56
- data/lib/rack/session/pool.rb +19 -43
- data/lib/rack/showexceptions.rb +53 -15
- data/lib/rack/showstatus.rb +14 -7
- data/lib/rack/static.rb +124 -12
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +49 -15
- data/lib/rack/utils/okjson.rb +600 -0
- data/lib/rack/utils.rb +363 -361
- data/lib/rack.rb +17 -23
- data/rack.gemspec +11 -20
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +5 -0
- data/test/builder/line.ru +1 -0
- data/test/builder/options.ru +2 -0
- data/test/cgi/assets/folder/test.js +1 -0
- data/test/cgi/assets/fonts/font.eot +1 -0
- data/test/cgi/assets/images/image.png +1 -0
- data/test/cgi/assets/index.html +1 -0
- data/test/cgi/assets/javascripts/app.js +1 -0
- data/test/cgi/assets/stylesheets/app.css +1 -0
- data/test/cgi/lighttpd.conf +26 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test+directory/test+file +1 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +10 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_null_byte +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_percentages +6 -0
- data/test/multipart/filename_with_unescaped_percentages2 +6 -0
- data/test/multipart/filename_with_unescaped_percentages3 +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/multipart/mixed_files +21 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/multipart/three_files_three_fields +31 -0
- data/test/multipart/webkit +32 -0
- data/test/rackup/config.ru +31 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
- data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
- data/test/spec_body_proxy.rb +85 -0
- data/test/spec_builder.rb +223 -0
- data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
- data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
- data/test/spec_chunked.rb +101 -0
- data/test/spec_commonlogger.rb +93 -0
- data/test/spec_conditionalget.rb +102 -0
- data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
- data/test/spec_content_length.rb +85 -0
- data/test/spec_content_type.rb +45 -0
- data/test/spec_deflater.rb +339 -0
- data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
- data/test/spec_etag.rb +107 -0
- data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
- data/test/spec_file.rb +221 -0
- data/test/spec_handler.rb +72 -0
- data/test/spec_head.rb +45 -0
- data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
- data/test/spec_lobster.rb +58 -0
- data/test/spec_lock.rb +164 -0
- data/test/spec_logger.rb +23 -0
- data/test/spec_methodoverride.rb +95 -0
- data/test/spec_mime.rb +51 -0
- data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
- data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
- data/test/spec_multipart.rb +600 -0
- data/test/spec_nulllogger.rb +20 -0
- data/test/spec_recursive.rb +72 -0
- data/test/spec_request.rb +1227 -0
- data/test/spec_response.rb +407 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +49 -0
- data/test/spec_sendfile.rb +130 -0
- data/test/spec_server.rb +167 -0
- data/test/spec_session_abstract_id.rb +53 -0
- data/test/spec_session_cookie.rb +410 -0
- data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
- data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
- data/test/spec_showexceptions.rb +85 -0
- data/test/spec_showstatus.rb +103 -0
- data/test/spec_static.rb +145 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
- data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
- data/test/spec_utils.rb +647 -0
- data/test/spec_version.rb +17 -0
- data/test/spec_webrick.rb +184 -0
- data/test/static/another/index.html +1 -0
- data/test/static/index.html +1 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +220 -239
- data/RDOX +0 -0
- data/README +0 -592
- data/lib/rack/adapter/camping.rb +0 -22
- data/test/spec_auth.rb +0 -57
- data/test/spec_rack_builder.rb +0 -84
- data/test/spec_rack_camping.rb +0 -55
- data/test/spec_rack_chunked.rb +0 -62
- data/test/spec_rack_commonlogger.rb +0 -61
- data/test/spec_rack_conditionalget.rb +0 -41
- data/test/spec_rack_content_length.rb +0 -43
- data/test/spec_rack_content_type.rb +0 -30
- data/test/spec_rack_deflater.rb +0 -127
- data/test/spec_rack_etag.rb +0 -17
- data/test/spec_rack_file.rb +0 -75
- data/test/spec_rack_handler.rb +0 -43
- data/test/spec_rack_head.rb +0 -30
- data/test/spec_rack_lobster.rb +0 -45
- data/test/spec_rack_lock.rb +0 -38
- data/test/spec_rack_logger.rb +0 -21
- data/test/spec_rack_methodoverride.rb +0 -60
- data/test/spec_rack_nulllogger.rb +0 -13
- data/test/spec_rack_recursive.rb +0 -77
- data/test/spec_rack_request.rb +0 -594
- data/test/spec_rack_response.rb +0 -221
- data/test/spec_rack_rewindable_input.rb +0 -118
- data/test/spec_rack_runtime.rb +0 -35
- data/test/spec_rack_sendfile.rb +0 -86
- data/test/spec_rack_session_cookie.rb +0 -92
- data/test/spec_rack_showexceptions.rb +0 -21
- data/test/spec_rack_showstatus.rb +0 -72
- data/test/spec_rack_static.rb +0 -37
- data/test/spec_rack_utils.rb +0 -557
- data/test/spec_rack_webrick.rb +0 -130
- data/test/spec_rackup.rb +0 -164
|
@@ -4,11 +4,10 @@ module Rack
|
|
|
4
4
|
class Params < Hash
|
|
5
5
|
|
|
6
6
|
def self.parse(str)
|
|
7
|
-
split_header_value(str).
|
|
7
|
+
Params[*split_header_value(str).map do |param|
|
|
8
8
|
k, v = param.split('=', 2)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
end
|
|
9
|
+
[k, dequote(v)]
|
|
10
|
+
end.flatten]
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def self.dequote(str) # From WEBrick::HTTPUtils
|
|
@@ -22,7 +21,7 @@ module Rack
|
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def initialize
|
|
25
|
-
super
|
|
24
|
+
super()
|
|
26
25
|
|
|
27
26
|
yield self if block_given?
|
|
28
27
|
end
|
|
@@ -35,12 +34,11 @@ module Rack
|
|
|
35
34
|
super k.to_s, v.to_s
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
UNQUOTED = ['
|
|
37
|
+
UNQUOTED = ['nc', 'stale']
|
|
39
38
|
|
|
40
39
|
def to_s
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
parts
|
|
40
|
+
map do |k, v|
|
|
41
|
+
"#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
|
|
44
42
|
end.join(', ')
|
|
45
43
|
end
|
|
46
44
|
|
|
@@ -6,17 +6,16 @@ module Rack
|
|
|
6
6
|
module Auth
|
|
7
7
|
module Digest
|
|
8
8
|
class Request < Auth::AbstractRequest
|
|
9
|
-
|
|
10
9
|
def method
|
|
11
|
-
@env['rack.methodoverride.original_method'] || @env[
|
|
10
|
+
@env['rack.methodoverride.original_method'] || @env[REQUEST_METHOD]
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def digest?
|
|
15
|
-
|
|
14
|
+
"digest" == scheme
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def correct_uri?
|
|
19
|
-
|
|
18
|
+
request.fullpath == uri
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def nonce
|
|
@@ -27,13 +26,15 @@ module Rack
|
|
|
27
26
|
@params ||= Params.parse(parts.last)
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
return params[key]
|
|
33
|
-
end
|
|
34
|
-
super
|
|
29
|
+
def respond_to?(sym, *)
|
|
30
|
+
super or params.has_key? sym.to_s
|
|
35
31
|
end
|
|
36
32
|
|
|
33
|
+
def method_missing(sym, *args)
|
|
34
|
+
return super unless params.has_key?(key = sym.to_s)
|
|
35
|
+
return params[key] if args.size == 0
|
|
36
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
|
|
37
|
+
end
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# :stopdoc:
|
|
2
|
+
|
|
3
|
+
# Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
|
|
4
|
+
#
|
|
5
|
+
# https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
|
|
6
|
+
#
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
module URI
|
|
10
|
+
TBLENCWWWCOMP_ = {} # :nodoc:
|
|
11
|
+
256.times do |i|
|
|
12
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
|
13
|
+
end
|
|
14
|
+
TBLENCWWWCOMP_[' '] = '+'
|
|
15
|
+
TBLENCWWWCOMP_.freeze
|
|
16
|
+
TBLDECWWWCOMP_ = {} # :nodoc:
|
|
17
|
+
256.times do |i|
|
|
18
|
+
h, l = i>>4, i&15
|
|
19
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
|
20
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
|
21
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
|
22
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
|
23
|
+
end
|
|
24
|
+
TBLDECWWWCOMP_['+'] = ' '
|
|
25
|
+
TBLDECWWWCOMP_.freeze
|
|
26
|
+
|
|
27
|
+
# Encode given +s+ to URL-encoded form data.
|
|
28
|
+
#
|
|
29
|
+
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
|
|
30
|
+
# (ASCII space) to + and converts others to %XX.
|
|
31
|
+
#
|
|
32
|
+
# This is an implementation of
|
|
33
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
|
34
|
+
#
|
|
35
|
+
# See URI.decode_www_form_component, URI.encode_www_form
|
|
36
|
+
def self.encode_www_form_component(s)
|
|
37
|
+
str = s.to_s
|
|
38
|
+
if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
|
|
39
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
|
40
|
+
'%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
|
|
41
|
+
end.tr(' ', '+')
|
|
42
|
+
else
|
|
43
|
+
str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Decode given +str+ of URL-encoded form data.
|
|
48
|
+
#
|
|
49
|
+
# This decodes + to SP.
|
|
50
|
+
#
|
|
51
|
+
# See URI.encode_www_form_component, URI.decode_www_form
|
|
52
|
+
def self.decode_www_form_component(str, enc=nil)
|
|
53
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
|
|
54
|
+
str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# :stopdoc:
|
|
2
|
+
|
|
3
|
+
# Stolen from ruby core's uri/common.rb @32618ba to fix DoS issues in 1.9.2
|
|
4
|
+
#
|
|
5
|
+
# https://github.com/ruby/ruby/blob/32618ba7438a2247042bba9b5d85b5d49070f5e5/lib/uri/common.rb
|
|
6
|
+
#
|
|
7
|
+
# Issue:
|
|
8
|
+
# http://redmine.ruby-lang.org/issues/5149
|
|
9
|
+
#
|
|
10
|
+
# Relevant Fixes:
|
|
11
|
+
# https://github.com/ruby/ruby/commit/b5f91deee04aa6ccbe07c23c8222b937c22a799b
|
|
12
|
+
# https://github.com/ruby/ruby/commit/93177c1e5c3906abf14472ae0b905d8b5c72ce1b
|
|
13
|
+
#
|
|
14
|
+
# This should probably be removed once there is a Ruby 1.9.2 patch level that
|
|
15
|
+
# includes this fix.
|
|
16
|
+
|
|
17
|
+
require 'uri/common'
|
|
18
|
+
|
|
19
|
+
module URI
|
|
20
|
+
TBLDECWWWCOMP_ = {} unless const_defined?(:TBLDECWWWCOMP_) #:nodoc:
|
|
21
|
+
if TBLDECWWWCOMP_.empty?
|
|
22
|
+
256.times do |i|
|
|
23
|
+
h, l = i>>4, i&15
|
|
24
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
|
25
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
|
26
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
|
27
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
|
28
|
+
end
|
|
29
|
+
TBLDECWWWCOMP_['+'] = ' '
|
|
30
|
+
TBLDECWWWCOMP_.freeze
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.decode_www_form(str, enc=Encoding::UTF_8)
|
|
34
|
+
return [] if str.empty?
|
|
35
|
+
unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
|
|
36
|
+
raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
|
|
37
|
+
end
|
|
38
|
+
ary = []
|
|
39
|
+
$&.scan(/([^=;&]+)=([^;&]*)/) do
|
|
40
|
+
ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
|
|
41
|
+
end
|
|
42
|
+
ary
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.decode_www_form_component(str, enc=Encoding::UTF_8)
|
|
46
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
|
|
47
|
+
str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
remove_const :WFKV_ if const_defined?(:WFKV_)
|
|
51
|
+
WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
|
|
52
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# :stopdoc:
|
|
2
|
+
|
|
3
|
+
require 'uri/common'
|
|
4
|
+
|
|
5
|
+
# Issue:
|
|
6
|
+
# http://bugs.ruby-lang.org/issues/5925
|
|
7
|
+
#
|
|
8
|
+
# Relevant commit:
|
|
9
|
+
# https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1
|
|
10
|
+
|
|
11
|
+
module URI
|
|
12
|
+
256.times do |i|
|
|
13
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
|
14
|
+
end
|
|
15
|
+
TBLENCWWWCOMP_[' '] = '+'
|
|
16
|
+
TBLENCWWWCOMP_.freeze
|
|
17
|
+
|
|
18
|
+
256.times do |i|
|
|
19
|
+
h, l = i>>4, i&15
|
|
20
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
|
21
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
|
22
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
|
23
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
|
24
|
+
end
|
|
25
|
+
TBLDECWWWCOMP_['+'] = ' '
|
|
26
|
+
TBLDECWWWCOMP_.freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# :startdoc:
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
class BodyProxy
|
|
3
|
+
def initialize(body, &block)
|
|
4
|
+
@body, @block, @closed = body, block, false
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def respond_to?(*args)
|
|
8
|
+
return false if args.first.to_s =~ /^to_ary$/
|
|
9
|
+
super or @body.respond_to?(*args)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def close
|
|
13
|
+
return if @closed
|
|
14
|
+
@closed = true
|
|
15
|
+
begin
|
|
16
|
+
@body.close if @body.respond_to? :close
|
|
17
|
+
ensure
|
|
18
|
+
@block.call
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def closed?
|
|
23
|
+
@closed
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# N.B. This method is a special case to address the bug described by #434.
|
|
27
|
+
# We are applying this special case for #each only. Future bugs of this
|
|
28
|
+
# class will be handled by requesting users to patch their ruby
|
|
29
|
+
# implementation, to save adding too many methods in this class.
|
|
30
|
+
def each(*args, &block)
|
|
31
|
+
@body.each(*args, &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def method_missing(*args, &block)
|
|
35
|
+
super if args.first.to_s =~ /^to_ary$/
|
|
36
|
+
@body.__send__(*args, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/rack/builder.rb
CHANGED
|
@@ -4,23 +4,28 @@ module Rack
|
|
|
4
4
|
#
|
|
5
5
|
# Example:
|
|
6
6
|
#
|
|
7
|
-
#
|
|
7
|
+
# require 'rack/lobster'
|
|
8
|
+
# app = Rack::Builder.new do
|
|
8
9
|
# use Rack::CommonLogger
|
|
9
10
|
# use Rack::ShowExceptions
|
|
10
11
|
# map "/lobster" do
|
|
11
12
|
# use Rack::Lint
|
|
12
13
|
# run Rack::Lobster.new
|
|
13
14
|
# end
|
|
14
|
-
#
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# run app
|
|
15
18
|
#
|
|
16
19
|
# Or
|
|
17
20
|
#
|
|
18
21
|
# app = Rack::Builder.app do
|
|
19
22
|
# use Rack::CommonLogger
|
|
20
|
-
# lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
|
|
23
|
+
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
|
|
21
24
|
# end
|
|
22
25
|
#
|
|
23
|
-
#
|
|
26
|
+
# run app
|
|
27
|
+
#
|
|
28
|
+
# +use+ adds middleware to the stack, +run+ dispatches to an application.
|
|
24
29
|
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
|
25
30
|
|
|
26
31
|
class Builder
|
|
@@ -31,9 +36,8 @@ module Rack
|
|
|
31
36
|
if cfgfile[/^#\\(.*)/] && opts
|
|
32
37
|
options = opts.parse! $1.split(/\s+/)
|
|
33
38
|
end
|
|
34
|
-
cfgfile.sub!(/^__END__\n
|
|
35
|
-
app =
|
|
36
|
-
TOPLEVEL_BINDING, config
|
|
39
|
+
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
|
40
|
+
app = new_from_string cfgfile, config
|
|
37
41
|
else
|
|
38
42
|
require config
|
|
39
43
|
app = Object.const_get(::File.basename(config, '.rb').capitalize)
|
|
@@ -41,40 +45,120 @@ module Rack
|
|
|
41
45
|
return app, options
|
|
42
46
|
end
|
|
43
47
|
|
|
44
|
-
def
|
|
45
|
-
|
|
48
|
+
def self.new_from_string(builder_script, file="(rackup)")
|
|
49
|
+
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
|
|
50
|
+
TOPLEVEL_BINDING, file, 0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(default_app = nil,&block)
|
|
54
|
+
@use, @map, @run, @warmup = [], nil, default_app, nil
|
|
46
55
|
instance_eval(&block) if block_given?
|
|
47
56
|
end
|
|
48
57
|
|
|
49
|
-
def self.app(&block)
|
|
50
|
-
self.new(&block).to_app
|
|
58
|
+
def self.app(default_app = nil, &block)
|
|
59
|
+
self.new(default_app, &block).to_app
|
|
51
60
|
end
|
|
52
61
|
|
|
62
|
+
# Specifies middleware to use in a stack.
|
|
63
|
+
#
|
|
64
|
+
# class Middleware
|
|
65
|
+
# def initialize(app)
|
|
66
|
+
# @app = app
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# def call(env)
|
|
70
|
+
# env["rack.some_header"] = "setting an example"
|
|
71
|
+
# @app.call(env)
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# use Middleware
|
|
76
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
|
77
|
+
#
|
|
78
|
+
# All requests through to this application will first be processed by the middleware class.
|
|
79
|
+
# The +call+ method in this example sets an additional environment key which then can be
|
|
80
|
+
# referenced in the application if required.
|
|
53
81
|
def use(middleware, *args, &block)
|
|
54
|
-
@
|
|
82
|
+
if @map
|
|
83
|
+
mapping, @map = @map, nil
|
|
84
|
+
@use << proc { |app| generate_map app, mapping }
|
|
85
|
+
end
|
|
86
|
+
@use << proc { |app| middleware.new(app, *args, &block) }
|
|
55
87
|
end
|
|
56
88
|
|
|
89
|
+
# Takes an argument that is an object that responds to #call and returns a Rack response.
|
|
90
|
+
# The simplest form of this is a lambda object:
|
|
91
|
+
#
|
|
92
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
|
93
|
+
#
|
|
94
|
+
# However this could also be a class:
|
|
95
|
+
#
|
|
96
|
+
# class Heartbeat
|
|
97
|
+
# def self.call(env)
|
|
98
|
+
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
|
|
99
|
+
# end
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# run Heartbeat
|
|
57
103
|
def run(app)
|
|
58
|
-
@
|
|
104
|
+
@run = app
|
|
59
105
|
end
|
|
60
106
|
|
|
107
|
+
# Takes a lambda or block that is used to warm-up the application.
|
|
108
|
+
#
|
|
109
|
+
# warmup do |app|
|
|
110
|
+
# client = Rack::MockRequest.new(app)
|
|
111
|
+
# client.get('/')
|
|
112
|
+
# end
|
|
113
|
+
#
|
|
114
|
+
# use SomeMiddleware
|
|
115
|
+
# run MyApp
|
|
116
|
+
def warmup(prc=nil, &block)
|
|
117
|
+
@warmup = prc || block
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Creates a route within the application.
|
|
121
|
+
#
|
|
122
|
+
# Rack::Builder.app do
|
|
123
|
+
# map '/' do
|
|
124
|
+
# run Heartbeat
|
|
125
|
+
# end
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# The +use+ method can also be used here to specify middleware to run under a specific path:
|
|
129
|
+
#
|
|
130
|
+
# Rack::Builder.app do
|
|
131
|
+
# map '/' do
|
|
132
|
+
# use Middleware
|
|
133
|
+
# run Heartbeat
|
|
134
|
+
# end
|
|
135
|
+
# end
|
|
136
|
+
#
|
|
137
|
+
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
|
|
138
|
+
#
|
|
61
139
|
def map(path, &block)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else
|
|
65
|
-
@ins << {}
|
|
66
|
-
map(path, &block)
|
|
67
|
-
end
|
|
140
|
+
@map ||= {}
|
|
141
|
+
@map[path] = block
|
|
68
142
|
end
|
|
69
143
|
|
|
70
144
|
def to_app
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@
|
|
145
|
+
app = @map ? generate_map(@run, @map) : @run
|
|
146
|
+
fail "missing run or map statement" unless app
|
|
147
|
+
app = @use.reverse.inject(app) { |a,e| e[a] }
|
|
148
|
+
@warmup.call(app) if @warmup
|
|
149
|
+
app
|
|
74
150
|
end
|
|
75
151
|
|
|
76
152
|
def call(env)
|
|
77
153
|
to_app.call(env)
|
|
78
154
|
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def generate_map(default_app, mapping)
|
|
159
|
+
mapped = default_app ? {'/' => default_app} : {}
|
|
160
|
+
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
|
|
161
|
+
URLMap.new(mapped)
|
|
162
|
+
end
|
|
79
163
|
end
|
|
80
164
|
end
|
data/lib/rack/cascade.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module Rack
|
|
2
|
-
# Rack::Cascade tries
|
|
3
|
-
# first response that is not 404 (or in a list of configurable
|
|
2
|
+
# Rack::Cascade tries a request on several apps, and returns the
|
|
3
|
+
# first response that is not 404 or 405 (or in a list of configurable
|
|
4
4
|
# status codes).
|
|
5
5
|
|
|
6
6
|
class Cascade
|
|
7
|
-
NotFound = [404, {}, []]
|
|
7
|
+
NotFound = [404, {CONTENT_TYPE => "text/plain"}, []]
|
|
8
8
|
|
|
9
9
|
attr_reader :apps
|
|
10
10
|
|
|
11
|
-
def initialize(apps, catch=404)
|
|
11
|
+
def initialize(apps, catch=[404, 405])
|
|
12
12
|
@apps = []; @has_app = {}
|
|
13
13
|
apps.each { |app| add app }
|
|
14
14
|
|
|
@@ -19,20 +19,31 @@ module Rack
|
|
|
19
19
|
def call(env)
|
|
20
20
|
result = NotFound
|
|
21
21
|
|
|
22
|
+
last_body = nil
|
|
23
|
+
|
|
22
24
|
@apps.each do |app|
|
|
25
|
+
# The SPEC says that the body must be closed after it has been iterated
|
|
26
|
+
# by the server, or if it is replaced by a middleware action. Cascade
|
|
27
|
+
# replaces the body each time a cascade happens. It is assumed that nil
|
|
28
|
+
# does not respond to close, otherwise the previous application body
|
|
29
|
+
# will be closed. The final application body will not be closed, as it
|
|
30
|
+
# will be passed to the server as a result.
|
|
31
|
+
last_body.close if last_body.respond_to? :close
|
|
32
|
+
|
|
23
33
|
result = app.call(env)
|
|
34
|
+
last_body = result[2]
|
|
24
35
|
break unless @catch.include?(result[0].to_i)
|
|
25
36
|
end
|
|
26
37
|
|
|
27
38
|
result
|
|
28
39
|
end
|
|
29
40
|
|
|
30
|
-
def add
|
|
41
|
+
def add(app)
|
|
31
42
|
@has_app[app] = true
|
|
32
43
|
@apps << app
|
|
33
44
|
end
|
|
34
45
|
|
|
35
|
-
def include?
|
|
46
|
+
def include?(app)
|
|
36
47
|
@has_app.include? app
|
|
37
48
|
end
|
|
38
49
|
|
data/lib/rack/chunked.rb
CHANGED
|
@@ -7,43 +7,63 @@ module Rack
|
|
|
7
7
|
class Chunked
|
|
8
8
|
include Rack::Utils
|
|
9
9
|
|
|
10
|
+
# A body wrapper that emits chunked responses
|
|
11
|
+
class Body
|
|
12
|
+
TERM = "\r\n"
|
|
13
|
+
TAIL = "0#{TERM}#{TERM}"
|
|
14
|
+
|
|
15
|
+
include Rack::Utils
|
|
16
|
+
|
|
17
|
+
def initialize(body)
|
|
18
|
+
@body = body
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each
|
|
22
|
+
term = TERM
|
|
23
|
+
@body.each do |chunk|
|
|
24
|
+
size = bytesize(chunk)
|
|
25
|
+
next if size == 0
|
|
26
|
+
|
|
27
|
+
chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
|
|
28
|
+
yield [size.to_s(16), term, chunk, term].join
|
|
29
|
+
end
|
|
30
|
+
yield TAIL
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def close
|
|
34
|
+
@body.close if @body.respond_to?(:close)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
10
38
|
def initialize(app)
|
|
11
39
|
@app = app
|
|
12
40
|
end
|
|
13
41
|
|
|
42
|
+
# pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
|
|
43
|
+
# a version (nor response headers)
|
|
44
|
+
def chunkable_version?(ver)
|
|
45
|
+
case ver
|
|
46
|
+
when "HTTP/1.0", nil, "HTTP/0.9"
|
|
47
|
+
false
|
|
48
|
+
else
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
14
53
|
def call(env)
|
|
15
54
|
status, headers, body = @app.call(env)
|
|
16
55
|
headers = HeaderHash.new(headers)
|
|
17
56
|
|
|
18
|
-
if env['HTTP_VERSION']
|
|
57
|
+
if ! chunkable_version?(env['HTTP_VERSION']) ||
|
|
19
58
|
STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
|
20
|
-
headers[
|
|
59
|
+
headers[CONTENT_LENGTH] ||
|
|
21
60
|
headers['Transfer-Encoding']
|
|
22
61
|
[status, headers, body]
|
|
23
62
|
else
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def chunk(status, headers, body)
|
|
29
|
-
@body = body
|
|
30
|
-
headers.delete('Content-Length')
|
|
31
|
-
headers['Transfer-Encoding'] = 'chunked'
|
|
32
|
-
[status, headers, self]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def each
|
|
36
|
-
term = "\r\n"
|
|
37
|
-
@body.each do |chunk|
|
|
38
|
-
size = bytesize(chunk)
|
|
39
|
-
next if size == 0
|
|
40
|
-
yield [size.to_s(16), term, chunk, term].join
|
|
63
|
+
headers.delete(CONTENT_LENGTH)
|
|
64
|
+
headers['Transfer-Encoding'] = 'chunked'
|
|
65
|
+
[status, headers, Body.new(body)]
|
|
41
66
|
end
|
|
42
|
-
yield ["0", term, "", term].join
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def close
|
|
46
|
-
@body.close if @body.respond_to?(:close)
|
|
47
67
|
end
|
|
48
68
|
end
|
|
49
69
|
end
|
data/lib/rack/commonlogger.rb
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
require 'rack/body_proxy'
|
|
2
|
+
|
|
1
3
|
module Rack
|
|
2
|
-
# Rack::CommonLogger forwards every request to
|
|
3
|
-
# logs a line in the
|
|
4
|
-
#
|
|
4
|
+
# Rack::CommonLogger forwards every request to the given +app+, and
|
|
5
|
+
# logs a line in the
|
|
6
|
+
# {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
|
|
7
|
+
# to the +logger+.
|
|
8
|
+
#
|
|
9
|
+
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
|
|
10
|
+
# an instance of Rack::NullLogger.
|
|
11
|
+
#
|
|
12
|
+
# +logger+ can be any class, including the standard library Logger, and is
|
|
13
|
+
# expected to have either +write+ or +<<+ method, which accepts the CommonLogger::FORMAT.
|
|
14
|
+
# According to the SPEC, the error stream must also respond to +puts+
|
|
15
|
+
# (which takes a single argument that responds to +to_s+), and +flush+
|
|
16
|
+
# (which is called without arguments in order to make the error appear for
|
|
17
|
+
# sure)
|
|
5
18
|
class CommonLogger
|
|
6
19
|
# Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
|
|
7
|
-
#
|
|
8
|
-
#
|
|
20
|
+
#
|
|
21
|
+
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
|
22
|
+
#
|
|
23
|
+
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
|
|
9
24
|
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
|
|
10
25
|
|
|
11
26
|
def initialize(app, logger=nil)
|
|
@@ -17,7 +32,7 @@ module Rack
|
|
|
17
32
|
began_at = Time.now
|
|
18
33
|
status, header, body = @app.call(env)
|
|
19
34
|
header = Utils::HeaderHash.new(header)
|
|
20
|
-
log(env, status, header, began_at)
|
|
35
|
+
body = BodyProxy.new(body) { log(env, status, header, began_at) }
|
|
21
36
|
[status, header, body]
|
|
22
37
|
end
|
|
23
38
|
|
|
@@ -27,22 +42,30 @@ module Rack
|
|
|
27
42
|
now = Time.now
|
|
28
43
|
length = extract_content_length(header)
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
logger.write FORMAT % [
|
|
45
|
+
msg = FORMAT % [
|
|
32
46
|
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
|
33
47
|
env["REMOTE_USER"] || "-",
|
|
34
|
-
now.strftime("%d/%b/%Y
|
|
35
|
-
env[
|
|
36
|
-
env[
|
|
37
|
-
env[
|
|
48
|
+
now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
|
49
|
+
env[REQUEST_METHOD],
|
|
50
|
+
env[PATH_INFO],
|
|
51
|
+
env[QUERY_STRING].empty? ? "" : "?"+env[QUERY_STRING],
|
|
38
52
|
env["HTTP_VERSION"],
|
|
39
53
|
status.to_s[0..3],
|
|
40
54
|
length,
|
|
41
55
|
now - began_at ]
|
|
56
|
+
|
|
57
|
+
logger = @logger || env['rack.errors']
|
|
58
|
+
# Standard library logger doesn't support write but it supports << which actually
|
|
59
|
+
# calls to write on the log device without formatting
|
|
60
|
+
if logger.respond_to?(:write)
|
|
61
|
+
logger.write(msg)
|
|
62
|
+
else
|
|
63
|
+
logger << msg
|
|
64
|
+
end
|
|
42
65
|
end
|
|
43
66
|
|
|
44
67
|
def extract_content_length(headers)
|
|
45
|
-
value = headers[
|
|
68
|
+
value = headers[CONTENT_LENGTH] or return '-'
|
|
46
69
|
value.to_s == '0' ? '-' : value
|
|
47
70
|
end
|
|
48
71
|
end
|