-
Notifications
You must be signed in to change notification settings - Fork 0
/
deflater.rb
129 lines (114 loc) · 3.66 KB
/
deflater.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require "zlib"
require "stringio"
require "time" # for Time.httpdate
require 'rack/utils'
module Rack
# This middleware enables compression of http responses.
#
# Currently supported compression algorithms:
#
# * gzip
# * deflate
# * identity (no transformation)
#
# The middleware automatically detects when compression is supported
# and allowed. For example no transformation is made when a cache
# directive of 'no-transform' is present, or when the response status
# code is one that doesn't allow an entity body.
class Deflater
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
# Skip compressing empty entity body responses and responses with
# no-transform set.
if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
headers['Cache-Control'].to_s =~ /\bno-transform\b/ ||
(headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
return [status, headers, body]
end
request = Request.new(env)
encoding = Utils.select_best_encoding(%w(gzip deflate identity),
request.accept_encoding)
# Set the Vary HTTP header.
vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
unless vary.include?("*") || vary.include?("Accept-Encoding")
headers["Vary"] = vary.push("Accept-Encoding").join(",")
end
case encoding
when "gzip"
headers['Content-Encoding'] = "gzip"
headers.delete('Content-Length')
mtime = headers.key?("Last-Modified") ?
Time.httpdate(headers["Last-Modified"]) : Time.now
[status, headers, GzipStream.new(body, mtime)]
when "deflate"
headers['Content-Encoding'] = "deflate"
headers.delete('Content-Length')
[status, headers, DeflateStream.new(body)]
when "identity"
[status, headers, body]
when nil
body.close if body.respond_to?(:close)
message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
[406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
end
end
class GzipStream
def initialize(body, mtime)
@body = body
@mtime = mtime
end
def each(&block)
@writer = block
@gzip =::Zlib::GzipWriter.new(self)
@gzip.mtime = @mtime
@body.each { |part|
@gzip.write(part)
@gzip.flush
}
ensure
close
@writer = nil
end
def write(data)
@writer.call(data)
end
def close
begin
@gzip.close if @gzip && !@gzip.closed?
rescue Zlib::GzipFile::Error
# For some reason this error is still thrown despite the .closed?
# check on the previous line.
end
@body.close if @body.respond_to?(:close)
end
end
class DeflateStream
DEFLATE_ARGS = [
Zlib::DEFAULT_COMPRESSION,
# drop the zlib header which causes both Safari and IE to choke
-Zlib::MAX_WBITS,
Zlib::DEF_MEM_LEVEL,
Zlib::DEFAULT_STRATEGY
]
def initialize(body)
@body = body
end
def each
@deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
@body.each { |part| yield @deflater.deflate(part, Zlib::SYNC_FLUSH) }
yield @deflater.finish
nil
ensure
close
end
def close
@deflater.close if @deflater && !@deflater.closed?
@body.close if @body.respond_to?(:close)
end
end
end
end