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.
Files changed (212) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +1 -1
  3. data/HISTORY.md +375 -0
  4. data/KNOWN-ISSUES +23 -0
  5. data/README.rdoc +312 -0
  6. data/Rakefile +124 -0
  7. data/SPEC +125 -32
  8. data/contrib/rack.png +0 -0
  9. data/contrib/rack.svg +150 -0
  10. data/contrib/rack_logo.svg +1 -1
  11. data/contrib/rdoc.css +412 -0
  12. data/example/protectedlobster.rb +1 -1
  13. data/lib/rack/auth/abstract/handler.rb +4 -4
  14. data/lib/rack/auth/abstract/request.rb +7 -5
  15. data/lib/rack/auth/basic.rb +1 -1
  16. data/lib/rack/auth/digest/md5.rb +7 -3
  17. data/lib/rack/auth/digest/nonce.rb +1 -1
  18. data/lib/rack/auth/digest/params.rb +7 -9
  19. data/lib/rack/auth/digest/request.rb +10 -9
  20. data/lib/rack/backports/uri/common_18.rb +56 -0
  21. data/lib/rack/backports/uri/common_192.rb +52 -0
  22. data/lib/rack/backports/uri/common_193.rb +29 -0
  23. data/lib/rack/body_proxy.rb +39 -0
  24. data/lib/rack/builder.rb +106 -22
  25. data/lib/rack/cascade.rb +17 -6
  26. data/lib/rack/chunked.rb +44 -24
  27. data/lib/rack/commonlogger.rb +36 -13
  28. data/lib/rack/conditionalget.rb +49 -17
  29. data/lib/rack/config.rb +5 -0
  30. data/lib/rack/content_length.rb +14 -6
  31. data/lib/rack/content_type.rb +7 -1
  32. data/lib/rack/deflater.rb +73 -15
  33. data/lib/rack/directory.rb +18 -8
  34. data/lib/rack/etag.rb +59 -9
  35. data/lib/rack/file.rb +106 -44
  36. data/lib/rack/handler/cgi.rb +11 -11
  37. data/lib/rack/handler/fastcgi.rb +18 -6
  38. data/lib/rack/handler/lsws.rb +2 -4
  39. data/lib/rack/handler/mongrel.rb +22 -6
  40. data/lib/rack/handler/scgi.rb +16 -8
  41. data/lib/rack/handler/thin.rb +19 -4
  42. data/lib/rack/handler/webrick.rb +72 -19
  43. data/lib/rack/handler.rb +47 -14
  44. data/lib/rack/head.rb +10 -2
  45. data/lib/rack/lint.rb +260 -75
  46. data/lib/rack/lobster.rb +13 -8
  47. data/lib/rack/lock.rb +13 -3
  48. data/lib/rack/logger.rb +0 -2
  49. data/lib/rack/methodoverride.rb +27 -8
  50. data/lib/rack/mime.rb +625 -167
  51. data/lib/rack/mock.rb +78 -53
  52. data/lib/rack/multipart/generator.rb +93 -0
  53. data/lib/rack/multipart/parser.rb +253 -0
  54. data/lib/rack/multipart/uploaded_file.rb +34 -0
  55. data/lib/rack/multipart.rb +34 -0
  56. data/lib/rack/nulllogger.rb +21 -2
  57. data/lib/rack/recursive.rb +10 -5
  58. data/lib/rack/reloader.rb +3 -2
  59. data/lib/rack/request.rb +201 -74
  60. data/lib/rack/response.rb +41 -28
  61. data/lib/rack/rewindable_input.rb +15 -11
  62. data/lib/rack/runtime.rb +16 -3
  63. data/lib/rack/sendfile.rb +47 -29
  64. data/lib/rack/server.rb +223 -47
  65. data/lib/rack/session/abstract/id.rb +289 -30
  66. data/lib/rack/session/cookie.rb +133 -44
  67. data/lib/rack/session/memcache.rb +30 -56
  68. data/lib/rack/session/pool.rb +19 -43
  69. data/lib/rack/showexceptions.rb +53 -15
  70. data/lib/rack/showstatus.rb +14 -7
  71. data/lib/rack/static.rb +124 -12
  72. data/lib/rack/tempfile_reaper.rb +22 -0
  73. data/lib/rack/urlmap.rb +49 -15
  74. data/lib/rack/utils/okjson.rb +600 -0
  75. data/lib/rack/utils.rb +363 -361
  76. data/lib/rack.rb +17 -23
  77. data/rack.gemspec +11 -20
  78. data/test/builder/anything.rb +5 -0
  79. data/test/builder/comment.ru +4 -0
  80. data/test/builder/end.ru +5 -0
  81. data/test/builder/line.ru +1 -0
  82. data/test/builder/options.ru +2 -0
  83. data/test/cgi/assets/folder/test.js +1 -0
  84. data/test/cgi/assets/fonts/font.eot +1 -0
  85. data/test/cgi/assets/images/image.png +1 -0
  86. data/test/cgi/assets/index.html +1 -0
  87. data/test/cgi/assets/javascripts/app.js +1 -0
  88. data/test/cgi/assets/stylesheets/app.css +1 -0
  89. data/test/cgi/lighttpd.conf +26 -0
  90. data/test/cgi/rackup_stub.rb +6 -0
  91. data/test/cgi/sample_rackup.ru +5 -0
  92. data/test/cgi/test +9 -0
  93. data/test/cgi/test+directory/test+file +1 -0
  94. data/test/cgi/test.fcgi +8 -0
  95. data/test/cgi/test.ru +5 -0
  96. data/test/gemloader.rb +10 -0
  97. data/test/multipart/bad_robots +259 -0
  98. data/test/multipart/binary +0 -0
  99. data/test/multipart/content_type_and_no_filename +6 -0
  100. data/test/multipart/empty +10 -0
  101. data/test/multipart/fail_16384_nofile +814 -0
  102. data/test/multipart/file1.txt +1 -0
  103. data/test/multipart/filename_and_modification_param +7 -0
  104. data/test/multipart/filename_and_no_name +6 -0
  105. data/test/multipart/filename_with_escaped_quotes +6 -0
  106. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  107. data/test/multipart/filename_with_null_byte +7 -0
  108. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  109. data/test/multipart/filename_with_unescaped_percentages +6 -0
  110. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  111. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  112. data/test/multipart/filename_with_unescaped_quotes +6 -0
  113. data/test/multipart/ie +6 -0
  114. data/test/multipart/invalid_character +6 -0
  115. data/test/multipart/mixed_files +21 -0
  116. data/test/multipart/nested +10 -0
  117. data/test/multipart/none +9 -0
  118. data/test/multipart/semicolon +6 -0
  119. data/test/multipart/text +15 -0
  120. data/test/multipart/three_files_three_fields +31 -0
  121. data/test/multipart/webkit +32 -0
  122. data/test/rackup/config.ru +31 -0
  123. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  124. data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
  125. data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
  126. data/test/spec_body_proxy.rb +85 -0
  127. data/test/spec_builder.rb +223 -0
  128. data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
  129. data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
  130. data/test/spec_chunked.rb +101 -0
  131. data/test/spec_commonlogger.rb +93 -0
  132. data/test/spec_conditionalget.rb +102 -0
  133. data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
  134. data/test/spec_content_length.rb +85 -0
  135. data/test/spec_content_type.rb +45 -0
  136. data/test/spec_deflater.rb +339 -0
  137. data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
  138. data/test/spec_etag.rb +107 -0
  139. data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
  140. data/test/spec_file.rb +221 -0
  141. data/test/spec_handler.rb +72 -0
  142. data/test/spec_head.rb +45 -0
  143. data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
  144. data/test/spec_lobster.rb +58 -0
  145. data/test/spec_lock.rb +164 -0
  146. data/test/spec_logger.rb +23 -0
  147. data/test/spec_methodoverride.rb +95 -0
  148. data/test/spec_mime.rb +51 -0
  149. data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
  150. data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
  151. data/test/spec_multipart.rb +600 -0
  152. data/test/spec_nulllogger.rb +20 -0
  153. data/test/spec_recursive.rb +72 -0
  154. data/test/spec_request.rb +1227 -0
  155. data/test/spec_response.rb +407 -0
  156. data/test/spec_rewindable_input.rb +118 -0
  157. data/test/spec_runtime.rb +49 -0
  158. data/test/spec_sendfile.rb +130 -0
  159. data/test/spec_server.rb +167 -0
  160. data/test/spec_session_abstract_id.rb +53 -0
  161. data/test/spec_session_cookie.rb +410 -0
  162. data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
  163. data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
  164. data/test/spec_showexceptions.rb +85 -0
  165. data/test/spec_showstatus.rb +103 -0
  166. data/test/spec_static.rb +145 -0
  167. data/test/spec_tempfile_reaper.rb +63 -0
  168. data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
  169. data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
  170. data/test/spec_utils.rb +647 -0
  171. data/test/spec_version.rb +17 -0
  172. data/test/spec_webrick.rb +184 -0
  173. data/test/static/another/index.html +1 -0
  174. data/test/static/index.html +1 -0
  175. data/test/testrequest.rb +78 -0
  176. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  177. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  178. metadata +220 -239
  179. data/RDOX +0 -0
  180. data/README +0 -592
  181. data/lib/rack/adapter/camping.rb +0 -22
  182. data/test/spec_auth.rb +0 -57
  183. data/test/spec_rack_builder.rb +0 -84
  184. data/test/spec_rack_camping.rb +0 -55
  185. data/test/spec_rack_chunked.rb +0 -62
  186. data/test/spec_rack_commonlogger.rb +0 -61
  187. data/test/spec_rack_conditionalget.rb +0 -41
  188. data/test/spec_rack_content_length.rb +0 -43
  189. data/test/spec_rack_content_type.rb +0 -30
  190. data/test/spec_rack_deflater.rb +0 -127
  191. data/test/spec_rack_etag.rb +0 -17
  192. data/test/spec_rack_file.rb +0 -75
  193. data/test/spec_rack_handler.rb +0 -43
  194. data/test/spec_rack_head.rb +0 -30
  195. data/test/spec_rack_lobster.rb +0 -45
  196. data/test/spec_rack_lock.rb +0 -38
  197. data/test/spec_rack_logger.rb +0 -21
  198. data/test/spec_rack_methodoverride.rb +0 -60
  199. data/test/spec_rack_nulllogger.rb +0 -13
  200. data/test/spec_rack_recursive.rb +0 -77
  201. data/test/spec_rack_request.rb +0 -594
  202. data/test/spec_rack_response.rb +0 -221
  203. data/test/spec_rack_rewindable_input.rb +0 -118
  204. data/test/spec_rack_runtime.rb +0 -35
  205. data/test/spec_rack_sendfile.rb +0 -86
  206. data/test/spec_rack_session_cookie.rb +0 -92
  207. data/test/spec_rack_showexceptions.rb +0 -21
  208. data/test/spec_rack_showstatus.rb +0 -72
  209. data/test/spec_rack_static.rb +0 -37
  210. data/test/spec_rack_utils.rb +0 -557
  211. data/test/spec_rack_webrick.rb +0 -130
  212. 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).inject(new) do |header, param|
7
+ Params[*split_header_value(str).map do |param|
8
8
  k, v = param.split('=', 2)
9
- header[k] = dequote(v)
10
- header
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 = ['qop', 'nc', 'stale']
37
+ UNQUOTED = ['nc', 'stale']
39
38
 
40
39
  def to_s
41
- inject([]) do |parts, (k, v)|
42
- parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
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['REQUEST_METHOD']
10
+ @env['rack.methodoverride.original_method'] || @env[REQUEST_METHOD]
12
11
  end
13
12
 
14
13
  def digest?
15
- :digest == scheme
14
+ "digest" == scheme
16
15
  end
17
16
 
18
17
  def correct_uri?
19
- (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri
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 method_missing(sym)
31
- if params.has_key? key = sym.to_s
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
- # app = Rack::Builder.new {
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
- # +use+ adds a middleware to the stack, +run+ dispatches to an application.
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 = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_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 initialize(&block)
45
- @ins = []
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
- @ins << lambda { |app| middleware.new(app, *args, &block) }
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
- @ins << app #lambda { |nothing| app }
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
- if @ins.last.kind_of? Hash
63
- @ins.last[path] = self.class.new(&block).to_app
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
- @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
72
- inner_app = @ins.last
73
- @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
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 an request on several apps, and returns the
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 app
41
+ def add(app)
31
42
  @has_app[app] = true
32
43
  @apps << app
33
44
  end
34
45
 
35
- def include? app
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'] == 'HTTP/1.0' ||
57
+ if ! chunkable_version?(env['HTTP_VERSION']) ||
19
58
  STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
20
- headers['Content-Length'] ||
59
+ headers[CONTENT_LENGTH] ||
21
60
  headers['Transfer-Encoding']
22
61
  [status, headers, body]
23
62
  else
24
- dup.chunk(status, headers, body)
25
- end
26
- end
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
@@ -1,11 +1,26 @@
1
+ require 'rack/body_proxy'
2
+
1
3
  module Rack
2
- # Rack::CommonLogger forwards every request to an +app+ given, and
3
- # logs a line in the Apache common log format to the +logger+, or
4
- # rack.errors by default.
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
- # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
8
- # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
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
- logger = @logger || env['rack.errors']
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 %H:%M:%S"),
35
- env["REQUEST_METHOD"],
36
- env["PATH_INFO"],
37
- env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
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['Content-Length'] or return '-'
68
+ value = headers[CONTENT_LENGTH] or return '-'
46
69
  value.to_s == '0' ? '-' : value
47
70
  end
48
71
  end