aBadAssCowboy
aBadAssCowboy

Reputation: 2520

rails json response with gzip compression

I have an api written in rails which on each request responds with a JSON response.

The response could be huge, so i need to compress the JSON response using gzip.

Wondering how to do this in rails controller?

I have added the line

use Rack::Deflater

in config.ru

Should I also be changing something in the line which renders JSON?

render :json => response.to_json()

Also, how do i check if the response is in gzip format or not..??

I did a curl request from terminal, I see only the normal plain JSON.

Upvotes: 30

Views: 22658

Answers (5)

Weihang Jian
Weihang Jian

Reputation: 8715

Consider not putting Rack middlewares in config.ru when using Rails

Rails has it's own middleware stack manager since Rails 2.

The correct way is:

# config/application.rb or config/environment.rb depends on your Rails version
config.middleware.use Rack::Deflater

Don't use @djcp's solution when using Rack::ETag

Short answer:

module MyApp
  class Application < Rails::Application
    config.middleware.insert_before Rack::ETag, Rack::Deflater
  end
end

The order of Rack::Deflater and Rack::ETag matters because Rack::Deflater uses Zlib::GzipWriter to compress the response body and it would compress with a timestamp by default, which means the compressed response body would change every second even if the original response body is the same.

To reproduce this problem, run the following script:

require 'rack/etag'
require 'rack/deflater'
require 'rack/content_length'

@app = Rack::Builder.new do
  use Rack::ETag
  use Rack::Deflater
  use Rack::ContentLength
  run ->(*) { [200, {}, ['hello world']] }
end

def puts_etag
  puts @app.call({ 'HTTP_ACCEPT_ENCODING' => 'gzip' })[1]['ETag']
end

puts_etag
sleep 1
puts_etag

One can simply swap the lines of Rack::ETag and Rack::Deflater and get the expected output.

Rails uses Rack::ETag by default and config.middleware.use is just appending. To insert Rack::Deflater before Rack::Etag, use config.middleware.insert_before instead.

🍻

Upvotes: 1

aBadAssCowboy
aBadAssCowboy

Reputation: 2520

For the response to be in gzip format we don't have to change the render method call.
If the request has the header Accept-Encoding: gzip, Rails will automatically compress the JSON response using gzip.

If you don't want the user to send a request with preset header., you can add the header to the request manually in the controller before rendering the response:

request.env['HTTP_ACCEPT_ENCODING'] = 'gzip'
render :json => response.to_json()

Upvotes: 16

Lev Lukomskyi
Lev Lukomskyi

Reputation: 6667

In some cases you can consider to write huge response into a file and gzip it:

res = {} # huge data hash
json = res.to_json

Zlib::GzipWriter.open('public/api/huge_data.json.gz') { |gz| gz.write json }

and update this file regularly

Upvotes: 1

djcp
djcp

Reputation: 699

My post Content Compression with Rack::Deflater describes a couple of ways to integrate Rack::Deflater. The easiest would be to just update config/application.rb with:

module YourApp
  class Application < Rails::Application
    config.middleware.use Rack::Deflater
  end
end

and you'll automatically compress all controller responses with deflate / gzip if the client explicitly says they can handle it.

Upvotes: 37

CuriousMind
CuriousMind

Reputation: 34145

You can query Curl by setting a custom header to get gzipped response

$ curl -H "Accept-Encoding: gzip, deflate" localhost:3000/posts.json > posts_json.gz

then, then decompress it to view the actual response json

 $ gzip -d posts_json.gz
 $ cat posts_json

If it doesn't work. post back with output of rake middlewares to help us troubleshoot further.

Upvotes: 5

Related Questions