How to accept gzipped requests in a rails 5 application?
Asked Answered
L

4

9

In the past I've used this solution, but since Rails 5 deprecated ParamsParser middleware, it doesn't work anymore.

Linkage answered 25/10, 2016 at 16:39 Comment(3)
Does it work if you insert it before ActionDispatch::Cookies? Which is the middleware after it and still exists.Charlesettacharleston
you can implement the solution using nginx to do the decompression nginx.com/resources/admin-guide/compression-and-decompression in this way rails doesn't have to deal with it and get the content right.Partlet
Many thanks for bounty. I glad it helped to you!Nguyetni
N
8

Just add this:

# config/initializers/middlewares.rb
require 'compressed_requests'

Rails.application.configure do
  config.middleware.insert_before 0, CompressedRequests
end

Then add the middleware from this Gist to lib/middleware/compressed_requests.rb:

class CompressedRequests
  def initialize(app)
    @app = app
  end

  def method_handled?(env)
    !!(env['REQUEST_METHOD'] =~ /(POST|PUT)/)
  end

  def encoding_handled?(env)
    ['gzip', 'deflate'].include? env['HTTP_CONTENT_ENCODING']
  end
  
  def call(env)
    if method_handled?(env) && encoding_handled?(env)
      extracted = decode(env['rack.input'], env['HTTP_CONTENT_ENCODING'])

      env.delete('HTTP_CONTENT_ENCODING')
      env['CONTENT_LENGTH'] = extracted.bytesize
      env['rack.input'] = StringIO.new(extracted)
    end

    @app.call(env)
  end
  
  def decode(input, content_encoding)
    case content_encoding
      when 'gzip' then Zlib::GzipReader.new(input).read
      when 'deflate' then Zlib::Inflate.inflate(input.read)
    end
  end
end

You can test it with:

# config/routes.rb
post '/', to: 'welcome#create'

# app/controllers/welcome_controller.rb
class WelcomeController < ActionController::Base
  def create
    render json: params
  end
end

And do the request:

$ curl --data-binary @<(echo "Uncompressed data" | gzip) \
  -H "CONTENT_ENCODING: gzip" \
  localhost:3000
{"Uncompressed data\n":null,"controller":"welcome","action":"create"}%
Nguyetni answered 3/11, 2016 at 14:32 Comment(0)
M
3

this should work if you insert the middleware right before Rack::Head

config.middleware.insert_before Rack::Head, "CompressedRequests"

this should do the trick

you can check your app's middleware stack with the following command

rake middleware
Matthieu answered 30/10, 2016 at 13:2 Comment(2)
your response is almost the same of @itsnikolay, I only accepted his instead of yours because he put a little more explanation and tests. But thanks anyway :)Linkage
i added it first! haha but does not matter as long as it solved ur prob :)Matthieu
P
3

If you have NginX in front of Unicorns, then you can just tell NginX to uncompress the data for you

http://www.pataliebre.net/howto-make-nginx-decompress-a-gzipped-request.html#.WBzSt-ErIUE

https://www.nginx.com/resources/admin-guide/compression-and-decompression/

Pargeting answered 3/11, 2016 at 23:16 Comment(1)
this doesn't work. it only decompress responses, not requests.Linkage
U
2

On your routes.rb file:

post 'my_endpoint', to: 'api#my_endpoint'

On your api_controller.rb:

class ApiController < ActionController::Base
  def my_endpoint
    render json: JSON.parse(ActiveSupport::Gzip.decompress(request.body.string))
  end
end

I've tested this on a brand new Rails 5 app using the Paw client:

enter image description here

Underhill answered 3/11, 2016 at 20:47 Comment(2)
is isn't really a good solution, since it's specific per action and you would be doing the decompression by hand every time.Linkage
@DiegoPlentz if you want to reuse this you could have a decompressed_body method holding the important bits (JSON.parse(ActiveSupport::Gzip.decompress(request.body.string))). That method could live in your API base controller, or in a controller concern enabling you to reuse it and keeping your code DRY.Underhill

© 2022 - 2024 — McMap. All rights reserved.