public
Created

custom rack_deflater.rb, for hardwiredcms

  • Download Gist
rack_deflater.rb
Ruby
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
require "zlib"
require "stringio"
require "time" # for Time.httpdate
require 'rack/utils'
 
module Hardwired
class Deflater
 
DEFAULT_CONTENT_TYPES =
[
# All html, text, css, and csv content should be compressed
"text/plain",
"text/html",
"text/csv",
"text/css",
 
# Only vector graphics and uncompressed bitmaps can benefit from compression.
#GIF, JPG, and PNG already use a lz* algorithm, and certain browsers can get confused.
"image/x-icon",
"image/svg+xml",
"application/x-font-ttf",
"application/x-font-opentype",
"application/vnd.ms-fontobject",
 
# All javascript should be compressed
"text/javascript",
"application/ecmascript",
"application/json",
"application/javascript",
 
# All xml should be compressed
"text/xml",
"application/xml",
"application/xml-dtd",
"application/soap+xml",
"application/xhtml+xml",
"application/rdf+xml",
"application/rss+xml",
"application/atom+xml"]
 
##
# Creates Rack::Deflater middleware.
#
# [app] rack app instance
# [options] hash of deflater options, i.e.
# 'min_length' - minimum content length to trigger deflating (defaults to 1024 bytes)
# 'skip_if' - a lambda which, if evaluates to true, skips deflating
# 'include_types' - a lambda (Ruby 1.9+) or an array denoting mime-types to compress
def initialize(app, options = {})
@app = app
 
@min_length = options[:min_length] || 1024
@skip_if = options[:skip_if]
@include_types = options[:include_types] || DEFAULT_CONTENT_TYPES
end
 
def call(env)
status, headers, body = @app.call(env)
headers = Rack::Utils::HeaderHash.new(headers)
 
unless should_deflate?(env, status, headers, body)
return [status, headers, body]
end
 
request = Rack::Request.new(env)
 
encoding = Rack::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
@body.close if @body.respond_to?(:close)
gzip.close
@writer = nil
end
 
def write(data)
@writer.call(data)
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
@body.close if @body.respond_to?(:close)
deflater.close
end
end
 
private
 
def should_deflate?(env, status, headers, body)
# Skip compressing empty entity body responses and responses with
# no-transform set.
if Rack::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 false
end
 
# Skip if response body is too short
if @min_length > headers['Content-Length'].to_i
return false
end
 
# Skip if :skip_if lambda is provided and evaluates to true
if @skip_if &&
@skip_if.call(env, status, headers, body)
return false
end
 
 
mime_type = headers['Content-Type'].gsub(/;.*\Z/,"").downcase
# Skip if :include is provided and evaluates to false
if @include_types &&
!((@include_types === mime_type) ||
(@include_types.respond_to?(:"include?") &&
@include_types.include?(mime_type)))
puts "Not compressing #{mime_type}"
return false
end
 
true
end
end
end

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.