How to Read Post Data in Rack Request

How to read POST data in rack request

From reading the docs for POST, looks like it is giving you parsed data based on other content types. If you want to process "application/json", you probably need to

JSON.parse( )

instead. To check this, try


where you currently have puts req.POST.

req.body is an I/O object, not a string. See the body documentation and view the source. You can see that this is in fact the same as mudasobwa's answer.

Note that other code in a Rack application may expect to read the same I/O, such as the param parsers in Sinatra or Rails. To ensure that they see the same data and not get an error, you may want to call req.body.rewind, possibly both before and after reading the request body in your code. However, if you are in such a situation, you might instead consider whether your framework has options to process JSON directly via some option on the controller or request content-type handler declaration etc - most likely there will be an option to handle this kind of request within the framework.

How to handle post request in Rack

As far as I'm aware, the idiomatic way to do this in Rack is to wrap your env in a Rack::Request object and call get?, post?, etc.

Here's a simple example:

run( do
req =
response = <<-RESP
get? #{req.get?}
post? #{}
[200, {"Content-Type" => "text/plain"}, [response]]

Here's how to check it out in action:

$ curl http://localhost:9292
get? true
post? false

$ curl --data "" http://localhost:9292
get? false
post? true

How to receive a JSON object with Rack


This worked for me. I found that using curl or wget to test POST requests against a Rack (v1.4.1) server required using this code as a fallback to get the request body. POST requests out in the wild (e.g. GitHub WebHooks) didn't have this same problem.

Receiving POST with Rack ruby server

That's because you are not returning a response. Your response is empty so you won't see anything. You can test this through cURL:

$ curl -F 'foo=bar' localhost:4001
curl: (52) Empty reply from server

Response from within app:

am I receiving anything ? 
if yes any parameters ? : {"foo"=>"bar"}

Try returning something:

app = do |env|
puts 'am I receiving anything ? '
req =
puts "if yes any parameters ? : #{req.inspect}"
[200, { 'Content-Type' => 'text/plain' }, ['Some body']]

Testing an AJAX POST using Rack::Test - how to pass in data?

Okay so my solution is a little weird and specific to the way I am triggering my JSON request in the first place, namely using jQuery Validation and jQuery Forms plugins on the client end. jQuery Forms doesn't bundle the form fields into a stringified Hash as I'd expected, but sends the form fields via AJAX but as a classic URI encoded params string. So by changing my test to the following, it now works fine.

describe 'POST /user/' do
include Rack::Test::Methods
it 'must allow user registration with valid information' do
fields = {
username: 'test_reg',
password: 'test_pass',
email: ''
post '/user', fields, {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
last_response.must_be :ok?
last_response.body.must_match 'test_reg has been saved'

Of course this is specific to the way the jQuery Forms plugin works and not at all how one would normally go about testing the POSTing of JSON data via AJAX. I hope this helps others.

How do I access the Rack environment from within Rails?

I'm pretty sure you can use the Rack::Request object for passing request-scope variables:

# middleware:
def call(env)
request = # no matter how many times you do 'new' you always get the same object
request[:foo] = 'bar'

# Controller:
def index
if params[:foo] == 'bar'

Alternatively, you can get at that "env" object directly:

# middleware:
def call(env)
env['foo'] = 'bar'

# controller:
def index
if request.env['foo'] == 'bar'

Get client's request type using RACK middleware


Content-Type tells you type of the request. Accept tells the server what the client wants as a response.

This should output the accept header to the console.

require 'rack'

class Accepter
def initialize(app, options={})
@app, @options = app, options
def call(env)
accepts = env["HTTP_ACCEPT"]
warn "accepts = #{accepts.inspect}"

Post request with body_stream and parameters

Well.. as i see it, this is a normal behaviour. I'll explain why. If you only have access to a Rack::Request,(i guess that) your middleware does not parse the response (you do not include something like ActionController::ParamsParser), so you don't have access to a hash of parameters, but to a StringIo. This StringIO corresponds to a stream like:

Content-Type: multipart/form-data; boundary=AaB03x
Content-Disposition: form-data; name="param1"
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...

What you are trying to do with the Net::HTTP class is to: (1). parse the request into a hash of parameters; (2). merge the parameters hash with your own parameters; (3). recreate the request. The problem is that Net::HTTP library can't do (1), since it is a client library, not a server one.

Therefore, you can not escape parsing some how your request before adding the new parameters.

Possible solutions:

  1. Insert ActionController::ParamsParser before your middleware. After that, you may use the excellent rest-client lib to do something like: ('http://your_server' + request.path_info), :params => params.merge(your_params)

  2. You can attempt to make a wrapper on the StringIO object, and add, at the end of stream,your own parameters. However, this is not trivial nor advisable.

Does request.body require rewind before read with Rails 4 and content-type application/json or is there a new approach?

This is indeed a bug. The fix has been integrated into master and should presumably pop up in 4.0.1.

Related Topics

Leave a reply