rack 2.2.17 → 3.2.0

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.

Potentially problematic release.


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

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +501 -70
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +376 -0
  6. data/SPEC.rdoc +243 -277
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +5 -1
  9. data/lib/rack/auth/basic.rb +1 -3
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +108 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +22 -17
  15. data/lib/rack/conditional_get.rb +20 -16
  16. data/lib/rack/constants.rb +68 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +17 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +8 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +817 -648
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/media_type.rb +6 -7
  29. data/lib/rack/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -300
  32. data/lib/rack/mock_request.rb +161 -0
  33. data/lib/rack/mock_response.rb +147 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +241 -95
  36. data/lib/rack/multipart/uploaded_file.rb +45 -4
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +116 -121
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +269 -141
  43. data/lib/rack/response.rb +151 -66
  44. data/lib/rack/rewindable_input.rb +27 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +30 -25
  47. data/lib/rack/show_exceptions.rb +25 -6
  48. data/lib/rack/show_status.rb +17 -9
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +228 -238
  53. data/lib/rack/version.rb +3 -15
  54. data/lib/rack.rb +13 -90
  55. metadata +15 -41
  56. data/README.rdoc +0 -347
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -53
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/logger.rb +0 -20
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -90
  87. data/rack.gemspec +0 -46
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Represents a 400 Bad Request error when input data fails to meet the
5
+ # requirements.
6
+ module BadRequest
7
+ end
8
+ end
@@ -15,7 +15,12 @@ module Rack
15
15
 
16
16
  # Return whether the wrapped body responds to the method.
17
17
  def respond_to_missing?(method_name, include_all = false)
18
- super or @body.respond_to?(method_name, include_all)
18
+ case method_name
19
+ when :to_str
20
+ false
21
+ else
22
+ super or @body.respond_to?(method_name, include_all)
23
+ end
19
24
  end
20
25
 
21
26
  # If not already closed, close the wrapped body and
@@ -24,7 +29,7 @@ module Rack
24
29
  return if @closed
25
30
  @closed = true
26
31
  begin
27
- @body.close if @body.respond_to? :close
32
+ @body.close if @body.respond_to?(:close)
28
33
  ensure
29
34
  @block.call
30
35
  end
@@ -38,8 +43,21 @@ module Rack
38
43
 
39
44
  # Delegate missing methods to the wrapped body.
40
45
  def method_missing(method_name, *args, &block)
41
- @body.__send__(method_name, *args, &block)
46
+ case method_name
47
+ when :to_str
48
+ super
49
+ when :to_ary
50
+ begin
51
+ @body.__send__(method_name, *args, &block)
52
+ ensure
53
+ close
54
+ end
55
+ else
56
+ @body.__send__(method_name, *args, &block)
57
+ end
42
58
  end
59
+ # :nocov:
43
60
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
61
+ # :nocov:
44
62
  end
45
63
  end
data/lib/rack/builder.rb CHANGED
@@ -1,35 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'urlmap'
4
+
5
+ module Rack; end
6
+ Rack::BUILDER_TOPLEVEL_BINDING = ->(builder){builder.instance_eval{binding}}
7
+
3
8
  module Rack
4
- # Rack::Builder implements a small DSL to iteratively construct Rack
5
- # applications.
9
+ # Rack::Builder provides a domain-specific language (DSL) to construct Rack
10
+ # applications. It is primarily used to parse +config.ru+ files which
11
+ # instantiate several middleware and a final application which are hosted
12
+ # by a Rack-compatible web server.
6
13
  #
7
14
  # Example:
8
15
  #
9
- # require 'rack/lobster'
10
- # app = Rack::Builder.new do
11
- # use Rack::CommonLogger
12
- # use Rack::ShowExceptions
13
- # map "/lobster" do
14
- # use Rack::Lint
15
- # run Rack::Lobster.new
16
- # end
17
- # end
16
+ # app = Rack::Builder.new do
17
+ # use Rack::CommonLogger
18
+ # map "/ok" do
19
+ # run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
20
+ # end
21
+ # end
18
22
  #
19
- # run app
23
+ # run app
20
24
  #
21
25
  # Or
22
26
  #
23
- # app = Rack::Builder.app do
24
- # use Rack::CommonLogger
25
- # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
26
- # end
27
+ # app = Rack::Builder.app do
28
+ # use Rack::CommonLogger
29
+ # run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
30
+ # end
27
31
  #
28
- # run app
32
+ # run app
29
33
  #
30
34
  # +use+ adds middleware to the stack, +run+ dispatches to an application.
31
35
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
32
-
33
36
  class Builder
34
37
 
35
38
  # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
@@ -39,13 +42,11 @@ module Rack
39
42
  #
40
43
  # If the config file ends in +.ru+, it is treated as a
41
44
  # rackup file and the contents will be treated as if
42
- # specified inside a Rack::Builder block, using the given
43
- # options.
45
+ # specified inside a Rack::Builder block.
44
46
  #
45
47
  # If the config file does not end in +.ru+, it is
46
48
  # required and Rack will use the basename of the file
47
49
  # to guess which constant will be the Rack application to run.
48
- # The options given will be ignored in this case.
49
50
  #
50
51
  # Examples:
51
52
  #
@@ -55,29 +56,24 @@ module Rack
55
56
  # Rack::Builder.parse_file('app.rb')
56
57
  # # requires app.rb, which can be anywhere in Ruby's
57
58
  # # load path. After requiring, assumes App constant
58
- # # contains Rack application
59
+ # # is a Rack application
59
60
  #
60
61
  # Rack::Builder.parse_file('./my_app.rb')
61
62
  # # requires ./my_app.rb, which should be in the
62
63
  # # process's current directory. After requiring,
63
- # # assumes MyApp constant contains Rack application
64
- def self.parse_file(config, opts = Server::Options.new)
65
- if config.end_with?('.ru')
66
- return self.load_file(config, opts)
64
+ # # assumes MyApp constant is a Rack application
65
+ def self.parse_file(path, **options)
66
+ if path.end_with?('.ru')
67
+ return self.load_file(path, **options)
67
68
  else
68
- require config
69
- app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
70
- return app, {}
69
+ require path
70
+ return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
71
71
  end
72
72
  end
73
73
 
74
74
  # Load the given file as a rackup file, treating the
75
75
  # contents as if specified inside a Rack::Builder block.
76
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
77
  # Ignores content in the file after +__END__+, so that
82
78
  # use of +__END__+ will not result in a syntax error.
83
79
  #
@@ -85,46 +81,56 @@ module Rack
85
81
  #
86
82
  # $ cat config.ru
87
83
  #
88
- # #\ -p 9393
89
- #
90
84
  # use Rack::ContentLength
91
85
  # require './app.rb'
92
86
  # 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
87
+ def self.load_file(path, **options)
88
+ config = ::File.read(path)
89
+ config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8
98
90
 
99
- if cfgfile[/^#\\(.*)/] && opts
100
- warn "Parsing options from the first comment line is deprecated!"
101
- options = opts.parse! $1.split(/\s+/)
91
+ if config[/^#\\(.*)/]
92
+ fail "Parsing options from the first comment line is no longer supported: #{path}"
102
93
  end
103
94
 
104
- cfgfile.sub!(/^__END__\n.*\Z/m, '')
105
- app = new_from_string cfgfile, path
95
+ config.sub!(/^__END__\n.*\Z/m, '')
106
96
 
107
- return app, options
97
+ return new_from_string(config, path, **options)
108
98
  end
109
99
 
110
100
  # Evaluate the given +builder_script+ string in the context of
111
101
  # a Rack::Builder block, returning a Rack application.
112
- def self.new_from_string(builder_script, file = "(rackup)")
102
+ def self.new_from_string(builder_script, path = "(rackup)", **options)
103
+ builder = self.new(**options)
104
+
113
105
  # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
114
106
  # 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
107
+ binding = BUILDER_TOPLEVEL_BINDING.call(builder)
108
+ eval(builder_script, binding, path)
109
+
110
+ return builder.to_app
118
111
  end
119
112
 
120
113
  # Initialize a new Rack::Builder instance. +default_app+ specifies the
121
114
  # default application if +run+ is not called later. If a block
122
- # is given, it is evaluted in the context of the instance.
123
- def initialize(default_app = nil, &block)
124
- @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
115
+ # is given, it is evaluated in the context of the instance.
116
+ def initialize(default_app = nil, **options, &block)
117
+ @use = []
118
+ @map = nil
119
+ @run = default_app
120
+ @warmup = nil
121
+ @freeze_app = false
122
+ @options = options
123
+
125
124
  instance_eval(&block) if block_given?
126
125
  end
127
126
 
127
+ # Any options provided to the Rack::Builder instance at initialization.
128
+ # These options can be server-specific. Some general options are:
129
+ #
130
+ # * +:isolation+: One of +process+, +thread+ or +fiber+. The execution
131
+ # isolation model to use.
132
+ attr :options
133
+
128
134
  # Create a new Rack::Builder instance and return the Rack application
129
135
  # generated from it.
130
136
  def self.app(default_app = nil, &block)
@@ -145,7 +151,7 @@ module Rack
145
151
  # end
146
152
  #
147
153
  # use Middleware
148
- # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
154
+ # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
149
155
  #
150
156
  # All requests through to this application will first be processed by the middleware class.
151
157
  # The +call+ method in this example sets an additional environment key which then can be
@@ -156,25 +162,42 @@ module Rack
156
162
  @use << proc { |app| generate_map(app, mapping) }
157
163
  end
158
164
  @use << proc { |app| middleware.new(app, *args, &block) }
165
+
166
+ nil
159
167
  end
168
+ # :nocov:
160
169
  ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
170
+ # :nocov:
161
171
 
162
- # Takes an argument that is an object that responds to #call and returns a Rack response.
163
- # The simplest form of this is a lambda object:
172
+ # Takes a block or argument that is an object that responds to #call and
173
+ # returns a Rack response.
174
+ #
175
+ # You can use a block:
176
+ #
177
+ # run do |env|
178
+ # [200, { "content-type" => "text/plain" }, ["Hello World!"]]
179
+ # end
180
+ #
181
+ # You can also provide a lambda:
164
182
  #
165
- # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
183
+ # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
166
184
  #
167
- # However this could also be a class:
185
+ # You can also provide a class instance:
168
186
  #
169
187
  # class Heartbeat
170
- # def self.call(env)
171
- # [200, { "Content-Type" => "text/plain" }, ["OK"]]
188
+ # def call(env)
189
+ # [200, { "content-type" => "text/plain" }, ["OK"]]
172
190
  # end
173
191
  # end
174
192
  #
175
- # run Heartbeat
176
- def run(app)
177
- @run = app
193
+ # run Heartbeat.new
194
+ #
195
+ def run(app = nil, &block)
196
+ raise ArgumentError, "Both app and block given!" if app && block_given?
197
+
198
+ @run = app || block
199
+
200
+ nil
178
201
  end
179
202
 
180
203
  # Takes a lambda or block that is used to warm-up the application. This block is called
@@ -195,21 +218,35 @@ module Rack
195
218
  # the Rack application specified by run inside the block. Other requests will be sent to the
196
219
  # default application specified by run outside the block.
197
220
  #
198
- # Rack::Builder.app do
221
+ # class App
222
+ # def call(env)
223
+ # [200, {'content-type' => 'text/plain'}, ["Hello World"]]
224
+ # end
225
+ # end
226
+ #
227
+ # class Heartbeat
228
+ # def call(env)
229
+ # [200, { "content-type" => "text/plain" }, ["OK"]]
230
+ # end
231
+ # end
232
+ #
233
+ # app = Rack::Builder.app do
199
234
  # map '/heartbeat' do
200
- # run Heartbeat
235
+ # run Heartbeat.new
201
236
  # end
202
- # run App
237
+ # run App.new
203
238
  # end
204
239
  #
240
+ # run app
241
+ #
205
242
  # The +use+ method can also be used inside the block to specify middleware to run under a specific path:
206
243
  #
207
- # Rack::Builder.app do
244
+ # app = Rack::Builder.app do
208
245
  # map '/heartbeat' do
209
246
  # use Middleware
210
- # run Heartbeat
247
+ # run Heartbeat.new
211
248
  # end
212
- # run App
249
+ # run App.new
213
250
  # end
214
251
  #
215
252
  # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
@@ -219,6 +256,8 @@ module Rack
219
256
  def map(path, &block)
220
257
  @map ||= {}
221
258
  @map[path] = block
259
+
260
+ nil
222
261
  end
223
262
 
224
263
  # Freeze the app (set using run) and all middleware instances when building the application
data/lib/rack/cascade.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+
3
5
  module Rack
4
6
  # Rack::Cascade tries a request on several apps, and returns the
5
7
  # first response that is not 404 or 405 (or in a list of configured
@@ -7,9 +9,6 @@ module Rack
7
9
  # status codes, return the last response.
8
10
 
9
11
  class Cascade
10
- # deprecated, no longer used
11
- NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
12
-
13
12
  # An array of applications to try in order.
14
13
  attr_reader :apps
15
14
 
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'body_proxy'
6
+ require_relative 'request'
7
+
3
8
  module Rack
4
9
  # Rack::CommonLogger forwards every request to the given +app+, and
5
10
  # logs a line in the
@@ -35,36 +40,36 @@ module Rack
35
40
  # cause the request not to be logged.
36
41
  def call(env)
37
42
  began_at = Utils.clock_time
38
- status, headers, body = @app.call(env)
39
- headers = Utils::HeaderHash[headers]
40
- body = BodyProxy.new(body) { log(env, status, headers, began_at) }
41
- [status, headers, body]
43
+ status, headers, body = response = @app.call(env)
44
+
45
+ response[2] = BodyProxy.new(body) { log(env, status, headers, began_at) }
46
+ response
42
47
  end
43
48
 
44
49
  private
45
50
 
46
51
  # Log the request to the configured logger.
47
- def log(env, status, header, began_at)
48
- length = extract_content_length(header)
52
+ def log(env, status, response_headers, began_at)
53
+ request = Rack::Request.new(env)
54
+ length = extract_content_length(response_headers)
49
55
 
50
- msg = FORMAT % [
51
- env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
52
- env["REMOTE_USER"] || "-",
56
+ msg = sprintf(FORMAT,
57
+ request.ip || "-",
58
+ request.get_header("REMOTE_USER") || "-",
53
59
  Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
54
- env[REQUEST_METHOD],
55
- env[SCRIPT_NAME],
56
- env[PATH_INFO],
57
- env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
58
- env[SERVER_PROTOCOL],
60
+ request.request_method,
61
+ request.script_name,
62
+ request.path_info,
63
+ request.query_string.empty? ? "" : "?#{request.query_string}",
64
+ request.get_header(SERVER_PROTOCOL),
59
65
  status.to_s[0..3],
60
66
  length,
61
- Utils.clock_time - began_at ]
67
+ Utils.clock_time - began_at)
62
68
 
63
69
  msg.gsub!(/[^[:print:]]/) { |c| sprintf("\\x%x", c.ord) }
64
70
  msg[-1] = "\n"
65
71
 
66
- logger = @logger || env[RACK_ERRORS]
67
-
72
+ logger = @logger || request.get_header(RACK_ERRORS)
68
73
  # Standard library logger doesn't support write but it supports << which actually
69
74
  # calls to write on the log device without formatting
70
75
  if logger.respond_to?(:write)
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'body_proxy'
6
+
3
7
  module Rack
4
8
 
5
- # Middleware that enables conditional GET using If-None-Match and
6
- # If-Modified-Since. The application should set either or both of the
7
- # Last-Modified or Etag response headers according to RFC 2616. When
9
+ # Middleware that enables conditional GET using if-none-match and
10
+ # if-modified-since. The application should set either or both of the
11
+ # last-modified or etag response headers according to RFC 2616. When
8
12
  # either of the conditions is met, the response body is set to be zero
9
13
  # length and the response status is set to 304 Not Modified.
10
14
  #
@@ -24,18 +28,18 @@ module Rack
24
28
  def call(env)
25
29
  case env[REQUEST_METHOD]
26
30
  when "GET", "HEAD"
27
- status, headers, body = @app.call(env)
28
- headers = Utils::HeaderHash[headers]
31
+ status, headers, body = response = @app.call(env)
32
+
29
33
  if status == 200 && fresh?(env, headers)
30
- status = 304
34
+ response[0] = 304
31
35
  headers.delete(CONTENT_TYPE)
32
36
  headers.delete(CONTENT_LENGTH)
33
- original_body = body
34
- body = Rack::BodyProxy.new([]) do
35
- original_body.close if original_body.respond_to?(:close)
36
- end
37
+
38
+ # We are done with the body:
39
+ body.close if body.respond_to?(:close)
40
+ response[2] = []
37
41
  end
38
- [status, headers, body]
42
+ response
39
43
  else
40
44
  @app.call(env)
41
45
  end
@@ -46,7 +50,7 @@ module Rack
46
50
  # Return whether the response has not been modified since the
47
51
  # last request.
48
52
  def fresh?(env, headers)
49
- # If-None-Match has priority over If-Modified-Since per RFC 7232
53
+ # if-none-match has priority over if-modified-since per RFC 7232
50
54
  if none_match = env['HTTP_IF_NONE_MATCH']
51
55
  etag_matches?(none_match, headers)
52
56
  elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since))
@@ -54,16 +58,16 @@ module Rack
54
58
  end
55
59
  end
56
60
 
57
- # Whether the ETag response header matches the If-None-Match request header.
61
+ # Whether the etag response header matches the if-none-match request header.
58
62
  # If so, the request has not been modified.
59
63
  def etag_matches?(none_match, headers)
60
- headers['ETag'] == none_match
64
+ headers[ETAG] == none_match
61
65
  end
62
66
 
63
- # Whether the Last-Modified response header matches the If-Modified-Since
67
+ # Whether the last-modified response header matches the if-modified-since
64
68
  # request header. If so, the request has not been modified.
65
69
  def modified_since?(modified_since, headers)
66
- last_modified = to_rfc2822(headers['Last-Modified']) and
70
+ last_modified = to_rfc2822(headers['last-modified']) and
67
71
  modified_since >= last_modified
68
72
  end
69
73
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Request env keys
5
+ HTTP_HOST = 'HTTP_HOST'
6
+ HTTP_PORT = 'HTTP_PORT'
7
+ HTTPS = 'HTTPS'
8
+ PATH_INFO = 'PATH_INFO'
9
+ REQUEST_METHOD = 'REQUEST_METHOD'
10
+ REQUEST_PATH = 'REQUEST_PATH'
11
+ SCRIPT_NAME = 'SCRIPT_NAME'
12
+ QUERY_STRING = 'QUERY_STRING'
13
+ SERVER_PROTOCOL = 'SERVER_PROTOCOL'
14
+ SERVER_NAME = 'SERVER_NAME'
15
+ SERVER_PORT = 'SERVER_PORT'
16
+ HTTP_COOKIE = 'HTTP_COOKIE'
17
+
18
+ # Response Header Keys
19
+ CACHE_CONTROL = 'cache-control'
20
+ CONTENT_LENGTH = 'content-length'
21
+ CONTENT_TYPE = 'content-type'
22
+ ETAG = 'etag'
23
+ EXPIRES = 'expires'
24
+ SET_COOKIE = 'set-cookie'
25
+ TRANSFER_ENCODING = 'transfer-encoding'
26
+
27
+ # HTTP method verbs
28
+ GET = 'GET'
29
+ POST = 'POST'
30
+ PUT = 'PUT'
31
+ PATCH = 'PATCH'
32
+ DELETE = 'DELETE'
33
+ HEAD = 'HEAD'
34
+ OPTIONS = 'OPTIONS'
35
+ CONNECT = 'CONNECT'
36
+ LINK = 'LINK'
37
+ UNLINK = 'UNLINK'
38
+ TRACE = 'TRACE'
39
+
40
+ # Rack environment variables
41
+ RACK_VERSION = 'rack.version'
42
+ RACK_TEMPFILES = 'rack.tempfiles'
43
+ RACK_EARLY_HINTS = 'rack.early_hints'
44
+ RACK_ERRORS = 'rack.errors'
45
+ RACK_LOGGER = 'rack.logger'
46
+ RACK_INPUT = 'rack.input'
47
+ RACK_SESSION = 'rack.session'
48
+ RACK_SESSION_OPTIONS = 'rack.session.options'
49
+ RACK_SHOWSTATUS_DETAIL = 'rack.showstatus.detail'
50
+ RACK_URL_SCHEME = 'rack.url_scheme'
51
+ RACK_HIJACK = 'rack.hijack'
52
+ RACK_IS_HIJACK = 'rack.hijack?'
53
+ RACK_RECURSIVE_INCLUDE = 'rack.recursive.include'
54
+ RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size'
55
+ RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory'
56
+ RACK_RESPONSE_FINISHED = 'rack.response_finished'
57
+ RACK_PROTOCOL = 'rack.protocol'
58
+ RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
59
+ RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
60
+ RACK_REQUEST_FORM_PAIRS = 'rack.request.form_pairs'
61
+ RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
62
+ RACK_REQUEST_FORM_ERROR = 'rack.request.form_error'
63
+ RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash'
64
+ RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string'
65
+ RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
66
+ RACK_REQUEST_QUERY_STRING = 'rack.request.query_string'
67
+ RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
68
+ end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  module Rack
4
7
 
5
- # Sets the Content-Length header on responses that do not specify
6
- # a Content-Length or Transfer-Encoding header. Note that this
7
- # does not fix responses that have an invalid Content-Length
8
+ # Sets the content-length header on responses that do not specify
9
+ # a content-length or transfer-encoding header. Note that this
10
+ # does not fix responses that have an invalid content-length
8
11
  # header specified.
9
12
  class ContentLength
10
13
  include Rack::Utils
@@ -14,25 +17,18 @@ module Rack
14
17
  end
15
18
 
16
19
  def call(env)
17
- status, headers, body = @app.call(env)
18
- headers = HeaderHash[headers]
20
+ status, headers, body = response = @app.call(env)
19
21
 
20
22
  if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
21
23
  !headers[CONTENT_LENGTH] &&
22
- !headers[TRANSFER_ENCODING]
23
-
24
- obody = body
25
- body, length = [], 0
26
- obody.each { |part| body << part; length += part.bytesize }
27
-
28
- body = BodyProxy.new(body) do
29
- obody.close if obody.respond_to?(:close)
30
- end
24
+ !headers[TRANSFER_ENCODING] &&
25
+ body.respond_to?(:to_ary)
31
26
 
32
- headers[CONTENT_LENGTH] = length.to_s
27
+ response[2] = body = body.to_ary
28
+ headers[CONTENT_LENGTH] = body.sum(&:bytesize).to_s
33
29
  end
34
30
 
35
- [status, headers, body]
31
+ response
36
32
  end
37
33
  end
38
34
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  module Rack
4
7
 
5
- # Sets the Content-Type header on responses which don't have one.
8
+ # Sets the content-type header on responses which don't have one.
6
9
  #
7
10
  # Builder Usage:
8
11
  # use Rack::ContentType, "text/plain"
@@ -13,18 +16,18 @@ module Rack
13
16
  include Rack::Utils
14
17
 
15
18
  def initialize(app, content_type = "text/html")
16
- @app, @content_type = app, content_type
19
+ @app = app
20
+ @content_type = content_type
17
21
  end
18
22
 
19
23
  def call(env)
20
- status, headers, body = @app.call(env)
21
- headers = Utils::HeaderHash[headers]
24
+ status, headers, _ = response = @app.call(env)
22
25
 
23
26
  unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
24
27
  headers[CONTENT_TYPE] ||= @content_type
25
28
  end
26
29
 
27
- [status, headers, body]
30
+ response
28
31
  end
29
32
  end
30
33
  end