rack 1.0.1 → 2.2.21

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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +829 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +355 -0
  6. data/Rakefile +64 -98
  7. data/SPEC.rdoc +292 -0
  8. data/bin/rackup +3 -174
  9. data/contrib/rack.png +0 -0
  10. data/contrib/rack.svg +150 -0
  11. data/contrib/rack_logo.svg +164 -111
  12. data/contrib/rdoc.css +412 -0
  13. data/example/lobster.ru +2 -0
  14. data/example/protectedlobster.rb +4 -2
  15. data/example/protectedlobster.ru +3 -1
  16. data/lib/rack/auth/abstract/handler.rb +7 -5
  17. data/lib/rack/auth/abstract/request.rb +12 -2
  18. data/lib/rack/auth/basic.rb +6 -4
  19. data/lib/rack/auth/digest/md5.rb +20 -13
  20. data/lib/rack/auth/digest/nonce.rb +6 -4
  21. data/lib/rack/auth/digest/params.rb +11 -12
  22. data/lib/rack/auth/digest/request.rb +15 -12
  23. data/lib/rack/body_proxy.rb +45 -0
  24. data/lib/rack/builder.rb +213 -19
  25. data/lib/rack/cascade.rb +50 -18
  26. data/lib/rack/chunked.rb +95 -27
  27. data/lib/rack/common_logger.rb +84 -0
  28. data/lib/rack/conditional_get.rb +83 -0
  29. data/lib/rack/config.rb +22 -0
  30. data/lib/rack/content_length.rb +19 -10
  31. data/lib/rack/content_type.rb +12 -5
  32. data/lib/rack/core_ext/regexp.rb +14 -0
  33. data/lib/rack/deflater.rb +90 -42
  34. data/lib/rack/directory.rb +118 -72
  35. data/lib/rack/etag.rb +77 -0
  36. data/lib/rack/events.rb +153 -0
  37. data/lib/rack/file.rb +4 -85
  38. data/lib/rack/files.rb +218 -0
  39. data/lib/rack/handler/cgi.rb +22 -24
  40. data/lib/rack/handler/fastcgi.rb +34 -24
  41. data/lib/rack/handler/lsws.rb +16 -15
  42. data/lib/rack/handler/scgi.rb +38 -29
  43. data/lib/rack/handler/thin.rb +23 -7
  44. data/lib/rack/handler/webrick.rb +95 -37
  45. data/lib/rack/handler.rb +49 -14
  46. data/lib/rack/head.rb +19 -13
  47. data/lib/rack/lint.rb +466 -217
  48. data/lib/rack/lobster.rb +19 -14
  49. data/lib/rack/lock.rb +24 -8
  50. data/lib/rack/logger.rb +20 -0
  51. data/lib/rack/media_type.rb +53 -0
  52. data/lib/rack/method_override.rb +52 -0
  53. data/lib/rack/mime.rb +652 -171
  54. data/lib/rack/mock.rb +203 -64
  55. data/lib/rack/multipart/generator.rb +97 -0
  56. data/lib/rack/multipart/parser.rb +440 -0
  57. data/lib/rack/multipart/uploaded_file.rb +41 -0
  58. data/lib/rack/multipart.rb +64 -0
  59. data/lib/rack/null_logger.rb +39 -0
  60. data/lib/rack/query_parser.rb +266 -0
  61. data/lib/rack/recursive.rb +18 -11
  62. data/lib/rack/reloader.rb +13 -5
  63. data/lib/rack/request.rb +607 -191
  64. data/lib/rack/response.rb +245 -110
  65. data/lib/rack/rewindable_input.rb +15 -21
  66. data/lib/rack/runtime.rb +34 -0
  67. data/lib/rack/sendfile.rb +192 -0
  68. data/lib/rack/server.rb +466 -0
  69. data/lib/rack/session/abstract/id.rb +449 -68
  70. data/lib/rack/session/cookie.rb +159 -47
  71. data/lib/rack/session/memcache.rb +4 -103
  72. data/lib/rack/session/pool.rb +39 -49
  73. data/lib/rack/show_exceptions.rb +390 -0
  74. data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
  75. data/lib/rack/static.rb +164 -14
  76. data/lib/rack/tempfile_reaper.rb +22 -0
  77. data/lib/rack/urlmap.rb +60 -18
  78. data/lib/rack/utils.rb +508 -277
  79. data/lib/rack/version.rb +29 -0
  80. data/lib/rack.rb +85 -34
  81. data/rack.gemspec +43 -51
  82. metadata +119 -207
  83. data/KNOWN-ISSUES +0 -18
  84. data/RDOX +0 -428
  85. data/README +0 -364
  86. data/SPEC +0 -164
  87. data/lib/rack/adapter/camping.rb +0 -22
  88. data/lib/rack/auth/openid.rb +0 -480
  89. data/lib/rack/commonlogger.rb +0 -61
  90. data/lib/rack/conditionalget.rb +0 -47
  91. data/lib/rack/handler/evented_mongrel.rb +0 -8
  92. data/lib/rack/handler/mongrel.rb +0 -87
  93. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  94. data/lib/rack/methodoverride.rb +0 -27
  95. data/lib/rack/showexceptions.rb +0 -349
  96. data/test/cgi/lighttpd.conf +0 -20
  97. data/test/cgi/test +0 -9
  98. data/test/cgi/test.fcgi +0 -8
  99. data/test/cgi/test.ru +0 -7
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/empty +0 -10
  102. data/test/multipart/ie +0 -6
  103. data/test/multipart/nested +0 -10
  104. data/test/multipart/none +0 -9
  105. data/test/multipart/semicolon +0 -6
  106. data/test/multipart/text +0 -10
  107. data/test/spec_rack_auth_basic.rb +0 -73
  108. data/test/spec_rack_auth_digest.rb +0 -226
  109. data/test/spec_rack_auth_openid.rb +0 -84
  110. data/test/spec_rack_builder.rb +0 -84
  111. data/test/spec_rack_camping.rb +0 -51
  112. data/test/spec_rack_cascade.rb +0 -50
  113. data/test/spec_rack_cgi.rb +0 -89
  114. data/test/spec_rack_chunked.rb +0 -62
  115. data/test/spec_rack_commonlogger.rb +0 -32
  116. data/test/spec_rack_conditionalget.rb +0 -41
  117. data/test/spec_rack_content_length.rb +0 -43
  118. data/test/spec_rack_content_type.rb +0 -30
  119. data/test/spec_rack_deflater.rb +0 -127
  120. data/test/spec_rack_directory.rb +0 -61
  121. data/test/spec_rack_fastcgi.rb +0 -89
  122. data/test/spec_rack_file.rb +0 -75
  123. data/test/spec_rack_handler.rb +0 -43
  124. data/test/spec_rack_head.rb +0 -30
  125. data/test/spec_rack_lint.rb +0 -521
  126. data/test/spec_rack_lobster.rb +0 -45
  127. data/test/spec_rack_lock.rb +0 -38
  128. data/test/spec_rack_methodoverride.rb +0 -60
  129. data/test/spec_rack_mock.rb +0 -157
  130. data/test/spec_rack_mongrel.rb +0 -189
  131. data/test/spec_rack_recursive.rb +0 -77
  132. data/test/spec_rack_request.rb +0 -504
  133. data/test/spec_rack_response.rb +0 -218
  134. data/test/spec_rack_rewindable_input.rb +0 -118
  135. data/test/spec_rack_session_cookie.rb +0 -82
  136. data/test/spec_rack_session_memcache.rb +0 -240
  137. data/test/spec_rack_session_pool.rb +0 -172
  138. data/test/spec_rack_showexceptions.rb +0 -21
  139. data/test/spec_rack_showstatus.rb +0 -72
  140. data/test/spec_rack_static.rb +0 -37
  141. data/test/spec_rack_thin.rb +0 -91
  142. data/test/spec_rack_urlmap.rb +0 -185
  143. data/test/spec_rack_utils.rb +0 -387
  144. data/test/spec_rack_webrick.rb +0 -130
  145. data/test/testrequest.rb +0 -57
  146. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  147. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/response.rb CHANGED
@@ -1,183 +1,318 @@
1
- require 'rack/request'
2
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
3
4
 
4
5
  module Rack
5
6
  # Rack::Response provides a convenient interface to create a Rack
6
7
  # response.
7
8
  #
8
9
  # It allows setting of headers and cookies, and provides useful
9
- # defaults (a OK response containing HTML).
10
+ # defaults (an OK response with empty headers and body).
10
11
  #
11
12
  # You can use Response#write to iteratively generate your response,
12
13
  # but note that this is buffered by Rack::Response until you call
13
14
  # +finish+. +finish+ however can take a block inside which calls to
14
- # +write+ are syncronous with the Rack response.
15
+ # +write+ are synchronous with the Rack response.
15
16
  #
16
17
  # Your application's +call+ should end returning Response#finish.
17
-
18
18
  class Response
19
- attr_accessor :length
20
-
21
- def initialize(body=[], status=200, header={}, &block)
22
- @status = status
23
- @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
24
- merge(header))
25
-
26
- @writer = lambda { |x| @body << x }
27
- @block = nil
28
- @length = 0
19
+ def self.[](status, headers, body)
20
+ self.new(body, status, headers)
21
+ end
29
22
 
30
- @body = []
23
+ CHUNKED = 'chunked'
24
+ STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
31
25
 
32
- if body.respond_to? :to_str
33
- write body.to_str
34
- elsif body.respond_to?(:each)
35
- body.each { |part|
36
- write part.to_s
37
- }
38
- else
39
- raise TypeError, "stringable or iterable required"
40
- end
41
-
42
- yield self if block_given?
43
- end
26
+ attr_accessor :length, :status, :body
27
+ attr_reader :headers
44
28
 
45
- attr_reader :header
46
- attr_accessor :status, :body
29
+ # @deprecated Use {#headers} instead.
30
+ alias header headers
47
31
 
48
- def [](key)
49
- header[key]
50
- end
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 = {})
43
+ @status = status.to_i
44
+ @headers = Utils::HeaderHash[headers]
51
45
 
52
- def []=(key, value)
53
- header[key] = value
54
- end
46
+ @writer = self.method(:append)
55
47
 
56
- def set_cookie(key, value)
57
- case value
58
- when Hash
59
- domain = "; domain=" + value[:domain] if value[:domain]
60
- path = "; path=" + value[:path] if value[:path]
61
- # According to RFC 2109, we need dashes here.
62
- # N.B.: cgi.rb uses spaces...
63
- expires = "; expires=" + value[:expires].clone.gmtime.
64
- strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
65
- secure = "; secure" if value[:secure]
66
- httponly = "; HttpOnly" if value[:httponly]
67
- value = value[:value]
68
- end
69
- value = [value] unless Array === value
70
- cookie = Utils.escape(key) + "=" +
71
- value.map { |v| Utils.escape v }.join("&") +
72
- "#{domain}#{path}#{expires}#{secure}#{httponly}"
73
-
74
- case self["Set-Cookie"]
75
- when Array
76
- self["Set-Cookie"] << cookie
77
- when String
78
- self["Set-Cookie"] = [self["Set-Cookie"], cookie]
79
- when nil
80
- self["Set-Cookie"] = cookie
81
- end
82
- end
48
+ @block = nil
83
49
 
84
- def delete_cookie(key, value={})
85
- unless Array === self["Set-Cookie"]
86
- self["Set-Cookie"] = [self["Set-Cookie"]].compact
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
59
+ else
60
+ @body = body
61
+ @buffered = false
62
+ @length = 0
87
63
  end
88
64
 
89
- self["Set-Cookie"].reject! { |cookie|
90
- cookie =~ /\A#{Utils.escape(key)}=/
91
- }
92
-
93
- set_cookie(key,
94
- {:value => '', :path => nil, :domain => nil,
95
- :expires => Time.at(0) }.merge(value))
65
+ yield self if block_given?
96
66
  end
97
67
 
98
- def redirect(target, status=302)
68
+ def redirect(target, status = 302)
99
69
  self.status = status
100
- self["Location"] = target
70
+ self.location = target
101
71
  end
102
72
 
103
- def finish(&block)
104
- @block = block
73
+ def chunked?
74
+ CHUNKED == get_header(TRANSFER_ENCODING)
75
+ end
105
76
 
106
- if [204, 304].include?(status.to_i)
107
- header.delete "Content-Type"
108
- [status.to_i, header.to_hash, []]
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.
80
+ def finish(&block)
81
+ if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
82
+ delete_header CONTENT_TYPE
83
+ delete_header CONTENT_LENGTH
84
+ close
85
+ return [@status, @headers, []]
109
86
  else
110
- [status.to_i, header.to_hash, self]
87
+ if block_given?
88
+ @block = block
89
+ return [@status, @headers, self]
90
+ else
91
+ return [@status, @headers, @body]
92
+ end
111
93
  end
112
94
  end
95
+
113
96
  alias to_a finish # For *response
114
97
 
115
98
  def each(&callback)
116
99
  @body.each(&callback)
117
- @writer = callback
118
- @block.call(self) if @block
100
+ @buffered = true
101
+
102
+ if @block
103
+ @writer = callback
104
+ @block.call(self)
105
+ end
119
106
  end
120
107
 
121
108
  # Append to body and update Content-Length.
122
109
  #
123
110
  # NOTE: Do not mix #write and direct #body access!
124
111
  #
125
- def write(str)
126
- s = str.to_s
127
- @length += Rack::Utils.bytesize(s)
128
- @writer.call s
112
+ def write(chunk)
113
+ buffered_body!
129
114
 
130
- header["Content-Length"] = @length.to_s
131
- str
115
+ @writer.call(chunk.to_s)
132
116
  end
133
117
 
134
118
  def close
135
- body.close if body.respond_to?(:close)
119
+ @body.close if @body.respond_to?(:close)
136
120
  end
137
121
 
138
122
  def empty?
139
123
  @block == nil && @body.empty?
140
124
  end
141
125
 
142
- alias headers header
126
+ def has_header?(key); headers.key? key; end
127
+ def get_header(key); headers[key]; end
128
+ def set_header(key, v); headers[key] = v; end
129
+ def delete_header(key); headers.delete key; end
130
+
131
+ alias :[] :get_header
132
+ alias :[]= :set_header
143
133
 
144
134
  module Helpers
145
- def invalid?; @status < 100 || @status >= 600; end
135
+ def invalid?; status < 100 || status >= 600; end
136
+
137
+ def informational?; status >= 100 && status < 200; end
138
+ def successful?; status >= 200 && status < 300; end
139
+ def redirection?; status >= 300 && status < 400; end
140
+ def client_error?; status >= 400 && status < 500; end
141
+ def server_error?; status >= 500 && status < 600; end
142
+
143
+ def ok?; status == 200; end
144
+ def created?; status == 201; end
145
+ def accepted?; status == 202; end
146
+ def no_content?; status == 204; end
147
+ def moved_permanently?; status == 301; end
148
+ def bad_request?; status == 400; end
149
+ def unauthorized?; status == 401; end
150
+ def forbidden?; status == 403; end
151
+ def not_found?; status == 404; end
152
+ def method_not_allowed?; status == 405; end
153
+ def precondition_failed?; status == 412; end
154
+ def unprocessable?; status == 422; end
155
+
156
+ def redirect?; [301, 302, 303, 307, 308].include? status; end
146
157
 
147
- def informational?; @status >= 100 && @status < 200; end
148
- def successful?; @status >= 200 && @status < 300; end
149
- def redirection?; @status >= 300 && @status < 400; end
150
- def client_error?; @status >= 400 && @status < 500; end
151
- def server_error?; @status >= 500 && @status < 600; end
158
+ def include?(header)
159
+ has_header? header
160
+ end
152
161
 
153
- def ok?; @status == 200; end
154
- def forbidden?; @status == 403; end
155
- def not_found?; @status == 404; end
162
+ # Add a header that may have multiple values.
163
+ #
164
+ # Example:
165
+ # response.add_header 'Vary', 'Accept-Encoding'
166
+ # response.add_header 'Vary', 'Cookie'
167
+ #
168
+ # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
169
+ #
170
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
171
+ def add_header(key, v)
172
+ if v.nil?
173
+ get_header key
174
+ elsif has_header? key
175
+ set_header key, "#{get_header key},#{v}"
176
+ else
177
+ set_header key, v
178
+ end
179
+ end
156
180
 
157
- def redirect?; [301, 302, 303, 307].include? @status; end
158
- def empty?; [201, 204, 304].include? @status; end
181
+ # Get the content type of the response.
182
+ def content_type
183
+ get_header CONTENT_TYPE
184
+ end
159
185
 
160
- # Headers
161
- attr_reader :headers, :original_headers
186
+ # Set the content type of the response.
187
+ def content_type=(content_type)
188
+ set_header CONTENT_TYPE, content_type
189
+ end
162
190
 
163
- def include?(header)
164
- !!headers[header]
191
+ def media_type
192
+ MediaType.type(content_type)
165
193
  end
166
194
 
167
- def content_type
168
- headers["Content-Type"]
195
+ def media_type_params
196
+ MediaType.params(content_type)
169
197
  end
170
198
 
171
199
  def content_length
172
- cl = headers["Content-Length"]
200
+ cl = get_header CONTENT_LENGTH
173
201
  cl ? cl.to_i : cl
174
202
  end
175
203
 
176
204
  def location
177
- headers["Location"]
205
+ get_header "Location"
206
+ end
207
+
208
+ def location=(location)
209
+ set_header "Location", location
210
+ end
211
+
212
+ def set_cookie(key, value)
213
+ cookie_header = get_header SET_COOKIE
214
+ set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
215
+ end
216
+
217
+ def delete_cookie(key, value = {})
218
+ set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
219
+ end
220
+
221
+ def set_cookie_header
222
+ get_header SET_COOKIE
223
+ end
224
+
225
+ def set_cookie_header=(v)
226
+ set_header SET_COOKIE, v
227
+ end
228
+
229
+ def cache_control
230
+ get_header CACHE_CONTROL
231
+ end
232
+
233
+ def cache_control=(v)
234
+ set_header CACHE_CONTROL, v
235
+ end
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
+
253
+ def etag
254
+ get_header ETAG
255
+ end
256
+
257
+ def etag=(v)
258
+ set_header ETAG, v
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
178
296
  end
179
297
  end
180
298
 
181
299
  include Helpers
300
+
301
+ class Raw
302
+ include Helpers
303
+
304
+ attr_reader :headers
305
+ attr_accessor :status
306
+
307
+ def initialize(status, headers)
308
+ @status = status
309
+ @headers = headers
310
+ end
311
+
312
+ def has_header?(key); headers.key? key; end
313
+ def get_header(key); headers[key]; end
314
+ def set_header(key, v); headers[key] = v; end
315
+ def delete_header(key); headers.delete key; end
316
+ end
182
317
  end
183
318
  end
@@ -1,3 +1,6 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
1
4
  require 'tempfile'
2
5
 
3
6
  module Rack
@@ -16,29 +19,29 @@ module Rack
16
19
  @rewindable_io = nil
17
20
  @unlinked = false
18
21
  end
19
-
22
+
20
23
  def gets
21
24
  make_rewindable unless @rewindable_io
22
25
  @rewindable_io.gets
23
26
  end
24
-
27
+
25
28
  def read(*args)
26
29
  make_rewindable unless @rewindable_io
27
30
  @rewindable_io.read(*args)
28
31
  end
29
-
32
+
30
33
  def each(&block)
31
34
  make_rewindable unless @rewindable_io
32
35
  @rewindable_io.each(&block)
33
36
  end
34
-
37
+
35
38
  def rewind
36
39
  make_rewindable unless @rewindable_io
37
40
  @rewindable_io.rewind
38
41
  end
39
-
42
+
40
43
  # Closes this RewindableInput object without closing the originally
41
- # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
44
+ # wrapped IO object. Cleans up any temporary resources that this RewindableInput
42
45
  # has created.
43
46
  #
44
47
  # This method may be called multiple times. It does nothing on subsequent calls.
@@ -52,17 +55,8 @@ module Rack
52
55
  @rewindable_io = nil
53
56
  end
54
57
  end
55
-
58
+
56
59
  private
57
-
58
- # Ruby's Tempfile class has a bug. Subclass it and fix it.
59
- class Tempfile < ::Tempfile
60
- def _close
61
- @tmpfile.close if @tmpfile
62
- @data[1] = nil if @data
63
- @tmpfile = nil
64
- end
65
- end
66
60
 
67
61
  def make_rewindable
68
62
  # Buffer all data into a tempfile. Since this tempfile is private to this
@@ -75,16 +69,16 @@ module Rack
75
69
  @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
76
70
  @rewindable_io.binmode
77
71
  if filesystem_has_posix_semantics?
78
- @rewindable_io.unlink
72
+ raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
79
73
  @unlinked = true
80
74
  end
81
-
82
- buffer = ""
75
+
76
+ buffer = "".dup
83
77
  while @io.read(1024 * 4, buffer)
84
78
  entire_buffer_written_out = false
85
79
  while !entire_buffer_written_out
86
80
  written = @rewindable_io.write(buffer)
87
- entire_buffer_written_out = written == buffer.size
81
+ entire_buffer_written_out = written == buffer.bytesize
88
82
  if !entire_buffer_written_out
89
83
  buffer.slice!(0 .. written - 1)
90
84
  end
@@ -92,7 +86,7 @@ module Rack
92
86
  end
93
87
  @rewindable_io.rewind
94
88
  end
95
-
89
+
96
90
  def filesystem_has_posix_semantics?
97
91
  RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
98
92
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Sets an "X-Runtime" response header, indicating the response
5
+ # time of the request, in seconds
6
+ #
7
+ # You can put it right before the application to see the processing
8
+ # time, or before all the other middlewares to include time for them,
9
+ # too.
10
+ class Runtime
11
+ FORMAT_STRING = "%0.6f" # :nodoc:
12
+ HEADER_NAME = "X-Runtime" # :nodoc:
13
+
14
+ def initialize(app, name = nil)
15
+ @app = app
16
+ @header_name = HEADER_NAME
17
+ @header_name += "-#{name}" if name
18
+ end
19
+
20
+ def call(env)
21
+ start_time = Utils.clock_time
22
+ status, headers, body = @app.call(env)
23
+ headers = Utils::HeaderHash[headers]
24
+
25
+ request_time = Utils.clock_time - start_time
26
+
27
+ unless headers.key?(@header_name)
28
+ headers[@header_name] = FORMAT_STRING % request_time
29
+ end
30
+
31
+ [status, headers, body]
32
+ end
33
+ end
34
+ end