Gzip Assets in Sinatra App

gzip assets in Sinatra app

It is that easy (isn't that nice:) but if you want to check, then look at the Content-Encoding response header and it should say gzip. In a webkit browser it's in the developer tools under "Network", then select the resource, like app.min.css and the "Headers" tab.

A way to test for this is given in the following blog post:

http://artsy.github.io/blog/2012/02/24/10x-rack-and-rails-output-compression-with-rack-deflater/

I modified the specs into shared examples, so I can add them in where I really want to check:

shared_examples "Compressed pages" do
subject { last_response.headers }
its(["Content-Encoding"]) { should be_nil }
context "After compression" do
before do
get page
@etag = last_response.headers["Etag"]
@content_length = last_response.headers["Content-Length"]
get page, {}, { "HTTP_ACCEPT_ENCODING" => "gzip" }
end
its(["Etag"]) { should == @etag }
its(["Content-Length"]) { should_not == @content_length }
its(["Content-Encoding"]) { should == "gzip"}
end
end

My main spec uses it like this:

  describe "Public pages" do

describe "Home page", :type => :request do
let(:page) { "/" }
it_behaves_like "Compressed pages"

The it_behaves_like "Compressed pages" will run that shared example and check it has the right header etc.

Using gzip compression in Sinatra with Ruby

After zipping the file you would simply return the result and ensure to set the header Content-Encoding: gzip for the response. Google has a nice, little introduction to gzip compression and what you have to watch out for. Here is what you could do in Sinatra:

get '/whatever' do
headers['Content-Encoding'] = 'gzip'
StringIO.new.tap do |io|
gz = Zlib::GzipWriter.new(io)
begin
gz.write(File.read('public/Assets/Styles/build.css'))
ensure
gz.close
end
end.string
end

One final word of caution, though. You should probably choose this approach only for content that you created on the fly or if you just want to use gzip compression in a few places.

If, however, your goal is to serve most or even all of your static resources with gzip compression enabled, then it will be a much better solution to rely on what is already supported by your web server instead of polluting your code with this detail. There's a good chance that you can enable gzip compression with some configuration settings. Here's an example of how it is done for nginx.

Another alternative would be to use the Rack::Deflater middleware.

Returning gzipped content on a Sinatra app

There's 2 things that come to mind to try.

Firstly, instead of using RoR as the router, let Rack handle it. There are several ways you could do this instead, the easiest is probably:

# config.ru
require 'sinatra_module'
require 'rails_app'

map "/" do
run RailsApp
end

map "/v2" do
use Rack::Deflater # you might want to put this in the Sinatra app.
run MySinatraModule
end

The other thing you might try is setting the Content-Encoding header to "gzip", or, if that doesn't work you could try setting the Content-type header to "application/x-gzip" (I'm much more dubious on changing the type header though). Rack::Deflater should handle that for you though.

Asset Pipeline in a sinatra app

I think you'd be better to add the root path using a proc:

set :assets, Proc.new { Sprockets::Environment.new(root) {|env|
env.append_path File.join( root, "/assets/js")
env.append_path File.join(root, "/assets/css")
# more…
}}

See if that improves things. You might want to use Pry or just warn to check that the value of settings.assets["app.js"] is what you expect it to be.

Workarounds

Like I said in the comments above, things don't always map well from one framework to another. Personally, I precompile my assets using Guard/SASS and Guard/Coffeescript into the public folder. There are also minification libraries that hook up with Guard I then use Sinatra Static Assets or Sinatra Exstatic* to point to the files in views/layouts. I don't like to combine the javascript into one file (YMMV).

I also wanted the jQuery stuff that Rails added in via jquery-rails, so I wrote rack-jquery, rack-jquery_ui, and rack-jquery_ui-themes. They may be of interest to you.

Another way to get Sprockets working for you would be to use Rack. I found this blog post that shows you how:

http://metabates.com/2011/08/31/using-sprockets-without-rails/

  • I also wrote Sinatra Exstatic, it's a fork of Sinatra Static Assets. It's a recent fork, if you use it any feedback will be welcome :)

Additional, now that the templates are posted in the question:

Sinatra won't do anything magic for you to point to the "super" css/js file, so if you have several CSS and javascript links then a client will still make several requests to the individual files. One way around this would be (in the case of the JS) to only have one statement, e.g:

<!-- Javascript -->
<script src="/assets/js/app.js" type="text/javascript"></script>

and that's it. Another way to do this would be to keep all the statements you've got, but catch every statement using the route, e.g:

  # The local variable `name` isn't used below
# but just in case you ever need it, it's there
get "/assets/js/:name.js" do |name|
content_type :javascript
settings.assets["app.js"]
end

Sinatra static assets are not found when using rackup

Looks like there are two good answers to this one (neither of the existing ones worked for me).

First off, in your config.ru file, you can include the following:

# Replace the directory names to taste
use Rack::Static, :urls => ['/stylesheets', '/javascripts'], :root => 'public'

Alternatively, if you're running your app via rackup, the :static option is set to false by default. You can remedy this by the following incantation:

class MyApp < Sinatra::Base
set :static, true
# ...
end

How to enable gzip compression for static Rack sites on Heroku Cedar?

From my previous experience with Sprockets, Sinatra, and the Rack::Deflater, I was pretty sure I was just another use Rack::Deflater line away from what I wanted.

I changed the config.ru to this:

use Rack::Static,
:urls => ["/images", "/js", "/css"],
:root => "public"
use Rack::Deflater

run lambda # ...same as in the question

and I was able to verify that responses were sent gzipped:

$ curl -H 'Accept-Encoding: gzip' http://localhost:9292 | file -
/dev/stdin: gzip compressed data

but not for static assets under /css, /js, or /images:

$ curl -H 'Accept-Encoding: gzip' http://localhost:9292/css/bootstrap.min.css | file -
/dev/stdin: ASCII English text, with very long lines

And that's when I realized this was a standard middleware stack—Rack::Static intercepts the call to static files and thus skips the following stack! That's why it worked for public/index.html but not for assets.

The following config.ru worked (note that use Rack::Deflater now precedes use Rack::Static):

use Rack::Deflater
use Rack::Static,
:urls => ["/images", "/js", "/css"],
:root => "public"

run lambda { |env|
[
200,
{
'Content-Type' => 'text/html',
'Cache-Control' => 'public, max-age=86400'
},
File.open('public/index.html', File::RDONLY)
]
}

Verified with:

$ curl -H 'Accept-Encoding: gzip' http://localhost:9292/css/bootstrap.min.css | file -
/dev/stdin: gzip compressed data, from Unix

How to compress assets without Rails or Sprockets?

You may take a look at Gulp. Here is the good article about using Gulp with Rails (but it would work for Sinatra too).

Where should I set HTTP headers, such as Expires?

After talking though and answering this question and seeing the comment above, I think I have figured out the answer to my own question.

The whole point of nginx actually removes the first two options.

That leads to Option #3. This is where all the other content config is set, such as gzip compression.



Related Topics



Leave a reply



Submit