Rails requests missing the HTTP body

August 15, 2009 Adam Milligan

This is a bug in Rails that quite likely affects you, but which you’ve even more likely never experienced. I’ve posted it here for the benefit of the small number of people who will run into this problem and turn to Google for help.

In short, if you use Mongrel app servers (this may affect Passenger as well, I don’t know), the first HTTP request to your Rails app after you restart your servers, or otherwise reload your environment, will have an empty HTTP body.

I say you’ve likely never experienced this because the majority of HTTP requests to your Rails app are likely GET requests, which always have empty HTTP bodies. After that first request everything will work just fine. Even if you’re unlucky enough to receive a POST or a PUT request containing a body immediately after restart it will only fail once, which you could easily write off an an anomaly. You also won’t see this behavior in your development environment, or any environment in which you use Mongrel as a web server rather than just an app server.

If you’re interested in a patch for the bug, I’ve submitted one to Rails here.

The source of the problem lies in how ActionController initializes itself. In the actionpack gem you’ll find the lib/action_controller/cgi_ext.rb file, which does little more than load the three files in the cgi_ext directory:

require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
...

The cgi_ext/query_extension.rb file is the interesting one:

require 'cgi'

class CGI #:nodoc:
  module QueryExtension
    # Remove the old initialize_query method before redefining it.
    remove_method :initialize_query

    # Neuter CGI parameter parsing.
    def initialize_query
      # Fix some strange request environments.
      env_table['REQUEST_METHOD'] ||= 'GET'

      # POST assumes missing Content-Type is application/x-www-form-urlencoded.
      if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
        env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
      end

      @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
      @params = {}
    end
  end
end

This replaces the default #initialize_query method provided by Ruby’s CGI library:

    def initialize_query()
      if ("POST" == env_table['REQUEST_METHOD']) and
         %r|Amultipart/form-data.*boundary="?([^";,]+)"?|n.match(env_table['CONTENT_TYPE'])
        boundary = $1.dup
        @multipart = true
        @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
      else
        @multipart = false
        @params = CGI::parse(
                    case env_table['REQUEST_METHOD']
                    when "GET", "HEAD"
                      if defined?(MOD_RUBY)
                        Apache::request.args or ""
                      else
                        env_table['QUERY_STRING'] or ""
                      end
                    when "POST"
                      stdinput.binmode if defined? stdinput.binmode
# =====>              stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
                    else
                      read_from_cmdline
                    end
                  )
      end

      @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
    end

The interesting line is the one I’ve marked with a comment rocket. Notice how it reads from stdinput; this leaves the read pointer at the end of the input stream. Now look back at the Rails override for this method, and notice how it does not read from stdinput, thus leaving the read pointer at the start of the input stream.

This is all fine and dandy as long as all of the ActionController code loads up and patches the CGI library properly. However, ActionController doesn’t load the cgi_ext.rb file (or its dependencies) until it references either the CgiRequest or CGIHandler classes (which require cgi_process.rb, which require cgi_ext.rb), as part of the first request, which is after the default Ruby CGI library has read the input stream containing the request body. ActionController then tries to read the request body assuming the read pointer is at the start of the stream. Oops. Subsequent requests work fine, because everything has now been loaded.

Finding the source of this bug took some doing (Chris Heisterkamp, aka “The Hammer” and I tracked it down together), but the fix is easy. If you look at the patch you’ll see it’s simply a single require in action_controller.rb. You can achieve the same result by requiring ‘action_controller/cgi_ext’ in an initializer file in your app.

Like many problems, this one should go away in Rails 3. Rails has deprecated use of the CGI library, and the CGI extensions have already been removed from the Rails master branch. However, it’s a real problem now, and will remain so for at least some amount of time.

About the Author

Biography

Previous
Marshal.dump vs YAML::dump
Marshal.dump vs YAML::dump

We find ourselves with a project with a very large dataset, more than 2 million items. This dataset changes...

Next
When 2,147,483,647 just isn't enough.
When 2,147,483,647 just isn't enough.

If only I had a paid a little more attention to any of the Facebook is growing more quickly than the Univer...