Rails JSONP Parsing error on receiving end - ruby-on-rails

I have a very specific issue...
I have 2 rails apps:
App 1 makes a request to App 2 and gets a valid JSONP response.
App 2 has been outfitted with Rack JSONP Middleware so it returns with the
correct wrapped callback (i.e. JQuery1919191([{[{"find_item":{"geo_lon":-74....etc)
For some reason App 1 throws the following error in javascript console:
JSONP Error: parsererror Error: jQueryXXXXXX was not called.
It works fine, however, when testing this in a non-rails context. See my fiddle for an example: http://jsfiddle.net/pBDyW/16/ This code (that works in this example) is the exact same code I'm using in my rails app.
What I'm confused about is HOW am I supposed to configure App 1 (the receiving app) to handle an incoming JSONP resuqest properly. I'm convinced it must be something with Rails, as calling this from a generic page, served in Apache, works fine.
Please HELP!

It turns out that there are (for whatever reason) subtleties in the way the rails app (App 1) seems to process the incoming request. I modified my approach, which was to render the JSON response in my controller with:
respond_to :json
def json_result
#blah = [stuff from database]
respond_with #blah
end
methods (recommended with the Rack JSON Middlware approach, and restored my JSON response to:
render :json => #find_results, :callback => params[:callback]
The difference here is an odd one. The rails app JSONP call will only succeed if I execute the response like this:
success: callbackFunction
and NOT
success: function(){//do stuff}
However, the latter example works in the JS fiddle. Weird. I guess it's just some kinda formatting discrepancy in Rails 3+.
Anyway, hope this was helpful to anyone who comes across it!

Related

Rails: what's providing my API with default exception-handling?

I'm working on a Rails 5 JSON API, and I've noticed that it's providing sensible exception-handling out of the box.
For example, ActiveRecord::RecordNotFound results in a 404, ActionController::ParameterMissing results in 400, and these are all returned as well-formed JSON responses.
I've found plenty of documentation on rescue_from, can't locate what's providing this exception-handling for me.
I've dug around in the Rails guides, the docs, as well as in the rails console, e.g.
ActionController::API.new.rescue_handlers
=> []
ApplicationController.new.rescue_handlers
=> []
I'd like to know where the exception-handling is coming from because I've noticed that it's gracefully applied for real requests, but not in controller specs.
For example, if I run a local server and send it a malformed request with curl, I get a sensible error response. But if I try to reproduce that in a controller spec,
it "returns 404 if Yogurt doesn't exist" do
put :update, params: { id: 293459 }
end
the test throws the raw exception (ActiveRecord::RecordNotFound in this case).
When you set up a Rails 5 app with the --api option you end up using ActionController::API. https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/api.rb
The modules are built here: https://github.com/rails/rails/blob/a0061d2389a178b093f0d3f64f58236ffbe088e0/actionpack/lib/action_controller/api.rb#L104 so that's where your responses are coming ultimately from. Looks like the rendering is being handed off to metal at some point.
API actions are configured to return :json by default, so that's why your test is behaving differently than your browser.
For your spec to work the same way you'll have to supply a format:
put :update, params: { id: 293459 }, format: :json

InvalidCrossOriginRequest when trying to send a Javascript Asset

I'm trying to create an "asset controller" shim which will filter static asset requests so only authorized users can get retrieve certain assets. I wanted to continue to use the asset pipeline so I setup a route like this
get 'assets/*assetfile' => 'assets#sendfile'
Then I created an AssetsController with one method "sendfile". Stripping it down to only the stuff that matters, it looks like this:
class AssetsController < ApplicationController
def sendfile
# Basically the following function forces the file
# path to be Rails.root/public/assets/basename
assetfilename=sanitize_filename(params[:assetfile] + '.' + params[:format])
send_file(assetfilename)
end
end
It looks like I have to run this in production mode as rails by-passes my route for assets in development. So I precompile my assets and I can verify in the controller that the files exist where they are expected to be.
However, now the problem is that I'm getting a "ActionController::InvalidCrossOriginRequest" when the Javascript asset is requested (just using the default application.* assets for now). I've read about this error and I understand that as of Rails 4.1 there are special cross-origin protections for Javascript assets. Sounds good to me, but I don't understand where the "cross-origin" part is coming from. Using firebug, I can see that the asset requests are being requested from the same domain as the original page.
I am certain that this is the problem because I can solve it by putting "skip_before_action :verify_authenticity_token" in the beginning of my controller. However, I really don't want to do this (I don't fully understand why this check is necessary, but I'm sure there are very good reasons).
The application.html.erb file is unchanged from the default generated file so I assume it's sending the CSRF token when the request is made, just as it would if I didn't have my own controller for assets.
So what am I missing?
Ok, I think I answered my own question (unsatisfactorily). Again, long post, so bear with me. I mistakenly forgot to add this to my original questions, but I'm using Ruby 2.2.0 and Rails 4.2.4.
From looking at the code in "actionpack-4.2.4/lib/action_controller/metal/request_forgery_protection.rb", it looks like Rails is doing two checks. The first check is the "verify_authenticity_token" method which does the expected validation of the authenticity token for POST requests. For GET requests, it ALSO sets a flag which causes a second check on the formed computed response to the request.
The check on the response simply says that if the request was NOT an XHR (AJAX) request AND the MIME Type of the response is "text/javascript", then raise an "ActionController::InvalidCrossOriginRequest", which was the error I was getting.
I verified this by setting the type to "application/javascript" for ".js" files in "send_file". Here's the code:
if request.format.js?
send_file(assetfilename, type: 'application/javascript')
else
send_file(assetfilename)
end
I can skip the response check all together by just adding the following line to the top of my controller class:
skip_after_action :verify_same_origin_request
The check on the response seems pretty weak to me and it's not clear how this really provides further protection against CSRF. But I'll post that in another question.

Using NetHTTP within Rails to hit Rails

We have a very unique use case where we want a Rails controller to access a route within the Rails app using Net::HTTP. Can this be done? I'm currently receiving a timeout when attempting to do so. The current code works when the uri is a separate Rails app, but not when the uri belongs to the app itself. Here's the gist of the current controller action:
def export_data
uri = URI("http://localhost:3000")
#data = JSON.parse( Net::HTTP.get(uri) )
respond_to do |format|
...
end
end
Forget why we want to do this. Why doesn't this work? Is there a modification that can be made to get it to work? Thanks in advance!
It doesn't work because you are not using a multi-threaded server. Your request is coming in and blocking the server until it's complete. During that time, you're making a request to your localhost that isn't being handled.
Easy solution? Try puma. Other easy solution, spin up two rails instances, connect to the 2nd instance.

Rails API functional tests with XML body

I've written functional tests for API endpoints built in Rails using shoulda testing framework.
An example looks like the following:
setup do
authenticated_xml_request('xml-file-name')
post :new
end
should respond_with :success
authenticated_xml_request is a test helper method that sets
#request.env['RAW_POST_DATA'] with XML content.
After upgrading the app from rails 2.3.3 to rails 2.3.8, the functional tests are failing because the XML content received is not merged in the params hash.
I'm setting the request with the correct mime type via #request.accept =
"text/xml"
I'm able to inspect the content of the request using request.raw_post but i'd like to keep the current setup working.
Also, running a test from the terminal using cURL or any other library (rest_http) in development mode, the API works perfectly well.
Any tips or help is much appreciated.
Now it's simpler:
post "/api/v1/users.xml", user.to_xml, "CONTENT_TYPE" => 'application/xml'
Note that you have to specify appropriate "CONTENT_TYPE". In other case your request will go as 'application/x-www-form-urlencoded' and xml won't be parsed properly.
I solved the issue by adding a custom patch to rails (test_process.rb file) to convert incoming xml to hash then merge into parameters hash.
on line 439:
parameters ||= {}
parameters.merge!(Hash.from_xml(#request.env['RAW_POST_DATA'])) if #request.env['RAW_POST_DATA'] && #request.env['CONTENT_TYPE']=='application/xml'

In Rails how can I get the contents of a page without making an HTTP request?

This may be really obvious, but in rails how can I get the contents of a page without making an HTTP request?
i.e. I want to grab the contents of any page that my rails app generates, in a script on the same server. This is of course trivial with an HTTP request, but how can I get the contents of a page otherwise?
This is what I am doing now which works fine:
require 'open-uri'
contents = open("http://localhost/path_to_my_page").read # but I really want to get rid of the overhead of the HTTP request
I need to be able to do this from a script, within the rails app, not a controller (e.g. with render_to_string)
This is how you get the contents of a page in the console. It might work for you:
require 'action_controller/integration'
app = ActionController::Integration::Session.new;
app.get('/path_to_your_page')
puts app.response.body
In your controller , you can call render_to_string instead of render and the page is returned instead of being send to the browser.
The arguments of both methods are the same, so you need to specify which controller and action you require to render.
Why not use erb directly? See this question for more details

Resources