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( req.body.read )
instead. To check this, try
puts req.body.read
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:
# config.ru
run(Proc.new do
req = Rack::Request.new(env)
response = <<-RESP
get? #{req.get?}
post? #{req.post?}
RESP
[200, {"Content-Type" => "text/plain"}, [response]]
end)
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
env['rack.input'].gets
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 = Proc.new do |env|
puts 'am I receiving anything ? '
req = Rack::Request.new(env).params
puts "if yes any parameters ? : #{req.inspect}"
[200, { 'Content-Type' => 'text/plain' }, ['Some body']]
end
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: 'test@testreg.co'
}
post '/user', fields, {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
last_response.must_be :ok?
last_response.body.must_match 'test_reg has been saved'
end
end
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 = Rack::Request.new(env) # no matter how many times you do 'new' you always get the same object
request[:foo] = 'bar'
@app.call(env)
end
# Controller:
def index
if params[:foo] == 'bar'
...
end
end
Alternatively, you can get at that "env
" object directly:
# middleware:
def call(env)
env['foo'] = 'bar'
@app.call(env)
end
# controller:
def index
if request.env['foo'] == 'bar'
...
end
end
Get client's request type using RACK middleware
See http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
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
end
def call(env)
accepts = env["HTTP_ACCEPT"]
warn "accepts = #{accepts.inspect}"
@app.call
end
end
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
--AaB03x
Content-Disposition: form-data; name="param1"
value1
--AaB03x
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--
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:
Insert ActionController::ParamsParser before your middleware. After that, you may use the excellent rest-client lib to do something like:
RestClient.post ('http://your_server' + request.path_info), :params => params.merge(your_params)
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
Rails Guides Offline Documentation
Bundle Install Tries to Use Cache File
Append Row to CSV File Ruby 1.9 CSV Lib
How to Read from Redis Inside a Multi Block in Ruby
How to Break Out of a Map/Collect and Return Whatever Has Been Collected Up to That Point
Solid Tutorial for Building a Simple Wiki Application in Ruby on Rails
Get Available Diskspace in Ruby
An Error Occurred While Installing Curb (0.8.5)
Simulating Race Conditions in Rspec Unit Tests
How to Do a Before_Action in Ruby (Like in Rails)
What Do I Need to Do to Get Hash.From_Xml() to Work
Heroku Rails Rake Task to Sync Production & Local Db