From 6b5dcfcf93cc34f2bfd1f435ae29548018315f2c Mon Sep 17 00:00:00 2001 From: Jonathan Baudanza Date: Tue, 26 Feb 2013 22:15:50 -0800 Subject: [PATCH] Added GzFile for serving gzipped static files --- lib/rack.rb | 1 + lib/rack/gz_file.rb | 41 ++++++++++++++++++++++++++ test/cgi/assets/compress_me.html | 1 + test/cgi/assets/compress_me.html.gz | Bin 0 -> 49 bytes test/spec_gz_file.rb | 43 ++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 lib/rack/gz_file.rb create mode 100644 test/cgi/assets/compress_me.html create mode 100644 test/cgi/assets/compress_me.html.gz create mode 100644 test/spec_gz_file.rb diff --git a/lib/rack.rb b/lib/rack.rb index 57119df3a..a05985c66 100644 --- a/lib/rack.rb +++ b/lib/rack.rb @@ -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" diff --git a/lib/rack/gz_file.rb b/lib/rack/gz_file.rb new file mode 100644 index 000000000..c97d3f3cc --- /dev/null +++ b/lib/rack/gz_file.rb @@ -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 + 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 diff --git a/test/cgi/assets/compress_me.html b/test/cgi/assets/compress_me.html new file mode 100644 index 000000000..3f0ce957d --- /dev/null +++ b/test/cgi/assets/compress_me.html @@ -0,0 +1 @@ +Hello, Rack! \ No newline at end of file diff --git a/test/cgi/assets/compress_me.html.gz b/test/cgi/assets/compress_me.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..8c2c944aec5d25d44e3e23a5ed645a01e2d9c0e2 GIT binary patch literal 49 zcmb2|=HN)5r5nh=oSdIqP?TC+9G{!2mr;_N!|>VT?8%eY13A2W&V{isNKGk^;$dK5 F006B&5YPYs literal 0 HcmV?d00001 diff --git a/test/spec_gz_file.rb b/test/spec_gz_file.rb new file mode 100644 index 000000000..7332e56c4 --- /dev/null +++ b/test/spec_gz_file.rb @@ -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 \ No newline at end of file