rack 2.0.8 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +690 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +152 -148
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +29 -5
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack.rb +67 -73
  13. data/lib/rack/auth/abstract/handler.rb +3 -1
  14. data/lib/rack/auth/abstract/request.rb +1 -1
  15. data/lib/rack/auth/basic.rb +7 -4
  16. data/lib/rack/auth/digest/md5.rb +13 -11
  17. data/lib/rack/auth/digest/nonce.rb +6 -3
  18. data/lib/rack/auth/digest/params.rb +4 -2
  19. data/lib/rack/auth/digest/request.rb +5 -3
  20. data/lib/rack/body_proxy.rb +15 -14
  21. data/lib/rack/builder.rb +116 -23
  22. data/lib/rack/cascade.rb +28 -12
  23. data/lib/rack/chunked.rb +68 -20
  24. data/lib/rack/common_logger.rb +33 -25
  25. data/lib/rack/conditional_get.rb +20 -16
  26. data/lib/rack/config.rb +2 -0
  27. data/lib/rack/content_length.rb +8 -7
  28. data/lib/rack/content_type.rb +5 -4
  29. data/lib/rack/core_ext/regexp.rb +14 -0
  30. data/lib/rack/deflater.rb +59 -34
  31. data/lib/rack/directory.rb +84 -64
  32. data/lib/rack/etag.rb +5 -4
  33. data/lib/rack/events.rb +19 -20
  34. data/lib/rack/file.rb +4 -173
  35. data/lib/rack/files.rb +218 -0
  36. data/lib/rack/handler.rb +7 -2
  37. data/lib/rack/handler/cgi.rb +2 -3
  38. data/lib/rack/handler/fastcgi.rb +4 -4
  39. data/lib/rack/handler/lsws.rb +3 -3
  40. data/lib/rack/handler/scgi.rb +9 -8
  41. data/lib/rack/handler/thin.rb +3 -3
  42. data/lib/rack/handler/webrick.rb +15 -6
  43. data/lib/rack/head.rb +1 -1
  44. data/lib/rack/lint.rb +71 -25
  45. data/lib/rack/lobster.rb +10 -10
  46. data/lib/rack/lock.rb +2 -1
  47. data/lib/rack/logger.rb +2 -0
  48. data/lib/rack/media_type.rb +10 -5
  49. data/lib/rack/method_override.rb +4 -2
  50. data/lib/rack/mime.rb +9 -1
  51. data/lib/rack/mock.rb +97 -20
  52. data/lib/rack/multipart.rb +6 -4
  53. data/lib/rack/multipart/generator.rb +17 -13
  54. data/lib/rack/multipart/parser.rb +54 -56
  55. data/lib/rack/multipart/uploaded_file.rb +15 -7
  56. data/lib/rack/null_logger.rb +2 -0
  57. data/lib/rack/query_parser.rb +53 -28
  58. data/lib/rack/recursive.rb +7 -5
  59. data/lib/rack/reloader.rb +8 -4
  60. data/lib/rack/request.rb +220 -61
  61. data/lib/rack/response.rb +127 -44
  62. data/lib/rack/rewindable_input.rb +4 -3
  63. data/lib/rack/runtime.rb +6 -4
  64. data/lib/rack/sendfile.rb +13 -9
  65. data/lib/rack/server.rb +95 -24
  66. data/lib/rack/session/abstract/id.rb +36 -23
  67. data/lib/rack/session/cookie.rb +11 -12
  68. data/lib/rack/session/memcache.rb +4 -93
  69. data/lib/rack/session/pool.rb +5 -3
  70. data/lib/rack/show_exceptions.rb +21 -17
  71. data/lib/rack/show_status.rb +9 -9
  72. data/lib/rack/static.rb +23 -11
  73. data/lib/rack/tempfile_reaper.rb +1 -1
  74. data/lib/rack/urlmap.rb +12 -6
  75. data/lib/rack/utils.rb +98 -109
  76. data/lib/rack/version.rb +29 -0
  77. data/rack.gemspec +40 -28
  78. metadata +36 -177
  79. data/HISTORY.md +0 -505
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_null_byte +0 -7
  114. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  115. data/test/multipart/filename_with_single_quote +0 -7
  116. data/test/multipart/filename_with_unescaped_percentages +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  119. data/test/multipart/filename_with_unescaped_quotes +0 -6
  120. data/test/multipart/ie +0 -6
  121. data/test/multipart/invalid_character +0 -6
  122. data/test/multipart/mixed_files +0 -21
  123. data/test/multipart/nested +0 -10
  124. data/test/multipart/none +0 -9
  125. data/test/multipart/quoted +0 -15
  126. data/test/multipart/rack-logo.png +0 -0
  127. data/test/multipart/semicolon +0 -6
  128. data/test/multipart/text +0 -15
  129. data/test/multipart/three_files_three_fields +0 -31
  130. data/test/multipart/unity3d_wwwform +0 -11
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -89
  135. data/test/spec_auth_digest.rb +0 -260
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -233
  138. data/test/spec_cascade.rb +0 -63
  139. data/test/spec_cgi.rb +0 -84
  140. data/test/spec_chunked.rb +0 -103
  141. data/test/spec_common_logger.rb +0 -95
  142. data/test/spec_conditional_get.rb +0 -103
  143. data/test/spec_config.rb +0 -23
  144. data/test/spec_content_length.rb +0 -86
  145. data/test/spec_content_type.rb +0 -46
  146. data/test/spec_deflater.rb +0 -375
  147. data/test/spec_directory.rb +0 -148
  148. data/test/spec_etag.rb +0 -108
  149. data/test/spec_events.rb +0 -133
  150. data/test/spec_fastcgi.rb +0 -85
  151. data/test/spec_file.rb +0 -264
  152. data/test/spec_handler.rb +0 -57
  153. data/test/spec_head.rb +0 -46
  154. data/test/spec_lint.rb +0 -515
  155. data/test/spec_lobster.rb +0 -59
  156. data/test/spec_lock.rb +0 -204
  157. data/test/spec_logger.rb +0 -24
  158. data/test/spec_media_type.rb +0 -42
  159. data/test/spec_method_override.rb +0 -110
  160. data/test/spec_mime.rb +0 -51
  161. data/test/spec_mock.rb +0 -359
  162. data/test/spec_multipart.rb +0 -722
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1407
  166. data/test/spec_response.rb +0 -510
  167. data/test/spec_rewindable_input.rb +0 -128
  168. data/test/spec_runtime.rb +0 -50
  169. data/test/spec_sendfile.rb +0 -125
  170. data/test/spec_server.rb +0 -193
  171. data/test/spec_session_abstract_id.rb +0 -31
  172. data/test/spec_session_abstract_session_hash.rb +0 -45
  173. data/test/spec_session_cookie.rb +0 -442
  174. data/test/spec_session_memcache.rb +0 -357
  175. data/test/spec_session_pool.rb +0 -247
  176. data/test/spec_show_exceptions.rb +0 -93
  177. data/test/spec_show_status.rb +0 -104
  178. data/test/spec_static.rb +0 -184
  179. data/test/spec_tempfile_reaper.rb +0 -64
  180. data/test/spec_thin.rb +0 -96
  181. data/test/spec_urlmap.rb +0 -237
  182. data/test/spec_utils.rb +0 -742
  183. data/test/spec_version.rb +0 -11
  184. data/test/spec_webrick.rb +0 -206
  185. data/test/static/another/index.html +0 -1
  186. data/test/static/foo.html +0 -1
  187. data/test/static/index.html +0 -1
  188. data/test/testrequest.rb +0 -78
  189. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  190. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Auth
3
5
  # Rack::Auth::AbstractHandler implements common authentication functionality.
@@ -8,7 +10,7 @@ module Rack
8
10
 
9
11
  attr_accessor :realm
10
12
 
11
- def initialize(app, realm=nil, &authenticator)
13
+ def initialize(app, realm = nil, &authenticator)
12
14
  @app, @realm, @authenticator = app, realm, authenticator
13
15
  end
14
16
 
@@ -1,4 +1,4 @@
1
- require 'rack/request'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
4
  module Auth
@@ -1,5 +1,8 @@
1
- require 'rack/auth/abstract/handler'
2
- require 'rack/auth/abstract/request'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'abstract/handler'
4
+ require_relative 'abstract/request'
5
+ require 'base64'
3
6
 
4
7
  module Rack
5
8
  module Auth
@@ -41,11 +44,11 @@ module Rack
41
44
 
42
45
  class Request < Auth::AbstractRequest
43
46
  def basic?
44
- "basic" == scheme
47
+ "basic" == scheme && credentials.length == 2
45
48
  end
46
49
 
47
50
  def credentials
48
- @credentials ||= params.unpack("m*").first.split(/:/, 2)
51
+ @credentials ||= Base64.decode64(params).split(':', 2)
49
52
  end
50
53
 
51
54
  def username
@@ -1,7 +1,9 @@
1
- require 'rack/auth/abstract/handler'
2
- require 'rack/auth/digest/request'
3
- require 'rack/auth/digest/params'
4
- require 'rack/auth/digest/nonce'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../abstract/handler'
4
+ require_relative 'request'
5
+ require_relative 'params'
6
+ require_relative 'nonce'
5
7
  require 'digest/md5'
6
8
 
7
9
  module Rack
@@ -21,7 +23,7 @@ module Rack
21
23
 
22
24
  attr_writer :passwords_hashed
23
25
 
24
- def initialize(app, realm=nil, opaque=nil, &authenticator)
26
+ def initialize(app, realm = nil, opaque = nil, &authenticator)
25
27
  @passwords_hashed = nil
26
28
  if opaque.nil? and realm.respond_to? :values_at
27
29
  realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
@@ -47,7 +49,7 @@ module Rack
47
49
 
48
50
  if valid?(auth)
49
51
  if auth.nonce.stale?
50
- return unauthorized(challenge(:stale => true))
52
+ return unauthorized(challenge(stale: true))
51
53
  else
52
54
  env['REMOTE_USER'] = auth.username
53
55
 
@@ -61,7 +63,7 @@ module Rack
61
63
 
62
64
  private
63
65
 
64
- QOP = 'auth'.freeze
66
+ QOP = 'auth'
65
67
 
66
68
  def params(hash = {})
67
69
  Params.new do |params|
@@ -106,21 +108,21 @@ module Rack
106
108
  alias :H :md5
107
109
 
108
110
  def KD(secret, data)
109
- H([secret, data] * ':')
111
+ H "#{secret}:#{data}"
110
112
  end
111
113
 
112
114
  def A1(auth, password)
113
- [ auth.username, auth.realm, password ] * ':'
115
+ "#{auth.username}:#{auth.realm}:#{password}"
114
116
  end
115
117
 
116
118
  def A2(auth)
117
- [ auth.method, auth.uri ] * ':'
119
+ "#{auth.method}:#{auth.uri}"
118
120
  end
119
121
 
120
122
  def digest(auth, password)
121
123
  password_hash = passwords_hashed? ? password : H(A1(auth, password))
122
124
 
123
- KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':')
125
+ KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
124
126
  end
125
127
 
126
128
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest/md5'
4
+ require 'base64'
2
5
 
3
6
  module Rack
4
7
  module Auth
@@ -18,7 +21,7 @@ module Rack
18
21
  end
19
22
 
20
23
  def self.parse(string)
21
- new(*string.unpack("m*").first.split(' ', 2))
24
+ new(*Base64.decode64(string).split(' ', 2))
22
25
  end
23
26
 
24
27
  def initialize(timestamp = Time.now, given_digest = nil)
@@ -26,11 +29,11 @@ module Rack
26
29
  end
27
30
 
28
31
  def to_s
29
- [([ @timestamp, digest ] * ' ')].pack("m*").strip
32
+ Base64.encode64("#{@timestamp} #{digest}").strip
30
33
  end
31
34
 
32
35
  def digest
33
- ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':')
36
+ ::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}")
34
37
  end
35
38
 
36
39
  def valid?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Auth
3
5
  module Digest
@@ -38,12 +40,12 @@ module Rack
38
40
 
39
41
  def to_s
40
42
  map do |k, v|
41
- "#{k}=" << (UNQUOTED.include?(k) ? v.to_s : quote(v))
43
+ "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
42
44
  end.join(', ')
43
45
  end
44
46
 
45
47
  def quote(str) # From WEBrick::HTTPUtils
46
- '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
48
+ '"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
47
49
  end
48
50
 
49
51
  end
@@ -1,6 +1,8 @@
1
- require 'rack/auth/abstract/request'
2
- require 'rack/auth/digest/params'
3
- require 'rack/auth/digest/nonce'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../abstract/request'
4
+ require_relative 'params'
5
+ require_relative 'nonce'
4
6
 
5
7
  module Rack
6
8
  module Auth
@@ -1,19 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
4
+ # Proxy for response bodies allowing calling a block when
5
+ # the response body is closed (after the response has been fully
6
+ # sent to the client).
2
7
  class BodyProxy
8
+ # Set the response body to wrap, and the block to call when the
9
+ # response has been fully sent.
3
10
  def initialize(body, &block)
4
11
  @body = body
5
12
  @block = block
6
13
  @closed = false
7
14
  end
8
15
 
9
- def respond_to?(method_name, include_all=false)
10
- case method_name
11
- when :to_ary, 'to_ary'
12
- return false
13
- end
16
+ # Return whether the wrapped body responds to the method.
17
+ def respond_to_missing?(method_name, include_all = false)
14
18
  super or @body.respond_to?(method_name, include_all)
15
19
  end
16
20
 
21
+ # If not already closed, close the wrapped body and
22
+ # then call the block the proxy was initialized with.
17
23
  def close
18
24
  return if @closed
19
25
  @closed = true
@@ -24,21 +30,16 @@ module Rack
24
30
  end
25
31
  end
26
32
 
33
+ # Whether the proxy is closed. The proxy starts as not closed,
34
+ # and becomes closed on the first call to close.
27
35
  def closed?
28
36
  @closed
29
37
  end
30
38
 
31
- # N.B. This method is a special case to address the bug described by #434.
32
- # We are applying this special case for #each only. Future bugs of this
33
- # class will be handled by requesting users to patch their ruby
34
- # implementation, to save adding too many methods in this class.
35
- def each
36
- @body.each { |body| yield body }
37
- end
38
-
39
+ # Delegate missing methods to the wrapped body.
39
40
  def method_missing(method_name, *args, &block)
40
- super if :to_ary == method_name
41
41
  @body.__send__(method_name, *args, &block)
42
42
  end
43
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
43
44
  end
44
45
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Builder implements a small DSL to iteratively construct Rack
3
5
  # applications.
@@ -29,32 +31,102 @@ module Rack
29
31
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
30
32
 
31
33
  class Builder
34
+
35
+ # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
36
+ UTF_8_BOM = '\xef\xbb\xbf'
37
+
38
+ # Parse the given config file to get a Rack application.
39
+ #
40
+ # If the config file ends in +.ru+, it is treated as a
41
+ # rackup file and the contents will be treated as if
42
+ # specified inside a Rack::Builder block, using the given
43
+ # options.
44
+ #
45
+ # If the config file does not end in +.ru+, it is
46
+ # required and Rack will use the basename of the file
47
+ # to guess which constant will be the Rack application to run.
48
+ # The options given will be ignored in this case.
49
+ #
50
+ # Examples:
51
+ #
52
+ # Rack::Builder.parse_file('config.ru')
53
+ # # Rack application built using Rack::Builder.new
54
+ #
55
+ # Rack::Builder.parse_file('app.rb')
56
+ # # requires app.rb, which can be anywhere in Ruby's
57
+ # # load path. After requiring, assumes App constant
58
+ # # contains Rack application
59
+ #
60
+ # Rack::Builder.parse_file('./my_app.rb')
61
+ # # requires ./my_app.rb, which should be in the
62
+ # # process's current directory. After requiring,
63
+ # # assumes MyApp constant contains Rack application
32
64
  def self.parse_file(config, opts = Server::Options.new)
33
- options = {}
34
- if config =~ /\.ru$/
35
- cfgfile = ::File.read(config)
36
- if cfgfile[/^#\\(.*)/] && opts
37
- options = opts.parse! $1.split(/\s+/)
38
- end
39
- cfgfile.sub!(/^__END__\n.*\Z/m, '')
40
- app = new_from_string cfgfile, config
65
+ if config.end_with?('.ru')
66
+ return self.load_file(config, opts)
41
67
  else
42
68
  require config
43
69
  app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
70
+ return app, {}
71
+ end
72
+ end
73
+
74
+ # Load the given file as a rackup file, treating the
75
+ # contents as if specified inside a Rack::Builder block.
76
+ #
77
+ # Treats the first comment at the beginning of a line
78
+ # that starts with a backslash as options similar to
79
+ # options passed on a rackup command line.
80
+ #
81
+ # Ignores content in the file after +__END__+, so that
82
+ # use of +__END__+ will not result in a syntax error.
83
+ #
84
+ # Example config.ru file:
85
+ #
86
+ # $ cat config.ru
87
+ #
88
+ # #\ -p 9393
89
+ #
90
+ # use Rack::ContentLength
91
+ # require './app.rb'
92
+ # run App
93
+ def self.load_file(path, opts = Server::Options.new)
94
+ options = {}
95
+
96
+ cfgfile = ::File.read(path)
97
+ cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
98
+
99
+ if cfgfile[/^#\\(.*)/] && opts
100
+ warn "Parsing options from the first comment line is deprecated!"
101
+ options = opts.parse! $1.split(/\s+/)
44
102
  end
103
+
104
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
105
+ app = new_from_string cfgfile, path
106
+
45
107
  return app, options
46
108
  end
47
109
 
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
110
+ # Evaluate the given +builder_script+ string in the context of
111
+ # a Rack::Builder block, returning a Rack application.
112
+ def self.new_from_string(builder_script, file = "(rackup)")
113
+ # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
114
+ # We cannot use instance_eval(String) as that would resolve constants differently.
115
+ binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
116
+ eval builder_script, binding, file
117
+ builder.to_app
51
118
  end
52
119
 
120
+ # Initialize a new Rack::Builder instance. +default_app+ specifies the
121
+ # default application if +run+ is not called later. If a block
122
+ # is given, it is evaluted in the context of the instance.
53
123
  def initialize(default_app = nil, &block)
54
- @use, @map, @run, @warmup = [], nil, default_app, nil
124
+ @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
55
125
  instance_eval(&block) if block_given?
56
126
  end
57
127
 
128
+ # Create a new Rack::Builder instance and return the Rack application
129
+ # generated from it.
58
130
  def self.app(default_app = nil, &block)
59
131
  self.new(default_app, &block).to_app
60
132
  end
@@ -81,10 +153,11 @@ module Rack
81
153
  def use(middleware, *args, &block)
82
154
  if @map
83
155
  mapping, @map = @map, nil
84
- @use << proc { |app| generate_map app, mapping }
156
+ @use << proc { |app| generate_map(app, mapping) }
85
157
  end
86
158
  @use << proc { |app| middleware.new(app, *args, &block) }
87
159
  end
160
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
88
161
 
89
162
  # Takes an argument that is an object that responds to #call and returns a Rack response.
90
163
  # The simplest form of this is a lambda object:
@@ -104,7 +177,8 @@ module Rack
104
177
  @run = app
105
178
  end
106
179
 
107
- # Takes a lambda or block that is used to warm-up the application.
180
+ # Takes a lambda or block that is used to warm-up the application. This block is called
181
+ # before the Rack application is returned by to_app.
108
182
  #
109
183
  # warmup do |app|
110
184
  # client = Rack::MockRequest.new(app)
@@ -113,51 +187,70 @@ module Rack
113
187
  #
114
188
  # use SomeMiddleware
115
189
  # run MyApp
116
- def warmup(prc=nil, &block)
190
+ def warmup(prc = nil, &block)
117
191
  @warmup = prc || block
118
192
  end
119
193
 
120
- # Creates a route within the application.
194
+ # Creates a route within the application. Routes under the mapped path will be sent to
195
+ # the Rack application specified by run inside the block. Other requests will be sent to the
196
+ # default application specified by run outside the block.
121
197
  #
122
198
  # Rack::Builder.app do
123
- # map '/' do
199
+ # map '/heartbeat' do
124
200
  # run Heartbeat
125
201
  # end
202
+ # run App
126
203
  # end
127
204
  #
128
- # The +use+ method can also be used here to specify middleware to run under a specific path:
205
+ # The +use+ method can also be used inside the block to specify middleware to run under a specific path:
129
206
  #
130
207
  # Rack::Builder.app do
131
- # map '/' do
208
+ # map '/heartbeat' do
132
209
  # use Middleware
133
210
  # run Heartbeat
134
211
  # end
212
+ # run App
135
213
  # end
136
214
  #
137
- # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
215
+ # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
138
216
  #
217
+ # Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement
218
+ # outside the block.
139
219
  def map(path, &block)
140
220
  @map ||= {}
141
221
  @map[path] = block
142
222
  end
143
223
 
224
+ # Freeze the app (set using run) and all middleware instances when building the application
225
+ # in to_app.
226
+ def freeze_app
227
+ @freeze_app = true
228
+ end
229
+
230
+ # Return the Rack application generated by this instance.
144
231
  def to_app
145
232
  app = @map ? generate_map(@run, @map) : @run
146
233
  fail "missing run or map statement" unless app
147
- app = @use.reverse.inject(app) { |a,e| e[a] }
234
+ app.freeze if @freeze_app
235
+ app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
148
236
  @warmup.call(app) if @warmup
149
237
  app
150
238
  end
151
239
 
240
+ # Call the Rack application generated by this builder instance. Note that
241
+ # this rebuilds the Rack application and runs the warmup code (if any)
242
+ # every time it is called, so it should not be used if performance is important.
152
243
  def call(env)
153
244
  to_app.call(env)
154
245
  end
155
246
 
156
247
  private
157
248
 
249
+ # Generate a URLMap instance by generating new Rack applications for each
250
+ # map block in this instance.
158
251
  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 }
252
+ mapped = default_app ? { '/' => default_app } : {}
253
+ mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
161
254
  URLMap.new(mapped)
162
255
  end
163
256
  end