Custom Rack Middleware for response compression

If you are serving a Rails application, you are probably already using some sort of compression for the responses. If not, enabling simple gzip compression is pretty straightforward (using a simple middleware) and can give boosts of as much as 80% in some cases. You can put this in an initializer somewhere and your responses will be gzipped if the receiver supports it.

config.middleware.insert_after ActionDispatch::Static, Rack::Deflater

The middleware automatically detects when compression is supported and allowed. For example, no transformation is made when a cache directive of ‘no-transform’ is present, or when the response status code is one that doesn’t allow an entity body.

But, if you have a really specific use case, you can go one step further. For example, if you deal mostly in JSON responses, messagepack could help your API get even leaner. A word of caution before you venture further, don’t expect huge savings if you are already using gzip compression as messagepack savings can vary hugely based on the kind of data you are transmitting over the socket.

Let’s create a custom middleware that can compress responses with messagepack before sending them out. A few things that we need to keep in mind for this extension are:

  1. It only compresses if there’s a specific header field (X-MessagePack-Compression) in the request signaling that it wants a compressed response.
  2. Since messagepack only works on JSON, make sure that it is executed only for JSON responses.

With these conditions, here’s a sample middleware that you can put in.

module MessagePack
  class RackMiddleware
    def initialize(app)
      @app = app
    end

    def call(env)
      status, headers, body = @app.call(env)
      return [status, headers, body] unless compress?(env, headers)

      suppress(Exception) do
        body = MessagePack.pack(Oj.load(body.body))
        headers["X-MessagePack-Compression"] = true
      end
      [status, headers, [body]]
    end

    private

    def compress?(env, headers)
      env["HTTP_X_MESSAGEPACK_COMPRESSION"] &&
        headers["Content-Type"].include?("application/json;")
    end
  end
end

Using the middleware is easy, you just need one extra line during initialization:

Rails.application.config.middleware.use MessagePack::RackMiddleware
Published 27 Nov 2018

I build mobile and web applications. Full Stack, Rails, React, Typescript, Kotlin, Swift
Pulkit Goyal on Twitter