rack 1.6.11 → 2.1.4

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +89 -139
  5. data/Rakefile +27 -28
  6. data/SPEC +6 -7
  7. data/bin/rackup +1 -0
  8. data/contrib/rack_logo.svg +164 -111
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +4 -2
  11. data/example/protectedlobster.ru +3 -1
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +7 -1
  14. data/lib/rack/auth/basic.rb +4 -1
  15. data/lib/rack/auth/digest/md5.rb +9 -7
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +5 -4
  18. data/lib/rack/auth/digest/request.rb +3 -1
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +42 -18
  21. data/lib/rack/cascade.rb +6 -5
  22. data/lib/rack/chunked.rb +33 -10
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +11 -10
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +5 -3
  27. data/lib/rack/content_type.rb +3 -1
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +33 -53
  30. data/lib/rack/directory.rb +75 -60
  31. data/lib/rack/etag.rb +8 -5
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -149
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +17 -16
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +22 -19
  39. data/lib/rack/handler/thin.rb +6 -1
  40. data/lib/rack/handler/webrick.rb +28 -28
  41. data/lib/rack/handler.rb +9 -26
  42. data/lib/rack/head.rb +17 -17
  43. data/lib/rack/lint.rb +54 -51
  44. data/lib/rack/lobster.rb +8 -6
  45. data/lib/rack/lock.rb +17 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  49. data/lib/rack/mime.rb +27 -6
  50. data/lib/rack/mock.rb +101 -60
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +280 -161
  53. data/lib/rack/multipart/uploaded_file.rb +3 -2
  54. data/lib/rack/multipart.rb +39 -8
  55. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +11 -9
  58. data/lib/rack/reloader.rb +10 -4
  59. data/lib/rack/request.rb +447 -305
  60. data/lib/rack/response.rb +196 -83
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +12 -18
  63. data/lib/rack/sendfile.rb +19 -14
  64. data/lib/rack/server.rb +118 -41
  65. data/lib/rack/session/abstract/id.rb +215 -94
  66. data/lib/rack/session/cookie.rb +45 -28
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +25 -16
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
  71. data/lib/rack/static.rb +41 -11
  72. data/lib/rack/tempfile_reaper.rb +4 -2
  73. data/lib/rack/urlmap.rb +25 -15
  74. data/lib/rack/utils.rb +186 -272
  75. data/lib/rack.rb +76 -24
  76. data/rack.gemspec +25 -14
  77. metadata +62 -182
  78. data/HISTORY.md +0 -375
  79. data/KNOWN-ISSUES +0 -44
  80. data/lib/rack/backports/uri/common_18.rb +0 -56
  81. data/lib/rack/backports/uri/common_192.rb +0 -52
  82. data/lib/rack/backports/uri/common_193.rb +0 -29
  83. data/lib/rack/handler/evented_mongrel.rb +0 -8
  84. data/lib/rack/handler/mongrel.rb +0 -106
  85. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  86. data/lib/rack/showexceptions.rb +0 -387
  87. data/lib/rack/utils/okjson.rb +0 -600
  88. data/test/builder/anything.rb +0 -5
  89. data/test/builder/comment.ru +0 -4
  90. data/test/builder/end.ru +0 -5
  91. data/test/builder/line.ru +0 -1
  92. data/test/builder/options.ru +0 -2
  93. data/test/cgi/assets/folder/test.js +0 -1
  94. data/test/cgi/assets/fonts/font.eot +0 -1
  95. data/test/cgi/assets/images/image.png +0 -1
  96. data/test/cgi/assets/index.html +0 -1
  97. data/test/cgi/assets/javascripts/app.js +0 -1
  98. data/test/cgi/assets/stylesheets/app.css +0 -1
  99. data/test/cgi/lighttpd.conf +0 -26
  100. data/test/cgi/rackup_stub.rb +0 -6
  101. data/test/cgi/sample_rackup.ru +0 -5
  102. data/test/cgi/test +0 -9
  103. data/test/cgi/test+directory/test+file +0 -1
  104. data/test/cgi/test.fcgi +0 -8
  105. data/test/cgi/test.ru +0 -5
  106. data/test/gemloader.rb +0 -10
  107. data/test/multipart/bad_robots +0 -259
  108. data/test/multipart/binary +0 -0
  109. data/test/multipart/content_type_and_no_filename +0 -6
  110. data/test/multipart/empty +0 -10
  111. data/test/multipart/fail_16384_nofile +0 -814
  112. data/test/multipart/file1.txt +0 -1
  113. data/test/multipart/filename_and_modification_param +0 -7
  114. data/test/multipart/filename_and_no_name +0 -6
  115. data/test/multipart/filename_with_escaped_quotes +0 -6
  116. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  117. data/test/multipart/filename_with_null_byte +0 -7
  118. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  121. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  122. data/test/multipart/filename_with_unescaped_quotes +0 -6
  123. data/test/multipart/ie +0 -6
  124. data/test/multipart/invalid_character +0 -6
  125. data/test/multipart/mixed_files +0 -21
  126. data/test/multipart/nested +0 -10
  127. data/test/multipart/none +0 -9
  128. data/test/multipart/semicolon +0 -6
  129. data/test/multipart/text +0 -15
  130. data/test/multipart/three_files_three_fields +0 -31
  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 -81
  135. data/test/spec_auth_digest.rb +0 -259
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -223
  138. data/test/spec_cascade.rb +0 -61
  139. data/test/spec_cgi.rb +0 -102
  140. data/test/spec_chunked.rb +0 -101
  141. data/test/spec_commonlogger.rb +0 -93
  142. data/test/spec_conditionalget.rb +0 -102
  143. data/test/spec_config.rb +0 -22
  144. data/test/spec_content_length.rb +0 -85
  145. data/test/spec_content_type.rb +0 -45
  146. data/test/spec_deflater.rb +0 -339
  147. data/test/spec_directory.rb +0 -88
  148. data/test/spec_etag.rb +0 -107
  149. data/test/spec_fastcgi.rb +0 -107
  150. data/test/spec_file.rb +0 -221
  151. data/test/spec_handler.rb +0 -72
  152. data/test/spec_head.rb +0 -45
  153. data/test/spec_lint.rb +0 -550
  154. data/test/spec_lobster.rb +0 -58
  155. data/test/spec_lock.rb +0 -164
  156. data/test/spec_logger.rb +0 -23
  157. data/test/spec_methodoverride.rb +0 -111
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -297
  160. data/test/spec_mongrel.rb +0 -182
  161. data/test/spec_multipart.rb +0 -600
  162. data/test/spec_nulllogger.rb +0 -20
  163. data/test/spec_recursive.rb +0 -72
  164. data/test/spec_request.rb +0 -1232
  165. data/test/spec_response.rb +0 -407
  166. data/test/spec_rewindable_input.rb +0 -118
  167. data/test/spec_runtime.rb +0 -49
  168. data/test/spec_sendfile.rb +0 -130
  169. data/test/spec_server.rb +0 -167
  170. data/test/spec_session_abstract_id.rb +0 -53
  171. data/test/spec_session_cookie.rb +0 -410
  172. data/test/spec_session_memcache.rb +0 -321
  173. data/test/spec_session_pool.rb +0 -209
  174. data/test/spec_showexceptions.rb +0 -98
  175. data/test/spec_showstatus.rb +0 -103
  176. data/test/spec_static.rb +0 -145
  177. data/test/spec_tempfile_reaper.rb +0 -63
  178. data/test/spec_thin.rb +0 -91
  179. data/test/spec_urlmap.rb +0 -236
  180. data/test/spec_utils.rb +0 -647
  181. data/test/spec_version.rb +0 -17
  182. data/test/spec_webrick.rb +0 -184
  183. data/test/static/another/index.html +0 -1
  184. data/test/static/index.html +0 -1
  185. data/test/testrequest.rb +0 -78
  186. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  187. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class BodyProxy
3
5
  def initialize(body, &block)
4
- @body, @block, @closed = body, block, false
6
+ @body = body
7
+ @block = block
8
+ @closed = false
5
9
  end
6
10
 
7
- def respond_to?(*args)
8
- return false if args.first.to_s =~ /^to_ary$/
9
- super or @body.respond_to?(*args)
11
+ def respond_to?(method_name, include_all = false)
12
+ super or @body.respond_to?(method_name, include_all)
10
13
  end
11
14
 
12
15
  def close
@@ -27,13 +30,12 @@ module Rack
27
30
  # We are applying this special case for #each only. Future bugs of this
28
31
  # class will be handled by requesting users to patch their ruby
29
32
  # implementation, to save adding too many methods in this class.
30
- def each(*args, &block)
31
- @body.each(*args, &block)
33
+ def each
34
+ @body.each { |body| yield body }
32
35
  end
33
36
 
34
- def method_missing(*args, &block)
35
- super if args.first.to_s =~ /^to_ary$/
36
- @body.__send__(*args, &block)
37
+ def method_missing(method_name, *args, &block)
38
+ @body.__send__(method_name, *args, &block)
37
39
  end
38
40
  end
39
41
  end
data/lib/rack/builder.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Builder implements a small DSL to iteratively construct Rack
3
5
  # applications.
@@ -29,29 +31,43 @@ module Rack
29
31
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
30
32
 
31
33
  class Builder
34
+
35
+ # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
36
+ UTF_8_BOM = '\xef\xbb\xbf'
37
+
32
38
  def self.parse_file(config, opts = Server::Options.new)
33
- options = {}
34
- if config =~ /\.ru$/
35
- cfgfile = ::File.read(config)
36
- if cfgfile[/^#\\(.*)/] && opts
37
- options = opts.parse! $1.split(/\s+/)
38
- end
39
- cfgfile.sub!(/^__END__\n.*\Z/m, '')
40
- app = new_from_string cfgfile, config
39
+ if config.end_with?('.ru')
40
+ return self.load_file(config, opts)
41
41
  else
42
42
  require config
43
- app = Object.const_get(::File.basename(config, '.rb').capitalize)
43
+ app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
44
+ return app, {}
44
45
  end
46
+ end
47
+
48
+ def self.load_file(path, opts = Server::Options.new)
49
+ options = {}
50
+
51
+ cfgfile = ::File.read(path)
52
+ cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
53
+
54
+ if cfgfile[/^#\\(.*)/] && opts
55
+ options = opts.parse! $1.split(/\s+/)
56
+ end
57
+
58
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
59
+ app = new_from_string cfgfile, path
60
+
45
61
  return app, options
46
62
  end
47
63
 
48
- def self.new_from_string(builder_script, file="(rackup)")
64
+ def self.new_from_string(builder_script, file = "(rackup)")
49
65
  eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
50
66
  TOPLEVEL_BINDING, file, 0
51
67
  end
52
68
 
53
- def initialize(default_app = nil,&block)
54
- @use, @map, @run, @warmup = [], nil, default_app, nil
69
+ def initialize(default_app = nil, &block)
70
+ @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
55
71
  instance_eval(&block) if block_given?
56
72
  end
57
73
 
@@ -81,10 +97,11 @@ module Rack
81
97
  def use(middleware, *args, &block)
82
98
  if @map
83
99
  mapping, @map = @map, nil
84
- @use << proc { |app| generate_map app, mapping }
100
+ @use << proc { |app| generate_map(app, mapping) }
85
101
  end
86
102
  @use << proc { |app| middleware.new(app, *args, &block) }
87
103
  end
104
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
88
105
 
89
106
  # Takes an argument that is an object that responds to #call and returns a Rack response.
90
107
  # The simplest form of this is a lambda object:
@@ -96,7 +113,7 @@ module Rack
96
113
  # class Heartbeat
97
114
  # def self.call(env)
98
115
  # [200, { "Content-Type" => "text/plain" }, ["OK"]]
99
- # end
116
+ # end
100
117
  # end
101
118
  #
102
119
  # run Heartbeat
@@ -113,7 +130,7 @@ module Rack
113
130
  #
114
131
  # use SomeMiddleware
115
132
  # run MyApp
116
- def warmup(prc=nil, &block)
133
+ def warmup(prc = nil, &block)
117
134
  @warmup = prc || block
118
135
  end
119
136
 
@@ -141,10 +158,17 @@ module Rack
141
158
  @map[path] = block
142
159
  end
143
160
 
161
+ # Freeze the app (set using run) and all middleware instances when building the application
162
+ # in to_app.
163
+ def freeze_app
164
+ @freeze_app = true
165
+ end
166
+
144
167
  def to_app
145
168
  app = @map ? generate_map(@run, @map) : @run
146
169
  fail "missing run or map statement" unless app
147
- app = @use.reverse.inject(app) { |a,e| e[a] }
170
+ app.freeze if @freeze_app
171
+ app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
148
172
  @warmup.call(app) if @warmup
149
173
  app
150
174
  end
@@ -156,8 +180,8 @@ module Rack
156
180
  private
157
181
 
158
182
  def generate_map(default_app, mapping)
159
- mapped = default_app ? {'/' => default_app} : {}
160
- mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
183
+ mapped = default_app ? { '/' => default_app } : {}
184
+ mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
161
185
  URLMap.new(mapped)
162
186
  end
163
187
  end
data/lib/rack/cascade.rb CHANGED
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Cascade tries a request on several apps, and returns the
3
5
  # first response that is not 404 or 405 (or in a list of configurable
4
6
  # status codes).
5
7
 
6
8
  class Cascade
7
- NotFound = [404, {CONTENT_TYPE => "text/plain"}, []]
9
+ NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
8
10
 
9
11
  attr_reader :apps
10
12
 
11
- def initialize(apps, catch=[404, 405])
12
- @apps = []; @has_app = {}
13
+ def initialize(apps, catch = [404, 405])
14
+ @apps = []
13
15
  apps.each { |app| add app }
14
16
 
15
17
  @catch = {}
@@ -39,12 +41,11 @@ module Rack
39
41
  end
40
42
 
41
43
  def add(app)
42
- @has_app[app] = true
43
44
  @apps << app
44
45
  end
45
46
 
46
47
  def include?(app)
47
- @has_app.include? app
48
+ @apps.include?(app)
48
49
  end
49
50
 
50
51
  alias_method :<<, :add
data/lib/rack/chunked.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -10,7 +12,7 @@ module Rack
10
12
  # A body wrapper that emits chunked responses
11
13
  class Body
12
14
  TERM = "\r\n"
13
- TAIL = "0#{TERM}#{TERM}"
15
+ TAIL = "0#{TERM}"
14
16
 
15
17
  include Rack::Utils
16
18
 
@@ -18,21 +20,38 @@ module Rack
18
20
  @body = body
19
21
  end
20
22
 
21
- def each
23
+ def each(&block)
22
24
  term = TERM
23
25
  @body.each do |chunk|
24
- size = bytesize(chunk)
26
+ size = chunk.bytesize
25
27
  next if size == 0
26
28
 
27
- chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
29
+ chunk = chunk.b
28
30
  yield [size.to_s(16), term, chunk, term].join
29
31
  end
30
32
  yield TAIL
33
+ insert_trailers(&block)
34
+ yield TERM
31
35
  end
32
36
 
33
37
  def close
34
38
  @body.close if @body.respond_to?(:close)
35
39
  end
40
+
41
+ private
42
+
43
+ def insert_trailers(&block)
44
+ end
45
+ end
46
+
47
+ class TrailerBody < Body
48
+ private
49
+
50
+ def insert_trailers(&block)
51
+ @body.trailers.each_pair do |k, v|
52
+ yield "#{k}: #{v}\r\n"
53
+ end
54
+ end
36
55
  end
37
56
 
38
57
  def initialize(app)
@@ -43,7 +62,7 @@ module Rack
43
62
  # a version (nor response headers)
44
63
  def chunkable_version?(ver)
45
64
  case ver
46
- when "HTTP/1.0", nil, "HTTP/0.9"
65
+ when 'HTTP/1.0', nil, 'HTTP/0.9'
47
66
  false
48
67
  else
49
68
  true
@@ -54,15 +73,19 @@ module Rack
54
73
  status, headers, body = @app.call(env)
55
74
  headers = HeaderHash.new(headers)
56
75
 
57
- if ! chunkable_version?(env['HTTP_VERSION']) ||
58
- STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
76
+ if ! chunkable_version?(env[SERVER_PROTOCOL]) ||
77
+ STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
59
78
  headers[CONTENT_LENGTH] ||
60
- headers['Transfer-Encoding']
79
+ headers[TRANSFER_ENCODING]
61
80
  [status, headers, body]
62
81
  else
63
82
  headers.delete(CONTENT_LENGTH)
64
- headers['Transfer-Encoding'] = 'chunked'
65
- [status, headers, Body.new(body)]
83
+ headers[TRANSFER_ENCODING] = 'chunked'
84
+ if headers['Trailer']
85
+ [status, headers, TrailerBody.new(body)]
86
+ else
87
+ [status, headers, Body.new(body)]
88
+ end
66
89
  end
67
90
  end
68
91
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/body_proxy'
2
4
 
3
5
  module Rack
@@ -23,13 +25,13 @@ module Rack
23
25
  # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
24
26
  FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
25
27
 
26
- def initialize(app, logger=nil)
28
+ def initialize(app, logger = nil)
27
29
  @app = app
28
30
  @logger = logger
29
31
  end
30
32
 
31
33
  def call(env)
32
- began_at = Time.now
34
+ began_at = Utils.clock_time
33
35
  status, header, body = @app.call(env)
34
36
  header = Utils::HeaderHash.new(header)
35
37
  body = BodyProxy.new(body) { log(env, status, header, began_at) }
@@ -39,22 +41,21 @@ module Rack
39
41
  private
40
42
 
41
43
  def log(env, status, header, began_at)
42
- now = Time.now
43
44
  length = extract_content_length(header)
44
45
 
45
46
  msg = FORMAT % [
46
47
  env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
47
48
  env["REMOTE_USER"] || "-",
48
- now.strftime("%d/%b/%Y:%H:%M:%S %z"),
49
+ Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
49
50
  env[REQUEST_METHOD],
50
51
  env[PATH_INFO],
51
- env[QUERY_STRING].empty? ? "" : "?"+env[QUERY_STRING],
52
- env["HTTP_VERSION"],
52
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
53
+ env[SERVER_PROTOCOL],
53
54
  status.to_s[0..3],
54
55
  length,
55
- now - began_at ]
56
+ Utils.clock_time - began_at ]
56
57
 
57
- logger = @logger || env['rack.errors']
58
+ logger = @logger || env[RACK_ERRORS]
58
59
  # Standard library logger doesn't support write but it supports << which actually
59
60
  # calls to write on the log device without formatting
60
61
  if logger.respond_to?(:write)
@@ -65,8 +66,8 @@ module Rack
65
66
  end
66
67
 
67
68
  def extract_content_length(headers)
68
- value = headers[CONTENT_LENGTH] or return '-'
69
- value.to_s == '0' ? '-' : value
69
+ value = headers[CONTENT_LENGTH]
70
+ !value || value.to_s == '0' ? '-' : value
70
71
  end
71
72
  end
72
73
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -68,7 +70,7 @@ module Rack
68
70
  # anything shorter is invalid, this avoids exceptions for common cases
69
71
  # most common being the empty string
70
72
  if since && since.length >= 16
71
- # NOTE: there is no trivial way to write this in a non execption way
73
+ # NOTE: there is no trivial way to write this in a non exception way
72
74
  # _rfc2822 returns a hash but is not that usable
73
75
  Time.rfc2822(since) rescue nil
74
76
  else
data/lib/rack/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Config modifies the environment using the block given during
3
5
  # initialization.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
  require 'rack/body_proxy'
3
5
 
@@ -15,14 +17,14 @@ module Rack
15
17
  status, headers, body = @app.call(env)
16
18
  headers = HeaderHash.new(headers)
17
19
 
18
- if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
20
+ if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
19
21
  !headers[CONTENT_LENGTH] &&
20
- !headers['Transfer-Encoding'] &&
22
+ !headers[TRANSFER_ENCODING] &&
21
23
  body.respond_to?(:to_ary)
22
24
 
23
25
  obody = body
24
26
  body, length = [], 0
25
- obody.each { |part| body << part; length += bytesize(part) }
27
+ obody.each { |part| body << part; length += part.bytesize }
26
28
 
27
29
  body = BodyProxy.new(body) do
28
30
  obody.close if obody.respond_to?(:close)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -19,7 +21,7 @@ module Rack
19
21
  status, headers, body = @app.call(env)
20
22
  headers = Utils::HeaderHash.new(headers)
21
23
 
22
- unless STATUS_WITH_NO_ENTITY_BODY.include?(status)
24
+ unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
23
25
  headers[CONTENT_TYPE] ||= @content_type
24
26
  end
25
27
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regexp has `match?` since Ruby 2.4
4
+ # so to support Ruby < 2.4 we need to define this method
5
+
6
+ module Rack
7
+ module RegexpExtensions
8
+ refine Regexp do
9
+ def match?(string, pos = 0)
10
+ !!match(string, pos)
11
+ end
12
+ end unless //.respond_to?(:match?)
13
+ end
14
+ end
data/lib/rack/deflater.rb CHANGED
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "zlib"
2
4
  require "time" # for Time.httpdate
3
5
  require 'rack/utils'
4
6
 
7
+ require_relative 'core_ext/regexp'
8
+
5
9
  module Rack
6
10
  # This middleware enables compression of http responses.
7
11
  #
8
12
  # Currently supported compression algorithms:
9
13
  #
10
14
  # * gzip
11
- # * deflate
12
15
  # * identity (no transformation)
13
16
  #
14
17
  # The middleware automatically detects when compression is supported
@@ -16,19 +19,26 @@ module Rack
16
19
  # directive of 'no-transform' is present, or when the response status
17
20
  # code is one that doesn't allow an entity body.
18
21
  class Deflater
22
+ using ::Rack::RegexpExtensions
23
+
19
24
  ##
20
25
  # Creates Rack::Deflater middleware.
21
26
  #
22
27
  # [app] rack app instance
23
28
  # [options] hash of deflater options, i.e.
24
29
  # 'if' - a lambda enabling / disabling deflation based on returned boolean value
25
- # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 }
30
+ # e.g use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }
26
31
  # 'include' - a list of content types that should be compressed
32
+ # 'sync' - determines if the stream is going to be flushed after every chunk.
33
+ # Flushing after every chunk reduces latency for
34
+ # time-sensitive streaming applications, but hurts
35
+ # compression and throughput. Defaults to `true'.
27
36
  def initialize(app, options = {})
28
37
  @app = app
29
38
 
30
39
  @condition = options[:if]
31
40
  @compressible_types = options[:include]
41
+ @sync = options[:sync] == false ? false : true
32
42
  end
33
43
 
34
44
  def call(env)
@@ -41,11 +51,11 @@ module Rack
41
51
 
42
52
  request = Request.new(env)
43
53
 
44
- encoding = Utils.select_best_encoding(%w(gzip deflate identity),
54
+ encoding = Utils.select_best_encoding(%w(gzip identity),
45
55
  request.accept_encoding)
46
56
 
47
57
  # Set the Vary HTTP header.
48
- vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
58
+ vary = headers["Vary"].to_s.split(",").map(&:strip)
49
59
  unless vary.include?("*") || vary.include?("Accept-Encoding")
50
60
  headers["Vary"] = vary.push("Accept-Encoding").join(",")
51
61
  end
@@ -53,37 +63,34 @@ module Rack
53
63
  case encoding
54
64
  when "gzip"
55
65
  headers['Content-Encoding'] = "gzip"
56
- headers.delete(CONTENT_LENGTH)
57
- mtime = headers.key?("Last-Modified") ?
58
- Time.httpdate(headers["Last-Modified"]) : Time.now
59
- [status, headers, GzipStream.new(body, mtime)]
60
- when "deflate"
61
- headers['Content-Encoding'] = "deflate"
62
- headers.delete(CONTENT_LENGTH)
63
- [status, headers, DeflateStream.new(body)]
66
+ headers.delete('Content-Length')
67
+ mtime = headers["Last-Modified"]
68
+ mtime = Time.httpdate(mtime).to_i if mtime
69
+ [status, headers, GzipStream.new(body, mtime, @sync)]
64
70
  when "identity"
65
71
  [status, headers, body]
66
72
  when nil
67
73
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
68
74
  bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
69
- [406, {CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s}, bp]
75
+ [406, { 'Content-Type' => "text/plain", 'Content-Length' => message.length.to_s }, bp]
70
76
  end
71
77
  end
72
78
 
73
79
  class GzipStream
74
- def initialize(body, mtime)
80
+ def initialize(body, mtime, sync)
81
+ @sync = sync
75
82
  @body = body
76
83
  @mtime = mtime
77
- @closed = false
78
84
  end
79
85
 
80
86
  def each(&block)
81
87
  @writer = block
82
- gzip =::Zlib::GzipWriter.new(self)
83
- gzip.mtime = @mtime
88
+ gzip = ::Zlib::GzipWriter.new(self)
89
+ gzip.mtime = @mtime if @mtime
84
90
  @body.each { |part|
85
- gzip.write(part)
86
- gzip.flush
91
+ len = gzip.write(part)
92
+ # Flushing empty parts would raise Zlib::BufError.
93
+ gzip.flush if @sync && len > 0
87
94
  }
88
95
  ensure
89
96
  gzip.close
@@ -95,39 +102,8 @@ module Rack
95
102
  end
96
103
 
97
104
  def close
98
- return if @closed
99
- @closed = true
100
- @body.close if @body.respond_to?(:close)
101
- end
102
- end
103
-
104
- class DeflateStream
105
- DEFLATE_ARGS = [
106
- Zlib::DEFAULT_COMPRESSION,
107
- # drop the zlib header which causes both Safari and IE to choke
108
- -Zlib::MAX_WBITS,
109
- Zlib::DEF_MEM_LEVEL,
110
- Zlib::DEFAULT_STRATEGY
111
- ]
112
-
113
- def initialize(body)
114
- @body = body
115
- @closed = false
116
- end
117
-
118
- def each
119
- deflator = ::Zlib::Deflate.new(*DEFLATE_ARGS)
120
- @body.each { |part| yield deflator.deflate(part, Zlib::SYNC_FLUSH) }
121
- yield deflator.finish
122
- nil
123
- ensure
124
- deflator.close
125
- end
126
-
127
- def close
128
- return if @closed
129
- @closed = true
130
105
  @body.close if @body.respond_to?(:close)
106
+ @body = nil
131
107
  end
132
108
  end
133
109
 
@@ -136,8 +112,8 @@ module Rack
136
112
  def should_deflate?(env, status, headers, body)
137
113
  # Skip compressing empty entity body responses and responses with
138
114
  # no-transform set.
139
- if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
140
- headers[CACHE_CONTROL].to_s =~ /\bno-transform\b/ ||
115
+ if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
116
+ /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
141
117
  (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
142
118
  return false
143
119
  end
@@ -148,6 +124,10 @@ module Rack
148
124
  # Skip if @condition lambda is given and evaluates to false
149
125
  return false if @condition && !@condition.call(env, status, headers, body)
150
126
 
127
+ # No point in compressing empty body, also handles usage with
128
+ # Rack::Sendfile.
129
+ return false if headers[CONTENT_LENGTH] == '0'
130
+
151
131
  true
152
132
  end
153
133
  end