rack 2.0.9.3 → 2.2.8

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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +740 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +151 -147
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +44 -15
  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 +7 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +6 -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 +33 -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 +59 -34
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +7 -4
  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 +15 -6
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +219 -184
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +2 -1
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +5 -3
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +97 -20
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +55 -56
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +4 -2
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +59 -30
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +222 -63
  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 +13 -9
  64. data/lib/rack/server.rb +95 -24
  65. data/lib/rack/session/abstract/id.rb +34 -21
  66. data/lib/rack/session/cookie.rb +12 -12
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +5 -3
  69. data/lib/rack/show_exceptions.rb +21 -17
  70. data/lib/rack/show_status.rb +9 -9
  71. data/lib/rack/static.rb +23 -11
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +13 -7
  74. data/lib/rack/utils.rb +105 -111
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -28
  78. metadata +36 -179
  79. data/HISTORY.md +0 -520
  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 -107
  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 -520
  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 -721
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1423
  166. data/test/spec_response.rb +0 -528
  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_persisted_secure_secure_session_hash.rb +0 -73
  176. data/test/spec_session_pool.rb +0 -247
  177. data/test/spec_show_exceptions.rb +0 -93
  178. data/test/spec_show_status.rb +0 -104
  179. data/test/spec_static.rb +0 -184
  180. data/test/spec_tempfile_reaper.rb +0 -64
  181. data/test/spec_thin.rb +0 -96
  182. data/test/spec_urlmap.rb +0 -237
  183. data/test/spec_utils.rb +0 -742
  184. data/test/spec_version.rb +0 -11
  185. data/test/spec_webrick.rb +0 -206
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/foo.html +0 -1
  188. data/test/static/index.html +0 -1
  189. data/test/testrequest.rb +0 -78
  190. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  191. 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, 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)
@@ -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,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'fileutils'
3
5
 
4
-
5
6
  module Rack
6
7
 
7
8
  class Server
9
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
8
10
 
9
11
  class Options
10
12
  def parse!(args)
@@ -21,10 +23,6 @@ module Rack
21
23
  lineno += 1
22
24
  }
23
25
 
24
- opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
25
- options[:builder] = line
26
- }
27
-
28
26
  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
29
27
  options[:debug] = true
30
28
  }
@@ -42,12 +40,16 @@ module Rack
42
40
 
43
41
  opts.on("-r", "--require LIBRARY",
44
42
  "require the library, before executing your script") { |library|
45
- options[:require] = library
43
+ (options[:require] ||= []) << library
46
44
  }
47
45
 
48
46
  opts.separator ""
49
47
  opts.separator "Rack options:"
50
- 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|
51
53
  options[:server] = s
52
54
  }
53
55
 
@@ -77,6 +79,24 @@ module Rack
77
79
  options[:pid] = ::File.expand_path(f)
78
80
  }
79
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
+
80
100
  opts.separator ""
81
101
  opts.separator "Common options:"
82
102
 
@@ -114,14 +134,14 @@ module Rack
114
134
 
115
135
  has_options = false
116
136
  server.valid_options.each do |name, description|
117
- 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.
118
138
  info << " -O %-21s %s" % [name, description]
119
139
  has_options = true
120
140
  end
121
141
  return "" if !has_options
122
142
  end
123
143
  info.join("\n")
124
- rescue NameError
144
+ rescue NameError, LoadError
125
145
  return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
126
146
  end
127
147
  end
@@ -152,7 +172,9 @@ module Rack
152
172
 
153
173
  # Options may include:
154
174
  # * :app
155
- # 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
156
178
  # * :config
157
179
  # a rackup configuration file path to load (.ru)
158
180
  # * :environment
@@ -182,6 +204,14 @@ module Rack
182
204
  # add given paths to $LOAD_PATH
183
205
  # * :require
184
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)
185
215
  def initialize(options = nil)
186
216
  @ignore_options = []
187
217
 
@@ -206,12 +236,12 @@ module Rack
206
236
  default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
207
237
 
208
238
  {
209
- :environment => environment,
210
- :pid => nil,
211
- :Port => 9292,
212
- :Host => default_host,
213
- :AccessLog => [],
214
- :config => "config.ru"
239
+ environment: environment,
240
+ pid: nil,
241
+ Port: 9292,
242
+ Host: default_host,
243
+ AccessLog: [],
244
+ config: "config.ru"
215
245
  }
216
246
  end
217
247
 
@@ -222,21 +252,19 @@ module Rack
222
252
  class << self
223
253
  def logging_middleware
224
254
  lambda { |server|
225
- server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
255
+ /CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
226
256
  }
227
257
  end
228
258
 
229
259
  def default_middleware_by_environment
230
- m = Hash.new {|h,k| h[k] = []}
260
+ m = Hash.new {|h, k| h[k] = []}
231
261
  m["deployment"] = [
232
262
  [Rack::ContentLength],
233
- [Rack::Chunked],
234
263
  logging_middleware,
235
264
  [Rack::TempfileReaper]
236
265
  ]
237
266
  m["development"] = [
238
267
  [Rack::ContentLength],
239
- [Rack::Chunked],
240
268
  logging_middleware,
241
269
  [Rack::ShowExceptions],
242
270
  [Rack::Lint],
@@ -255,7 +283,7 @@ module Rack
255
283
  self.class.middleware
256
284
  end
257
285
 
258
- def start &blk
286
+ def start(&block)
259
287
  if options[:warn]
260
288
  $-w = true
261
289
  end
@@ -264,7 +292,7 @@ module Rack
264
292
  $LOAD_PATH.unshift(*includes)
265
293
  end
266
294
 
267
- if library = options[:require]
295
+ Array(options[:require]).each do |library|
268
296
  require library
269
297
  end
270
298
 
@@ -280,7 +308,9 @@ module Rack
280
308
 
281
309
  # Touch the wrapped app, so that the config.ru is loaded before
282
310
  # daemonization (i.e. before chdir, etc).
283
- wrapped_app
311
+ handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
312
+ wrapped_app
313
+ end
284
314
 
285
315
  daemonize_app if options[:daemonize]
286
316
 
@@ -294,7 +324,7 @@ module Rack
294
324
  end
295
325
  end
296
326
 
297
- server.run wrapped_app, options, &blk
327
+ server.run(wrapped_app, **options, &block)
298
328
  end
299
329
 
300
330
  def server
@@ -321,6 +351,44 @@ module Rack
321
351
  app
322
352
  end
323
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
+
324
392
  def build_app_from_string
325
393
  Rack::Builder.new_from_string(self.options[:builder])
326
394
  end
@@ -355,7 +423,10 @@ module Rack
355
423
  end
356
424
 
357
425
  def daemonize_app
426
+ # Cannot be covered as it forks
427
+ # :nocov:
358
428
  Process.daemon
429
+ # :nocov:
359
430
  end
360
431
 
361
432
  def write_pid