Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Rack::File serves gzipped assets when available #479

Open
wants to merge 1 commit into
from
Jump to file or symbol
Failed to load files and symbols.
+86 −0
Split
View
@@ -34,6 +34,7 @@ def self.release
autoload :ContentType, "rack/content_type"
autoload :ETag, "rack/etag"
autoload :File, "rack/file"
+ autoload :GzFile, "rack/gz_file"
autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory"
autoload :ForwardRequest, "rack/recursive"
View
@@ -0,0 +1,41 @@
+module Rack
+ # Rack::GzFile behaves exactly the same as Rack::File, except that it will
+ # also serve up a gzip encoding of a file, if one is available on the
+ # filesystem.
+ #
+ # For each request, Rack::GzFile first checks the filesystem for a file with a
+ # .gz extension. If one is found, the appropriate encoding headers are added
+ # to the response and the gzip file is served.
+ #
+ # If no .gz file is found, Rack::GzFile will behave exactly like Rack::File.
+ class GzFile
+ def initialize(root, headers={}, default_mime = 'text/plain')
+ @file_server = Rack::File.new(root, headers, default_mime)
+ @default_mime = default_mime
+ end
+
+ def call(env)
+ path_info = env['PATH_INFO']
+
+ status, headers, body = @file_server.call(
+ env.merge('PATH_INFO' => path_info + '.gz')
+ )
+
+ gzip_available = status != 404
+
+ if !gzip_available || env['HTTP_ACCEPT_ENCODING'] !~ /\bgzip\b/
+ status, headers, body = @file_server.call(env)
+ else
@balexand

balexand Aug 1, 2013

@jbaudanza While testing this in development, I got this Rack::Lint error. Apparently, the Content-Type shouldn't be set for 304 Not Modified response.

image

I solved this by changing this line to:

elsif status != 304

I don't think this is the best solution. I'd be happy to propose a more robust solution if the maintainers of Rack have any interest in this pull-request.

@jbaudanza

jbaudanza Aug 1, 2013

Thanks. good catch!

@jjb

jjb Nov 24, 2013

@jbaudanza remember to fix this...

@jbaudanza

jbaudanza Nov 25, 2013

@jjb holding off on any work on this until I get a nod from a maintainer.

+ headers['Content-Type'] = Mime.mime_type(::File.extname(path_info),
+ @default_mime)
+ headers['Content-Encoding'] = 'gzip'
+ end
+
+ if gzip_available
+ headers['Vary'] = 'Accept-Encoding'
+ end
+
+ [status, headers, body]
+ end
+ end
+end
@@ -0,0 +1 @@
+Hello, Rack!
Binary file not shown.
View
@@ -0,0 +1,43 @@
+require 'rack/gz_file'
+require 'rack/lint'
+require 'rack/mock'
+require 'zlib'
+
+describe Rack::GzFile do
+ DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
+
+ def request
+ Rack::MockRequest.new(Rack::Lint.new(Rack::GzFile.new(DOCROOT)))
+ end
+
+ should "serve an uncompressed file when gzip is not supported by the server" do
+ res = request.get('/cgi/assets/index.html')
+ res.body.should.equal "### TestFile ###\n"
+ res.headers.should.not.include 'Vary'
+ res.headers.should.not.include('Content-Encoding')
+ res.headers['Content-Length'].should.equal(
+ File.size(DOCROOT + '/cgi/assets/index.html').to_s)
+ end
+
+ should "serve an uncompressed file when gzip is not supported by the client" do
+ res = request.get('/cgi/assets/compress_me.html')
+ res.body.should.equal 'Hello, Rack!'
+ res.headers['Vary'].should.equal 'Accept-Encoding'
+ res.headers.should.not.include('Content-Encoding')
+ res.headers['Content-Length'].should.equal(
+ File.size(DOCROOT + '/cgi/assets/compress_me.html').to_s)
+ end
+
+ should "serve a compressed file" do
+ res = request.get('/cgi/assets/compress_me.html',
+ 'HTTP_ACCEPT_ENCODING' => 'gzip')
+
+ gz = Zlib::GzipReader.new(StringIO.new(res.body))
+ gz.read.should.equal "Hello, Rack!"
+ res.headers['Vary'].should.equal 'Accept-Encoding'
+ res.headers['Content-Encoding'].should.equal 'gzip'
+ res.headers['Content-Type'].should.equal 'text/html'
+ res.headers['Content-Length'].should.equal(
+ File.size(DOCROOT + '/cgi/assets/compress_me.html.gz').to_s)
+ end
+end
@jjb

jjb Nov 24, 2013

should put a newline here