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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +89 -139
- data/Rakefile +27 -28
- data/SPEC +6 -7
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +7 -1
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +5 -4
- data/lib/rack/auth/digest/request.rb +3 -1
- data/lib/rack/body_proxy.rb +11 -9
- data/lib/rack/builder.rb +42 -18
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +33 -10
- data/lib/rack/{commonlogger.rb → common_logger.rb} +11 -10
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +5 -3
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +33 -53
- data/lib/rack/directory.rb +75 -60
- data/lib/rack/etag.rb +8 -5
- data/lib/rack/events.rb +156 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +18 -17
- data/lib/rack/handler/fastcgi.rb +17 -16
- data/lib/rack/handler/lsws.rb +14 -12
- data/lib/rack/handler/scgi.rb +22 -19
- data/lib/rack/handler/thin.rb +6 -1
- data/lib/rack/handler/webrick.rb +28 -28
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +17 -17
- data/lib/rack/lint.rb +54 -51
- data/lib/rack/lobster.rb +8 -6
- data/lib/rack/lock.rb +17 -10
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +101 -60
- data/lib/rack/multipart/generator.rb +11 -12
- data/lib/rack/multipart/parser.rb +280 -161
- data/lib/rack/multipart/uploaded_file.rb +3 -2
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +218 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +447 -305
- data/lib/rack/response.rb +196 -83
- data/lib/rack/rewindable_input.rb +5 -14
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +19 -14
- data/lib/rack/server.rb +118 -41
- data/lib/rack/session/abstract/id.rb +215 -94
- data/lib/rack/session/cookie.rb +45 -28
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +25 -16
- data/lib/rack/show_exceptions.rb +392 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
- data/lib/rack/static.rb +41 -11
- data/lib/rack/tempfile_reaper.rb +4 -2
- data/lib/rack/urlmap.rb +25 -15
- data/lib/rack/utils.rb +186 -272
- data/lib/rack.rb +76 -24
- data/rack.gemspec +25 -14
- metadata +62 -182
- data/HISTORY.md +0 -375
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -321
- data/test/spec_session_pool.rb +0 -209
- data/test/spec_showexceptions.rb +0 -98
- data/test/spec_showstatus.rb +0 -103
- data/test/spec_static.rb +0 -145
- data/test/spec_tempfile_reaper.rb +0 -63
- data/test/spec_thin.rb +0 -91
- data/test/spec_urlmap.rb +0 -236
- data/test/spec_utils.rb +0 -647
- data/test/spec_version.rb +0 -17
- data/test/spec_webrick.rb +0 -184
- data/test/static/another/index.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/body_proxy.rb
CHANGED
@@ -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
|
6
|
+
@body = body
|
7
|
+
@block = block
|
8
|
+
@closed = false
|
5
9
|
end
|
6
10
|
|
7
|
-
def respond_to?(
|
8
|
-
|
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
|
31
|
-
@body.each
|
33
|
+
def each
|
34
|
+
@body.each { |body| yield body }
|
32
35
|
end
|
33
36
|
|
34
|
-
def method_missing(*args, &block)
|
35
|
-
|
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
|
-
|
34
|
-
|
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
|
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
|
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
|
-
#
|
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
|
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 = []
|
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
|
-
@
|
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}
|
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
|
26
|
+
size = chunk.bytesize
|
25
27
|
next if size == 0
|
26
28
|
|
27
|
-
chunk = chunk.
|
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
|
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[
|
58
|
-
STATUS_WITH_NO_ENTITY_BODY.
|
76
|
+
if ! chunkable_version?(env[SERVER_PROTOCOL]) ||
|
77
|
+
STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
|
59
78
|
headers[CONTENT_LENGTH] ||
|
60
|
-
headers[
|
79
|
+
headers[TRANSFER_ENCODING]
|
61
80
|
[status, headers, body]
|
62
81
|
else
|
63
82
|
headers.delete(CONTENT_LENGTH)
|
64
|
-
headers[
|
65
|
-
|
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 =
|
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? ? "" : "
|
52
|
-
env[
|
52
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
53
|
+
env[SERVER_PROTOCOL],
|
53
54
|
status.to_s[0..3],
|
54
55
|
length,
|
55
|
-
|
56
|
+
Utils.clock_time - began_at ]
|
56
57
|
|
57
|
-
logger = @logger || env[
|
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]
|
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
|
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
data/lib/rack/content_length.rb
CHANGED
@@ -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.
|
20
|
+
if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
|
19
21
|
!headers[CONTENT_LENGTH] &&
|
20
|
-
!headers[
|
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
|
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)
|
data/lib/rack/content_type.rb
CHANGED
@@ -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.
|
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 { |
|
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
|
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
|
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(
|
57
|
-
mtime = headers
|
58
|
-
|
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, {
|
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
|
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
|
-
|
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.
|
140
|
-
|
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
|