rack 2.0.1 → 2.2.17

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 (189) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +795 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +188 -145
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +46 -17
  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/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +1 -1
  14. data/lib/rack/auth/basic.rb +6 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +5 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +5 -3
  19. data/lib/rack/body_proxy.rb +15 -14
  20. data/lib/rack/builder.rb +116 -23
  21. data/lib/rack/cascade.rb +28 -12
  22. data/lib/rack/chunked.rb +68 -20
  23. data/lib/rack/common_logger.rb +37 -25
  24. data/lib/rack/conditional_get.rb +20 -16
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +8 -7
  27. data/lib/rack/content_type.rb +5 -4
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +60 -70
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +8 -5
  32. data/lib/rack/events.rb +19 -20
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +218 -0
  35. data/lib/rack/handler/cgi.rb +2 -3
  36. data/lib/rack/handler/fastcgi.rb +4 -4
  37. data/lib/rack/handler/lsws.rb +3 -3
  38. data/lib/rack/handler/scgi.rb +9 -8
  39. data/lib/rack/handler/thin.rb +3 -3
  40. data/lib/rack/handler/webrick.rb +19 -10
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +221 -186
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +14 -4
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +23 -8
  48. data/lib/rack/method_override.rb +13 -4
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +135 -29
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +85 -68
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +6 -5
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +108 -36
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +232 -60
  60. data/lib/rack/response.rb +127 -44
  61. data/lib/rack/rewindable_input.rb +4 -3
  62. data/lib/rack/runtime.rb +6 -4
  63. data/lib/rack/sendfile.rb +14 -10
  64. data/lib/rack/server.rb +97 -25
  65. data/lib/rack/session/abstract/id.rb +113 -25
  66. data/lib/rack/session/cookie.rb +22 -14
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +24 -10
  69. data/lib/rack/show_exceptions.rb +22 -18
  70. data/lib/rack/show_status.rb +9 -9
  71. data/lib/rack/static.rb +25 -12
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +13 -7
  74. data/lib/rack/utils.rb +135 -123
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -29
  78. metadata +25 -184
  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_percent_escaped_quotes +0 -6
  114. data/test/multipart/filename_with_single_quote +0 -7
  115. data/test/multipart/filename_with_unescaped_percentages +0 -6
  116. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  118. data/test/multipart/filename_with_unescaped_quotes +0 -6
  119. data/test/multipart/ie +0 -6
  120. data/test/multipart/invalid_character +0 -6
  121. data/test/multipart/mixed_files +0 -21
  122. data/test/multipart/nested +0 -10
  123. data/test/multipart/none +0 -9
  124. data/test/multipart/quoted +0 -15
  125. data/test/multipart/rack-logo.png +0 -0
  126. data/test/multipart/semicolon +0 -6
  127. data/test/multipart/text +0 -15
  128. data/test/multipart/three_files_three_fields +0 -31
  129. data/test/multipart/unity3d_wwwform +0 -11
  130. data/test/multipart/webkit +0 -32
  131. data/test/rackup/config.ru +0 -31
  132. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  133. data/test/spec_auth_basic.rb +0 -89
  134. data/test/spec_auth_digest.rb +0 -260
  135. data/test/spec_body_proxy.rb +0 -85
  136. data/test/spec_builder.rb +0 -233
  137. data/test/spec_cascade.rb +0 -63
  138. data/test/spec_cgi.rb +0 -84
  139. data/test/spec_chunked.rb +0 -103
  140. data/test/spec_common_logger.rb +0 -95
  141. data/test/spec_conditional_get.rb +0 -103
  142. data/test/spec_config.rb +0 -23
  143. data/test/spec_content_length.rb +0 -86
  144. data/test/spec_content_type.rb +0 -46
  145. data/test/spec_deflater.rb +0 -365
  146. data/test/spec_directory.rb +0 -148
  147. data/test/spec_etag.rb +0 -108
  148. data/test/spec_events.rb +0 -133
  149. data/test/spec_fastcgi.rb +0 -85
  150. data/test/spec_file.rb +0 -251
  151. data/test/spec_handler.rb +0 -57
  152. data/test/spec_head.rb +0 -46
  153. data/test/spec_lint.rb +0 -515
  154. data/test/spec_lobster.rb +0 -59
  155. data/test/spec_lock.rb +0 -194
  156. data/test/spec_logger.rb +0 -24
  157. data/test/spec_media_type.rb +0 -42
  158. data/test/spec_method_override.rb +0 -83
  159. data/test/spec_mime.rb +0 -51
  160. data/test/spec_mock.rb +0 -342
  161. data/test/spec_multipart.rb +0 -716
  162. data/test/spec_null_logger.rb +0 -21
  163. data/test/spec_recursive.rb +0 -75
  164. data/test/spec_request.rb +0 -1393
  165. data/test/spec_response.rb +0 -510
  166. data/test/spec_rewindable_input.rb +0 -128
  167. data/test/spec_runtime.rb +0 -50
  168. data/test/spec_sendfile.rb +0 -125
  169. data/test/spec_server.rb +0 -193
  170. data/test/spec_session_abstract_id.rb +0 -31
  171. data/test/spec_session_abstract_session_hash.rb +0 -28
  172. data/test/spec_session_cookie.rb +0 -442
  173. data/test/spec_session_memcache.rb +0 -320
  174. data/test/spec_session_pool.rb +0 -210
  175. data/test/spec_show_exceptions.rb +0 -80
  176. data/test/spec_show_status.rb +0 -104
  177. data/test/spec_static.rb +0 -184
  178. data/test/spec_tempfile_reaper.rb +0 -64
  179. data/test/spec_thin.rb +0 -96
  180. data/test/spec_urlmap.rb +0 -237
  181. data/test/spec_utils.rb +0 -742
  182. data/test/spec_version.rb +0 -11
  183. data/test/spec_webrick.rb +0 -208
  184. data/test/static/another/index.html +0 -1
  185. data/test/static/foo.html +0 -1
  186. data/test/static/index.html +0 -1
  187. data/test/testrequest.rb +0 -78
  188. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  189. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/response.rb CHANGED
@@ -1,7 +1,5 @@
1
- require 'rack/request'
2
- require 'rack/utils'
3
- require 'rack/body_proxy'
4
- require 'rack/media_type'
1
+ # frozen_string_literal: true
2
+
5
3
  require 'time'
6
4
 
7
5
  module Rack
@@ -17,38 +15,57 @@ module Rack
17
15
  # +write+ are synchronous with the Rack response.
18
16
  #
19
17
  # Your application's +call+ should end returning Response#finish.
20
-
21
18
  class Response
19
+ def self.[](status, headers, body)
20
+ self.new(body, status, headers)
21
+ end
22
+
23
+ CHUNKED = 'chunked'
24
+ STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
25
+
22
26
  attr_accessor :length, :status, :body
23
- attr_reader :header
24
- alias headers header
27
+ attr_reader :headers
25
28
 
26
- CHUNKED = 'chunked'.freeze
29
+ # @deprecated Use {#headers} instead.
30
+ alias header headers
27
31
 
28
- def initialize(body=[], status=200, header={})
32
+ # Initialize the response object with the specified body, status
33
+ # and headers.
34
+ #
35
+ # @param body [nil, #each, #to_str] the response body.
36
+ # @param status [Integer] the integer status as defined by the
37
+ # HTTP protocol RFCs.
38
+ # @param headers [#each] a list of key-value header pairs which
39
+ # conform to the HTTP protocol RFCs.
40
+ #
41
+ # Providing a body which responds to #to_str is legacy behaviour.
42
+ def initialize(body = nil, status = 200, headers = {})
29
43
  @status = status.to_i
30
- @header = Utils::HeaderHash.new.merge(header)
44
+ @headers = Utils::HeaderHash[headers]
31
45
 
32
- @writer = lambda { |x| @body << x }
33
- @block = nil
34
- @length = 0
46
+ @writer = self.method(:append)
35
47
 
36
- @body = []
48
+ @block = nil
37
49
 
38
- if body.respond_to? :to_str
39
- write body.to_str
40
- elsif body.respond_to?(:each)
41
- body.each { |part|
42
- write part.to_s
43
- }
50
+ # Keep track of whether we have expanded the user supplied body.
51
+ if body.nil?
52
+ @body = []
53
+ @buffered = true
54
+ @length = 0
55
+ elsif body.respond_to?(:to_str)
56
+ @body = [body]
57
+ @buffered = true
58
+ @length = body.to_str.bytesize
44
59
  else
45
- raise TypeError, "stringable or iterable required"
60
+ @body = body
61
+ @buffered = false
62
+ @length = 0
46
63
  end
47
64
 
48
- yield self if block_given?
65
+ yield self if block_given?
49
66
  end
50
67
 
51
- def redirect(target, status=302)
68
+ def redirect(target, status = 302)
52
69
  self.status = status
53
70
  self.location = target
54
71
  end
@@ -57,42 +74,49 @@ module Rack
57
74
  CHUNKED == get_header(TRANSFER_ENCODING)
58
75
  end
59
76
 
77
+ # Generate a response array consistent with the requirements of the SPEC.
78
+ # @return [Array] a 3-tuple suitable of `[status, headers, body]`
79
+ # which is suitable to be returned from the middleware `#call(env)` method.
60
80
  def finish(&block)
61
- @block = block
62
-
63
- if [204, 205, 304].include?(status.to_i)
81
+ if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
64
82
  delete_header CONTENT_TYPE
65
83
  delete_header CONTENT_LENGTH
66
84
  close
67
- [status.to_i, header, []]
85
+ return [@status, @headers, []]
68
86
  else
69
- [status.to_i, header, BodyProxy.new(self){}]
87
+ if block_given?
88
+ @block = block
89
+ return [@status, @headers, self]
90
+ else
91
+ return [@status, @headers, @body]
92
+ end
70
93
  end
71
94
  end
95
+
72
96
  alias to_a finish # For *response
73
- alias to_ary finish # For implicit-splat on Ruby 1.9.2
74
97
 
75
98
  def each(&callback)
76
99
  @body.each(&callback)
77
- @writer = callback
78
- @block.call(self) if @block
100
+ @buffered = true
101
+
102
+ if @block
103
+ @writer = callback
104
+ @block.call(self)
105
+ end
79
106
  end
80
107
 
81
108
  # Append to body and update Content-Length.
82
109
  #
83
110
  # NOTE: Do not mix #write and direct #body access!
84
111
  #
85
- def write(str)
86
- s = str.to_s
87
- @length += s.bytesize unless chunked?
88
- @writer.call s
112
+ def write(chunk)
113
+ buffered_body!
89
114
 
90
- set_header(CONTENT_LENGTH, @length.to_s) unless chunked?
91
- str
115
+ @writer.call(chunk.to_s)
92
116
  end
93
117
 
94
118
  def close
95
- body.close if body.respond_to?(:close)
119
+ @body.close if @body.respond_to?(:close)
96
120
  end
97
121
 
98
122
  def empty?
@@ -144,7 +168,7 @@ module Rack
144
168
  # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
145
169
  #
146
170
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
147
- def add_header key, v
171
+ def add_header(key, v)
148
172
  if v.nil?
149
173
  get_header key
150
174
  elsif has_header? key
@@ -154,10 +178,16 @@ module Rack
154
178
  end
155
179
  end
156
180
 
181
+ # Get the content type of the response.
157
182
  def content_type
158
183
  get_header CONTENT_TYPE
159
184
  end
160
185
 
186
+ # Set the content type of the response.
187
+ def content_type=(content_type)
188
+ set_header CONTENT_TYPE, content_type
189
+ end
190
+
161
191
  def media_type
162
192
  MediaType.type(content_type)
163
193
  end
@@ -184,7 +214,7 @@ module Rack
184
214
  set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
185
215
  end
186
216
 
187
- def delete_cookie(key, value={})
217
+ def delete_cookie(key, value = {})
188
218
  set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
189
219
  end
190
220
 
@@ -192,7 +222,7 @@ module Rack
192
222
  get_header SET_COOKIE
193
223
  end
194
224
 
195
- def set_cookie_header= v
225
+ def set_cookie_header=(v)
196
226
  set_header SET_COOKIE, v
197
227
  end
198
228
 
@@ -200,17 +230,70 @@ module Rack
200
230
  get_header CACHE_CONTROL
201
231
  end
202
232
 
203
- def cache_control= v
233
+ def cache_control=(v)
204
234
  set_header CACHE_CONTROL, v
205
235
  end
206
236
 
237
+ # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
238
+ def do_not_cache!
239
+ set_header CACHE_CONTROL, "no-cache, must-revalidate"
240
+ set_header EXPIRES, Time.now.httpdate
241
+ end
242
+
243
+ # Specify that the content should be cached.
244
+ # @param duration [Integer] The number of seconds until the cache expires.
245
+ # @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
246
+ def cache!(duration = 3600, directive: "public")
247
+ unless headers[CACHE_CONTROL] =~ /no-cache/
248
+ set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
249
+ set_header EXPIRES, (Time.now + duration).httpdate
250
+ end
251
+ end
252
+
207
253
  def etag
208
254
  get_header ETAG
209
255
  end
210
256
 
211
- def etag= v
257
+ def etag=(v)
212
258
  set_header ETAG, v
213
259
  end
260
+
261
+ protected
262
+
263
+ def buffered_body!
264
+ return if @buffered
265
+
266
+ if @body.is_a?(Array)
267
+ # The user supplied body was an array:
268
+ @body = @body.compact
269
+ @body.each do |part|
270
+ @length += part.to_s.bytesize
271
+ end
272
+ else
273
+ # Turn the user supplied body into a buffered array:
274
+ body = @body
275
+ @body = Array.new
276
+
277
+ body.each do |part|
278
+ @writer.call(part.to_s)
279
+ end
280
+
281
+ body.close if body.respond_to?(:close)
282
+ end
283
+
284
+ @buffered = true
285
+ end
286
+
287
+ def append(chunk)
288
+ @body << chunk
289
+
290
+ unless chunked?
291
+ @length += chunk.bytesize
292
+ set_header(CONTENT_LENGTH, @length.to_s)
293
+ end
294
+
295
+ return chunk
296
+ end
214
297
  end
215
298
 
216
299
  include Helpers
@@ -221,7 +304,7 @@ module Rack
221
304
  attr_reader :headers
222
305
  attr_accessor :status
223
306
 
224
- def initialize status, headers
307
+ def initialize(status, headers)
225
308
  @status = status
226
309
  @headers = headers
227
310
  end
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'tempfile'
3
- require 'rack/utils'
4
5
 
5
6
  module Rack
6
7
  # Class which can make any IO object rewindable, including non-rewindable ones. It does
@@ -40,7 +41,7 @@ module Rack
40
41
  end
41
42
 
42
43
  # Closes this RewindableInput object without closing the originally
43
- # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
44
+ # wrapped IO object. Cleans up any temporary resources that this RewindableInput
44
45
  # has created.
45
46
  #
46
47
  # This method may be called multiple times. It does nothing on subsequent calls.
@@ -72,7 +73,7 @@ module Rack
72
73
  @unlinked = true
73
74
  end
74
75
 
75
- buffer = ""
76
+ buffer = "".dup
76
77
  while @io.read(1024 * 4, buffer)
77
78
  entire_buffer_written_out = false
78
79
  while !entire_buffer_written_out
data/lib/rack/runtime.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
4
  # Sets an "X-Runtime" response header, indicating the response
@@ -8,8 +8,8 @@ module Rack
8
8
  # time, or before all the other middlewares to include time for them,
9
9
  # too.
10
10
  class Runtime
11
- FORMAT_STRING = "%0.6f".freeze # :nodoc:
12
- HEADER_NAME = "X-Runtime".freeze # :nodoc:
11
+ FORMAT_STRING = "%0.6f" # :nodoc:
12
+ HEADER_NAME = "X-Runtime" # :nodoc:
13
13
 
14
14
  def initialize(app, name = nil)
15
15
  @app = app
@@ -20,9 +20,11 @@ module Rack
20
20
  def call(env)
21
21
  start_time = Utils.clock_time
22
22
  status, headers, body = @app.call(env)
23
+ headers = Utils::HeaderHash[headers]
24
+
23
25
  request_time = Utils.clock_time - start_time
24
26
 
25
- unless headers.has_key?(@header_name)
27
+ unless headers.key?(@header_name)
26
28
  headers[@header_name] = FORMAT_STRING % request_time
27
29
  end
28
30
 
data/lib/rack/sendfile.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'rack/file'
2
- require 'rack/body_proxy'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Rack
5
4
 
@@ -14,7 +13,7 @@ module Rack
14
13
  #
15
14
  # In order to take advantage of this middleware, the response body must
16
15
  # respond to +to_path+ and the request must include an X-Sendfile-Type
17
- # header. Rack::File and other components implement +to_path+ so there's
16
+ # header. Rack::Files and other components implement +to_path+ so there's
18
17
  # rarely anything you need to do in your application. The X-Sendfile-Type
19
18
  # header is typically set in your web servers configuration. The following
20
19
  # sections attempt to document
@@ -53,7 +52,7 @@ module Rack
53
52
  # that it maps to. The middleware performs a simple substitution on the
54
53
  # resulting path.
55
54
  #
56
- # See Also: http://wiki.codemongers.com/NginxXSendfile
55
+ # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
57
56
  #
58
57
  # === lighttpd
59
58
  #
@@ -99,7 +98,7 @@ module Rack
99
98
  # will be matched with case indifference.
100
99
 
101
100
  class Sendfile
102
- def initialize(app, variation=nil, mappings=[])
101
+ def initialize(app, variation = nil, mappings = [])
103
102
  @app = app
104
103
  @variation = variation
105
104
  @mappings = mappings.map do |internal, external|
@@ -115,7 +114,8 @@ module Rack
115
114
  path = ::File.expand_path(body.to_path)
116
115
  if url = map_accel_path(env, path)
117
116
  headers[CONTENT_LENGTH] = '0'
118
- headers[type] = url
117
+ # '?' must be percent-encoded because it is not query string but a part of path
118
+ headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
119
119
  obody = body
120
120
  body = Rack::BodyProxy.new([]) do
121
121
  obody.close if obody.respond_to?(:close)
@@ -133,7 +133,7 @@ module Rack
133
133
  end
134
134
  when '', nil
135
135
  else
136
- env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
136
+ env[RACK_ERRORS].puts "Unknown x-sendfile variation: #{type.inspect}"
137
137
  end
138
138
  end
139
139
  [status, headers, body]
@@ -147,11 +147,15 @@ module Rack
147
147
  end
148
148
 
149
149
  def map_accel_path(env, path)
150
- if mapping = @mappings.find { |internal,_| internal =~ path }
150
+ if mapping = @mappings.find { |internal, _| internal =~ path }
151
151
  path.sub(*mapping)
152
152
  elsif mapping = env['HTTP_X_ACCEL_MAPPING']
153
- internal, external = mapping.split('=', 2).map(&:strip)
154
- path.sub(/^#{internal}/i, external)
153
+ mapping.split(',').map(&:strip).each do |m|
154
+ internal, external = m.split('=', 2).map(&:strip)
155
+ new_path = path.sub(/^#{internal}/i, external)
156
+ return new_path unless path == new_path
157
+ end
158
+ path
155
159
  end
156
160
  end
157
161
  end
data/lib/rack/server.rb CHANGED
@@ -1,9 +1,12 @@
1
- require 'optparse'
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'optparse'
4
+ require 'fileutils'
3
5
 
4
6
  module Rack
5
7
 
6
8
  class Server
9
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
7
10
 
8
11
  class Options
9
12
  def parse!(args)
@@ -20,10 +23,6 @@ module Rack
20
23
  lineno += 1
21
24
  }
22
25
 
23
- opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
24
- options[:builder] = line
25
- }
26
-
27
26
  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
28
27
  options[:debug] = true
29
28
  }
@@ -41,12 +40,16 @@ module Rack
41
40
 
42
41
  opts.on("-r", "--require LIBRARY",
43
42
  "require the library, before executing your script") { |library|
44
- options[:require] = library
43
+ (options[:require] ||= []) << library
45
44
  }
46
45
 
47
46
  opts.separator ""
48
47
  opts.separator "Rack options:"
49
- opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
48
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
49
+ options[:builder] = line
50
+ }
51
+
52
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s|
50
53
  options[:server] = s
51
54
  }
52
55
 
@@ -76,6 +79,24 @@ module Rack
76
79
  options[:pid] = ::File.expand_path(f)
77
80
  }
78
81
 
82
+ opts.separator ""
83
+ opts.separator "Profiling options:"
84
+
85
+ opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e|
86
+ options[:heapfile] = e
87
+ end
88
+
89
+ opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e|
90
+ options[:profile_file] = e
91
+ end
92
+
93
+ opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e|
94
+ { cpu: true, wall: true, object: true }.fetch(e.to_sym) do
95
+ raise OptionParser::InvalidOption, "unknown profile mode: #{e}"
96
+ end
97
+ options[:profile_mode] = e.to_sym
98
+ end
99
+
79
100
  opts.separator ""
80
101
  opts.separator "Common options:"
81
102
 
@@ -113,14 +134,14 @@ module Rack
113
134
 
114
135
  has_options = false
115
136
  server.valid_options.each do |name, description|
116
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
137
+ next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own.
117
138
  info << " -O %-21s %s" % [name, description]
118
139
  has_options = true
119
140
  end
120
141
  return "" if !has_options
121
142
  end
122
143
  info.join("\n")
123
- rescue NameError
144
+ rescue NameError, LoadError
124
145
  return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
125
146
  end
126
147
  end
@@ -151,7 +172,9 @@ module Rack
151
172
 
152
173
  # Options may include:
153
174
  # * :app
154
- # a rack application to run (overrides :config)
175
+ # a rack application to run (overrides :config and :builder)
176
+ # * :builder
177
+ # a string to evaluate a Rack::Builder from
155
178
  # * :config
156
179
  # a rackup configuration file path to load (.ru)
157
180
  # * :environment
@@ -181,6 +204,14 @@ module Rack
181
204
  # add given paths to $LOAD_PATH
182
205
  # * :require
183
206
  # require the given libraries
207
+ #
208
+ # Additional options for profiling app initialization include:
209
+ # * :heapfile
210
+ # location for ObjectSpace.dump_all to write the output to
211
+ # * :profile_file
212
+ # location for CPU/Memory (StackProf) profile output (defaults to a tempfile)
213
+ # * :profile_mode
214
+ # StackProf profile mode (cpu|wall|object)
184
215
  def initialize(options = nil)
185
216
  @ignore_options = []
186
217
 
@@ -205,12 +236,12 @@ module Rack
205
236
  default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
206
237
 
207
238
  {
208
- :environment => environment,
209
- :pid => nil,
210
- :Port => 9292,
211
- :Host => default_host,
212
- :AccessLog => [],
213
- :config => "config.ru"
239
+ environment: environment,
240
+ pid: nil,
241
+ Port: 9292,
242
+ Host: default_host,
243
+ AccessLog: [],
244
+ config: "config.ru"
214
245
  }
215
246
  end
216
247
 
@@ -221,21 +252,19 @@ module Rack
221
252
  class << self
222
253
  def logging_middleware
223
254
  lambda { |server|
224
- server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
255
+ /CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
225
256
  }
226
257
  end
227
258
 
228
259
  def default_middleware_by_environment
229
- m = Hash.new {|h,k| h[k] = []}
260
+ m = Hash.new {|h, k| h[k] = []}
230
261
  m["deployment"] = [
231
262
  [Rack::ContentLength],
232
- [Rack::Chunked],
233
263
  logging_middleware,
234
264
  [Rack::TempfileReaper]
235
265
  ]
236
266
  m["development"] = [
237
267
  [Rack::ContentLength],
238
- [Rack::Chunked],
239
268
  logging_middleware,
240
269
  [Rack::ShowExceptions],
241
270
  [Rack::Lint],
@@ -254,7 +283,7 @@ module Rack
254
283
  self.class.middleware
255
284
  end
256
285
 
257
- def start &blk
286
+ def start(&block)
258
287
  if options[:warn]
259
288
  $-w = true
260
289
  end
@@ -263,7 +292,7 @@ module Rack
263
292
  $LOAD_PATH.unshift(*includes)
264
293
  end
265
294
 
266
- if library = options[:require]
295
+ Array(options[:require]).each do |library|
267
296
  require library
268
297
  end
269
298
 
@@ -279,7 +308,9 @@ module Rack
279
308
 
280
309
  # Touch the wrapped app, so that the config.ru is loaded before
281
310
  # daemonization (i.e. before chdir, etc).
282
- wrapped_app
311
+ handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
312
+ wrapped_app
313
+ end
283
314
 
284
315
  daemonize_app if options[:daemonize]
285
316
 
@@ -293,7 +324,7 @@ module Rack
293
324
  end
294
325
  end
295
326
 
296
- server.run wrapped_app, options, &blk
327
+ server.run(wrapped_app, **options, &block)
297
328
  end
298
329
 
299
330
  def server
@@ -320,6 +351,44 @@ module Rack
320
351
  app
321
352
  end
322
353
 
354
+ def handle_profiling(heapfile, profile_mode, filename)
355
+ if heapfile
356
+ require "objspace"
357
+ ObjectSpace.trace_object_allocations_start
358
+ yield
359
+ GC.start
360
+ ::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) }
361
+ exit
362
+ end
363
+
364
+ if profile_mode
365
+ require "stackprof"
366
+ require "tempfile"
367
+
368
+ make_profile_name(filename) do |filename|
369
+ ::File.open(filename, "w") do |f|
370
+ StackProf.run(mode: profile_mode, out: f) do
371
+ yield
372
+ end
373
+ puts "Profile written to: #{filename}"
374
+ end
375
+ end
376
+ exit
377
+ end
378
+
379
+ yield
380
+ end
381
+
382
+ def make_profile_name(filename)
383
+ if filename
384
+ yield filename
385
+ else
386
+ ::Dir::Tmpname.create("profile.dump") do |tmpname, _, _|
387
+ yield tmpname
388
+ end
389
+ end
390
+ end
391
+
323
392
  def build_app_from_string
324
393
  Rack::Builder.new_from_string(self.options[:builder])
325
394
  end
@@ -354,12 +423,15 @@ module Rack
354
423
  end
355
424
 
356
425
  def daemonize_app
426
+ # Cannot be covered as it forks
427
+ # :nocov:
357
428
  Process.daemon
429
+ # :nocov:
358
430
  end
359
431
 
360
432
  def write_pid
361
433
  ::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") }
362
- at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
434
+ at_exit { ::FileUtils.rm_f(options[:pid]) }
363
435
  rescue Errno::EEXIST
364
436
  check_pid!
365
437
  retry