1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Enable gzip compression by default

If someone is using ActionDispatch::Static to serve assets and makes it past the `match?` then the file exists on disk and it will be served. This PR adds in logic that checks to see if the file being served is already compressed (via gzip) and on disk, if it is it will be served as long as the client can handle gzip encoding. If not, then a non gzip file will be served.

This additional logic slows down an individual asset request but should speed up the consumer experience as compressed files are served and production applications should be delivered with a CDN. This PR allows a CDN to cache a gzip file by setting the `Vary` header appropriately. In net this should speed up a production application that are using Rails as an origin for a CDN. Non-asset request speed is not affected in this PR.
This commit is contained in:
schneems 2014-08-11 13:29:25 -05:00
parent 2e355fe0c7
commit cfaaacd976
7 changed files with 77 additions and 17 deletions

View file

@ -1,3 +1,9 @@
* Requests that hit `ActionDispatch::Static` can now take advantage
of gzipped assets on disk. By default a gzip asset will be served if
the client supports gzip and a compressed file is on disk.
*Richard Schneeman*
* `ActionController::Parameters` will stop inheriting from `Hash` and
`HashWithIndifferentAccess` in the next major release. If you use any method
that is not available on `ActionController::Parameters` you should consider

View file

@ -16,8 +16,9 @@ module ActionDispatch
def initialize(root, cache_control)
@root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
headers = cache_control && { 'Cache-Control' => cache_control }
@file_server = ::Rack::File.new(@root, headers)
headers = {}
headers['Cache-Control'] = cache_control if cache_control
@file_server = ::Rack::File.new(@root, headers)
end
def match?(path)
@ -36,23 +37,48 @@ module ActionDispatch
end
def call(env)
@file_server.call(env)
end
def ext
@ext ||= begin
ext = ::ActionController::Base.default_static_extension
"{,#{ext},/index#{ext}}"
path = env['PATH_INFO']
gzip_file_exists = gzip_file_exists?(path)
if gzip_file_exists && gzip_encoding_accepted?(env)
env['PATH_INFO'] = "#{path}.gz"
status, headers, body = @file_server.call(env)
headers['Content-Encoding'] = 'gzip'
headers['Content-Type'] = content_type(path)
else
status, headers, body = @file_server.call(env)
end
headers['Vary'] = 'Accept-Encoding' if gzip_file_exists
return [status, headers, body]
end
def unescape_path(path)
URI.parser.unescape(path)
end
private
def ext
@ext ||= begin
ext = ::ActionController::Base.default_static_extension
"{,#{ext},/index#{ext}}"
end
end
def escape_glob_chars(path)
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
end
def unescape_path(path)
URI.parser.unescape(path)
end
def escape_glob_chars(path)
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
end
def content_type(path)
::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
end
def gzip_encoding_accepted?(env)
env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
end
def gzip_file_exists?(path)
File.exist?(File.join(@root, "#{::Rack::Utils.unescape(path)}.gz"))
end
end
# This middleware will attempt to return the contents of a file's body from

View file

@ -1,6 +1,7 @@
# encoding: utf-8
require 'abstract_unit'
require 'rbconfig'
require 'zlib'
module StaticTests
def test_serves_dynamic_content
@ -106,6 +107,18 @@ module StaticTests
end
end
def test_serves_gzip_files_when_header_set
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip')
assert_gzip file_name, response
assert_equal 'application/javascript', response.headers['Content-Type']
assert_equal 'Accept-Encoding', response.headers["Vary"]
assert_equal 'gzip', response.headers["Content-Encoding"]
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => '')
refute_equal 'gzip', response.headers["Content-Encoding"]
end
# Windows doesn't allow \ / : * ? " < > | in filenames
unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
def test_serves_static_file_with_colon
@ -125,13 +138,20 @@ module StaticTests
private
def assert_gzip(file_name, response)
expected = File.read("#{FIXTURE_LOAD_PATH}/#{public_path}" + file_name)
actual = Zlib::GzipReader.new(StringIO.new(response.body)).read
assert_equal expected, actual
end
def assert_html(body, response)
assert_equal body, response.body
assert_equal "text/html", response.headers["Content-Type"]
refute response.headers.key?("Vary")
end
def get(path)
Rack::MockRequest.new(@app).request("GET", path)
def get(path, headers = {})
Rack::MockRequest.new(@app).request("GET", path, headers)
end
def with_static_file(file)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long